SH1R0_HACKER
Stack 0 본문
Stack 0.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
코드를 분석해보면 modified가 0이 아닐 때 우리가 원하는
"you have changed the 'modified' variable\n" 메시지를 띄울 수 있을 것 같다.
먼저 취약해 보이는 gets가 보인다.
gets()는 키보드로부터 문자열을 입력받아 버퍼에 저장하는 함수이다.
사용자가 엔터를 치기 전까지 입력한 값들을 인자로 주어진 메모리주소에 저장한다.
[ BOF? ]
버퍼 오버플로우 공격의 대상이 되는 프로그램은 사용자로부터의 입력을 받는 것이다.
버퍼(Buffer)란? 어떤 데이터가 한 곳에서 다른곳으로 이동할 때,
그 데이터가 일시적으로 보관되는 임시 기억공간을 말한다.
이 제한된 버퍼에 사용자가 크기가 과한 데이터를 입력해버리면 문제가 발생한다.
Buffer OverFlow 공격에 취약한 함수는 다음과 같다.
"strcpy, strcat, gets, fscanf, scanf, sprintf, sscanf, vfscanf, vsprintf, vscanf, vsscanf, streadd, strecpy, strtrns"
이러한 함수들의 공통적인 특징은 처리하는 문자열의 최대 크기를 정하지 않는다는 점이다.
[ Memory ]
메모리 구조는 다음과 같다.
여기서 우리가 실제로 사용하는 유저영역을 보자.
여기서 스택 영역을 보면
스택은 FILO (First In Last Out) 구조를 가진다.
가장 먼저 들어간것이 가장 나중에 나온다.
스택에 데이터가 들어가는 현상을 push, 데이터가 나오는 현상을 pop이라고 이야기한다.
스택의 가장 아랫부분을 bottom이라고 부르며 이 bottom을 기준으로 스택이 쌓인다.
top은 스택의 데이터가 있는 부분 중에서 가장 위를 가리킨다.
데이터를 입출력하면 bottom은 바뀌지 않고 top만 데이터의 양에 따라 움직인다.
운영체제는 top을 esp 레지스터로, bottom을 ebp 레지스터로 항상 가리킨다.
BOF 공격시 필요한 것이 Buffer와 EBP까지 거리를 구한 뒤 RET을 변조하는 방식이다.
[ 취약점 파악 및 분석 ]
먼저 stack0을 실행해보자.
대표사진 삭제
사진 설명을 입력하세요.
실행하니 인자를 입력받을 수 있다.
TEST를 입력해보니 Try agin? 이라는 문구가 뜬다.
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
modified가 0이 아니도록 바꿔줘야 할 것 같다.
gdb를 이용하여 어셈블리어로 보자.
아래는 어셈블리어 해석이다.
0x080483f4 <+0>: push ebp
0x080483f5 <+1>: mov ebp,esp
0x080483f7 <+3>: and esp,0xfffffff0
0x080483fa <+6>: sub esp,0x60
//main 함수가 사용할 공간 할당
0x080483fd <+9>: mov DWORD PTR [esp+0x5c],0x0
//[esp+0x5c] 주소에 0을 지정
//modified 변수의 주소 : [esp+0x5c]
//DWORD : 4byte, WORD : 2byte, BYTE : 1byte
0x08048405 <+17>: lea eax,[esp+0x1c]
//[esp+0x1c]를 eax에 저장
0x08048409 <+21>: mov DWORD PTR [esp],eax
//gets함수에 사용할 파라미터로 eax에 저장된 [esp+0x1c]를 저장
//buffer 변수의 주소 : [esp+0x1c]
0x0804840c <+24>: call 0x804830c <gets@plt>
0x08048411 <+29>: mov eax,DWORD PTR [esp+0x5c]
//modified(esp+0x5c) 변수를 eax 레지스터에 저장
0x08048415 <+33>: test eax,eax
//0이 아닌지 판단
0x08048417 <+35>: je 0x8048427 <main+51>
//Jump Equal 두 값이 같다면 main+51로 이동
//두 값이 다르다면 계속 진행
0x08048419 <+37>: mov DWORD PTR [esp],0x8048500
// "you have changed the 'modified' variable"
0x08048420 <+44>: call 0x804832c <puts@plt>
// puts로 출력되는 걸로 보아 문자열임을 예측가능
0x08048425 <+49>: jmp 0x8048433 <main+63>
0x08048427 <+51>: mov DWORD PTR [esp],0x8048529
// "Try again?"
0x0804842e <+58>: call 0x804832c <puts@plt>
0x08048433 <+63>: leave
0x08048434 <+64>: ret
modified의 위치 : esp+0x5c
buffer의 위치 : esp+0x1c
스택은 위에 있을수록 작은주소이고 아래에 있을수록 높은 주소를 갖는다.
따라서 buffer가 수가 더 적으므로 buffer가 modified보다 위에 위치한다는 사실을 알 수 있다.
gets() 함수로 buffer에 데이터를 넣을 때 크기를 지정하지 않는다는 사실로 보아
buffer의 크기를 다 채우고 modified에 영향을 줄 수 있을 것 같다.
[ Exploit ]
이 프로그램에서 사용자가 영향을 줄 수 있는 부분은 buffer 뿐이다.
gets 함수를 통해 buffer에 데이터를 넣을 수 있다.
char형이 1byte이기 때문에 buffer 변수의 크기는 64byte이다.
buffer을 A 문자열 64개로 다 채워버리고 modified 변수는 0만 아니면 되므로 여기도 A로 덮어씌운다.
buffer의 주소와 modified 주소간의 거리를 정확하게 파악해보자.
modified는 4byte이기 때문에 총 68개의 문자열을 입력하면 buffer 변수와 modified 변수가 모두 A로 수정된다.
[ Pwntools ]