카나리, NX 보호기법이 사용되었다. 기본적으로 지금 풀고 있는 문제는 전부 bof 관련된 문제이므로, canary leak을 해주어야 할 것이고, NX로 인해 스택 영역에 실행권한이 없을 것이므로 쉘코드를 실행할 수 없을 것. 따라서 system("/bin/sh") 을 실행할 수 있도록 함수의 주소, 문자열의 주소, 가젯 등을 이용해야 할 것이다.
C 코드를 살펴보도록 하자.
#include<stdio.h>
#include<unistd.h>
constchar*binsh="/bin/sh";
intmain(){
charbuf[0x30];
setvbuf(stdin,0,_IONBF,0);
setvbuf(stdout,0,_IONBF,0);
// Add system function to plt's entry
system("echo 'system@plt");
// Leak canary
printf("[1] Leak Canary\n");
printf("Buf: ");
read(0,buf,0x100);
printf("Buf: %s\n",buf);
// Overwrite return address
printf("[2] Overwrite return address\n");
printf("Buf: ");
read(0,buf,0x100);
return0;
}
문제가 친절하다. "/bin/sh"의 주소도 주고, system 함수도 사용해주면서 system 함수도 사용해주면서 system 함수를 이용할 수 있도록 한다.
그 다음으로는, read함수를 통해 buf에 값을 써주고 출력해주면서 canary leak을 해줄 수 있도록 한다.
그 다음으로는, return address 를 overwrite해주는, 즉, 또다시 bof를 이용해야한다. 이를 통해 RTL을 성공적으로 마칠 수 있을 것 같다.
그렇다면 공격 시나리오는 Canary Leak -> get system address, get /bin/sh address, geet gadget -> Return To Library
이렇게 볼 수 있을 것 같다.
Canary Leak
카나리 릭은 지금까지 많이 해왔으니까... 간단하게 빠르게 넘어가자.
read함수에서 buf 배열을 이용하는데, read함수를 사용하는 곳으로 가보면
buf 배열이 [rbp-0x40]에 있는 것을 알 수 있다. 그렇다면 SFP(RBP)와 BUF의 사이에는 dummy data 8바이트와 canary 8바이트가 있을 것이다. 이를 바탕으로 페이로드를 구성해보자.
이렇게 카나리를 Leak 할 수 있겠다.
RTL 정보 추출하기.
위 코드에서 "/bin/sh"이라는 문자열도 사용되었고, system함수도 사용되었으므로 둘 다 메모리상에 주소가 존재할 것이다. 한번 찾아보자.
/bin/sh 전역변수의 주소plt 테이블
이로써 필요한 정보를 수집하는 데 성공했다. 이제 이걸 어떻게 적용하면 될까?
이렇게 페이로드를 구성하면 된다.
SFP(rbp)를 덮는 것 까지는 이전과 같은데, RET 자리에 ret gadget을 두었다. 이를 통해 그 다음 명령어들까지 실행을 할 수 있다. (사실 이 가젯까지는 잘 이해를 하지 못했는데, 이거 없이 실행할 때 오류가 나면 이걸 넣어서 해결한다고 하길래)
<가젯 찾는 법>
그 다음으로는 pop rdi, ret이 있다. 이 명령어를 실행하면, "/bin/sh"이 저장된 주소를 pop 해서 rdi에 저장한다.
다들 rdi가 뭔지는 잘 알 것이다. 바로 함수의 첫 번째 인자로 들어가는 녀석이다. 인자까지 준비를 완료했으니, ret 가젯이 실행되면, 인자가 system 함수로 들어가 최종적으로 system("/bin/sh") 명령이 실행되는 것이다.
이 문제는 작동하고 있는 서비스(ssp_001)의 바이너리와 소스코드가 주어집니다. 프로그램의 취약점을 찾고 SSP 방어 기법을 우회하여 익스플로잇해 셸을 획득한 후, “flag” 파일을 읽으세요. “flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다. 플래그의 형식은 DH{…} 입니다.
switch안에 들어가는 인자는 read함수에서 받아오니 이건 p.send를 써야하겠다... 라는 생각을 하며 진행해보도록 하자.
총 세 가지 기능이 구현되어있는데,
F : fill the box
P : print box value
E : fill name buffer and return.
이렇게 되어있다. 이제 checksec을 해보자.
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
voidalarm_handler()
{
puts("TIME OUT");
exit(-1);
}
voidinitialize()
{
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
signal(SIGALRM,alarm_handler);
alarm(30);
}
//target
voidget_shell()
{
system("/bin/sh");
}
voidprint_box(unsignedchar*box,intidx)
{
printf("Element of index %d is : %02x\n",idx,box[idx]);
}
voidmenu()
{
puts("[F]ill the box");
puts("[P]rint the box");
puts("[E]xit");
printf(">");
}
intmain(intargc,char*argv[])
{
unsignedcharbox[0x40]={};
charname[0x40]={};
charselect[2]={};
intidx=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);
return0;
default:
break;
}
}
}
Canary가 적용되어있으니 어쨋든 Leak을 해주어야할 것 같다. Canary Leak을 하기 위해서는(다른 방법은 아직 모른다..) 스택의 데이터를 출력해주는 함수가 필요한데, 딱 적당하게 P 기능에서 이를 수행해준다.
P 기능 내의 print_box를 살펴보자.
voidprint_box(unsignedchar*box,intidx)
{printf("Element of index %d is : %02x\n",idx,box[idx]);}
box[idx]는 사실 *(box + idx) 라고 볼 수 있는거니까, box 배열의 base address에서부터 idx만큼 떨어져있는 곳의 데이터를 말하는 것이다. 따라서 이를 이용하면 Canary 값을 읽을 수 있다.
그럼 한번 스택 상황을 보러 가볼까?
일단 처음에, 공간을 0x94만큼 잡아주는 것을 볼 수 있다.
pwndbg를 이용해 까보았더니 cmp eax, 0x50 즉, 'P'에 해당하면, <main + 192>로 jmp 해준다. <main+192>부터 보면, [ebp-0x88]의 주소를 PUSH해서 print_box로 넘겨주는 것을 볼수 있다. 그렇다면, box의 주소는 0x88일테니까,
그림을 그려봐야겠다.
이런 느낌일 것이다.
그렇다면, P기능에서 81개의 아무 데이터나 넣고, 그 다음 3바이트의 canary 값을 받아오면 canary leak을 할 수 있겠다.
exploit 코드
그리고, 일단 get_shell의 주소를 받아와야한다.
exploit 코드
이렇게 했으면, canary 값도 받아왔고, get_shell의 주소도 구했으니 RET 주소에 get_shell의 주소만 넣으면 되겠다.
buf의 주소를 주는 것을 보니, BOF를 일으켜야하는 것으로 보이고, distance between buf and $rbp를 주는 것을 주목해야겠다. 이 문제가 Canary 단원 안에 속해있다는 것을 생각해볼 때, 저 distance에 집중해야겠다.
기본적인 canary의 개념을 짚고 가자면,
이렇게 생각할 수 있겠다. 즉, sfp와 ret의 무결성 보장을 위해 canary의 값이 변경되었는지를 확인하는 것이다. 따라서 canary의 값을 알아내기 위해 해야할 일은 bof를 통해 canary 값을 leak 해야 하는 것인 것 같다.(아무래도 처음부터 더 어려운건 안시키겠지...)
BUF 주소 leak
charbuf[0x50];
init();
printf("Address of the buf: %p\n",buf);
printf("Distance between buf and $rbp: %ld\n",
(char*)__builtin_frame_address(0)-buf);
이 부분을 보면, p.recvuntil("buf: ") 해서 recv(14)로 buf 값을 가져올 수 있고, distance도 구할 수 있을 것이다.
해당 부분의 exploit 코드
Canary Leak
그 다음으로 canary leak을 해봐야 하겠다.
printf("[1] Leak the canary\n");
printf("Input: ");
fflush(stdout);
read(0,buf,0x100);
printf("Your input is '%s'\n",buf);
문제에서 친절하게도 안내를 해준다 ㅋㅅㅋ
그렇다면 해줘야지.
buf에 distance - 8 (8인 이유는 canary가 들어갈 자리 때문.) 까지 다른 데이터로 채워주고, canary의 NULL문자 부분까지 하나만 더 "A"로 채워주어 canary 정보를 leak 해보았다.
canary Leak
이렇게 하면 canary까지 가져왔으니, 남은건 BOF 뿐이다.
BOF
puts("[2] Overwrite the return address");
printf("Input: ");
fflush(stdout);
gets(buf);
위에를 처리하고나니 이젠 귀여운 BOF만 남아있다. 바로 익스 해주자.
BOF 코드
새로운 함수 몇 가지를 공부했는데, ljust라는 굉장한 녀석이 있다. 코드를 보면 뭐하는 친구일지 이해가 될 것이다.
이 문제는 서버에서 작동하고 있는 서비스(basic_exploitation_000)의 바이너리와 소스 코드가 주어집니다. 프로그램의 취약점을 찾고 익스플로잇해 셸을 획득한 후, “flag” 파일을 읽으세요. “flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다. 플래그의 형식은 DH{…} 입니다.
라고 한다.
그런데, 주어진 소스코드를 살펴보면 익스해서 셸을 획득할 수 있는 코드가 존재하지 않는다.
그렇다면... 셸코드를 이용해야할 것 같다. 셸코드는 대략 25~26byte정도 되었던 것으로 기억하니, 그걸 감안하고 문제를 풀어보도록 해야겠다. (아직 bof말고는 아무것도 몰라서 제 설명이 맞는지는 모르겠습니다! 틀렸다면 죄송!!!)
alarm_handler() 는 무시해도 될 것 같다.
initialize()는 그냥 버퍼 초기화하는 함수인 것 같으니까 무시해야겠다.
main()함수만 보도록 하자!
공부한대로 그냥 차근차근 보자.
일단 파일이 어떤녀석인지 보자.
32bit, SYSV 이정도만 알면 될 것 같다.
다음으로, stack에는 매개변수, 반환주소(RET, SFP), 지역변수 순으로 저장된다. 이를 그림으로 그려보면
1. 우선 처음 nc 접속하면 "buf = (buf의 address)" 가 출력된다. buf의 address는 (0xXXXXXXXX) 이런 형태를 띠고있을 것이므로, 해당 주소는 10글자라고 할 수 있다. 따라서, "buf = (" 이후, 열글자를 16진수형 정수로 받아 이를 buf_addr에 저장해준다.
2. 그 다음, shellcode를 buf에 저장해주고, 나머지는 NOP으로 가득 채워준 후, SFP도 그냥 NOP으로 채우고 나서, return address를 buf의 시작주소로 지정해주어 이를 실행해주면 될 것이다!