초보자를위한예제와함께 배워보는 OllyDbg 사용법 -1 부 - By Beist Security Study Group (http://beist.org) 요약 : 이문서는 Ollydbg 프로그램을이용하여 Reverse Engineering을하는방법에대해서다룬다. 초보자를위하여작성된문서이며예제와함께 Ollydbg의각기능에대해서알아본다. 주로기초적인내용을다루고있다.
0. Prolog 이강좌는 Debugging과 Reverse Engineering을하기위한강력하고효과적인프로그램인 OllyDbg의사용법을제공하기위해만들어졌습니다. OllyDbg를사용하여임시예제의소스와프로그램을분석하면서 OllyDbg의사용방법을하나씩알아보는방법으로설명하겠습니다. 1. Debug & Debugging Debug란프로그래머가자신이만든프로그램의오류를찾고그것을해결하는과정을말합니다. Debug를하기위해서 3가지방법이있습니다. 1. 프로그래머의눈으로소스코드의내용을일일이따라가면서오류를찾아내는방법이있습니다. 하지만이방법은시간과체력을많이소진하고프로그램이커질수록힘들어지는경우가많습니다. 2. 오류로예상되는부분의앞뒤에 printf와같은함수를사용하면서프로그램의실행구조를파악하는방법입니다. 사람이계산하는것보다쉽게할수있습니다. 3. Debugging을도와주는프로그램을이용하는방법입니다. 디버깅툴로는 (gdb, softice, ollydbg 등이 ) 있습니다. 위 3 번째에서언급한 Debugging 툴을이용하여작업하는것이효율적입니다. 2. OllyDbg 메뉴 -2.1 Tool Bar
[ 그림 1] [A] 부분은 Debug툴바입니다. 프로그램을 restart, close, run 시키거나, 프로세스의실행단계에대해서조작할수있는기능들을제공합니다. [B] 부분은입력된주소값으로이동해서 disassemble코드를보여주는역할을합니다. [C] 부분은디버그하고있는프로그램에대한여러정보를윈도우형식으로표시해줍니다. thread, window, call stack, break point등의정보를볼수있습니다. -2.2 작업영역 Interface
[ 그림 2] 각각 5개의부분으로나눠져있는것을볼수있습니다. 먼저 [A] 부분은 disassembling된코드와 OP코드를나타내고있는 CODE부분입니다. 여기서프로그램의흐름을체크하고분석합니다. [B] 부분은 CPU의레지스터상태를나타냅니다. 이부분을이용해각종레지스터의값이나 Flag를직접조작할수도있습니다. 예를들어 Zero flag의값을변경해다른분기로도점프해보면서프로그램을분석해나갈수도있습니다. [C] 부분은상태바입니다. [A] 부분에서실행되고있는각해당위치의 offset값과변경된메모리주소, 레지스터의내용등을나타내줍니다. [D] 부분은메모리의각값들을 HEX 코드와 ASCII 코드로보여주는부분으로, 메뉴설정을통해다양한데이터형태로값들을확인할수있습니다. [E] 부분은 Stack의내용을보여주고있습니다. 이부분을통해원하는주소의 Stack 영역의값들을확인할수있습니다. [A], [D], [E] 각창들은인터페이스가조금씩차이가있지만기본적으로프로세스의모든메모리영역에접근할수있다는공통점이있습니다.
3. 리버싱을통해알아보는 search & break point -3.1 Test Code 아래소스코드는사용자로부터패스워드를체크하는간단한프로그램입니다. 만약패스워드를맞추면 BINGO라는메시지를볼수있습니다. #include <windows.h> #include <stdlib.h> #include <time.h> #define ID_BUTTON1 104 #define ID_EDIT1 105 #define MAX_STRING 256 LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); HINSTANCE g_hinst; HWND hwndmain; LPCTSTR lpszclass=text("ollytest"); HWND hwndedit; HWND hwndbutton1; char tmp_buf[max_string]; char str_guess[10]= "1ab23"; int APIENTRY WinMain(HINSTANCE hinstance,hinstance hprevinstance,lpstr lpszcmdparam,int ncmdshow){ HWND hwnd; MSG Message; WNDCLASS WndClass; g_hinst=hinstance; WndClass.cbClsExtra=0; WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); WndClass.hCursor=LoadCursor(NULL,IDC_ARROW); WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION); WndClass.hInstance=hInstance; WndClass.lpfnWndProc=(WNDPROC)WndProc; WndClass.lpszClassName=lpszClass; WndClass.lpszMenuName=NULL; WndClass.style=CS_HREDRAW CS_VREDRAW; RegisterClass(&WndClass); hwnd=createwindow(lpszclass,lpszclass,ws_overlappedwindow, 0,0,180,150, NULL,NULL,hInstance,NULL); ShowWindow(hWnd,nCmdShow); } while(getmessage(&message,0,0,0)) { TranslateMessage(&Message); DispatchMessage(&Message); } return Message.wParam; LRESULT CALLBACK WndProc(HWND hwnd,uint imessage,wparam wparam,lparam lparam){ switch(imessage){ case WM_CREATE: CreateWindow(TEXT("static"),"Type password",ws_child WS_VISIBLE, 10,10,150,30,hWnd,(HMENU)0, g_hinst, NULL); hwndedit=createwindow(text("edit"),null,ws_child WS_VISIBLE WS_BORDER ES_LEFT ES_MULTILINE ES_AUTOVSCROLL, 10,40,150,30,hWnd,(HMENU)ID_EDIT1,g_hInst,NULL); hwndbutton1=createwindow(text("button"),"try",ws_child WS_VISIBLE BS_PU SHBUTTON,
10,70,150,30,hWnd,(HMENU)ID_BUTTON1,g_hInst,NULL); return 0; case WM_COMMAND: switch(loword(wparam)){ case ID_BUTTON1: GetDlgItemText(hWnd,ID_EDIT1,tmp_buf,MAX_STRING); if(!strcmp(str_guess,tmp_buf)){ MessageBox(NULL,"BINGO","successful try",mb_ok); }else{ MessageBox(NULL,"Try again","mission failed",mb_ok); } break; } return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return(defwindowproc(hwnd,imessage,wparam,lparam)); -3.2 리버싱시나리오 1 위프로그램을실행해잘못된패스워드를입력하면 Try again 메시지를, 올바른패스워드를입력하면 BINGO 메시지를볼수있습니다. 2 우리는프로그램을직접실행해봄으로써문자열을띄우는함수가 MessageBox 라는함수인것을추측할수있습니다. 또한 Try again 이라는문자열은 MessageBox 함수의인자로들어가리라는것도추측할수있습니다.
3 OllyDbg를실행시키고위프로그램을로딩한후 Try again 이라는문자열을사용하는코드가있는지찾아봅시다. 4 Try again 문자열을사용하는 MessageBox 함수를호출하는코드를찾았다면, 호출하는코드가불리기전에사용자가입력한문자열과암호를비교하는코드를찾아야합니다. 5 두문자열을비교하는곳에서암호문자열과암호문자열의위치를알수있을것입니다. -3.2 Search & break point 이제위시나리오에따라하나씩진행해나가겠습니다. OllyDbg를실행시키고, 우리가위소스코드를컴파일한파일을선택해서열어줍니다. [ 그림 3] 먼저 Disassembling한코드에서마우스오른쪽버튼을클릭하여 POP-UP메뉴를보면여러가지기능이존재합니다. 그중에 Search for 메뉴를이용하여여러가지데이터형태로우리가원하는정보를찾을수있습니다. 명령어, 바이너리문자열, 모듈에서의함수이름등으로찾을수있는기능이제공됩니다. 우리가입력한암호가다르면발생하는문자열인 Try again 이라는문자열을참조한위치를알기위해서 All referenced text string라는기능
을사용하겠습니다. [ 그림 4] [ 그림 4] 에서보듯이해당프로그램에서참조된문자열들이출력되어있습니다. 우리는여기서 Try again 문자열이사용된부분을발견할수있습니다. 원하는문자열을선택하면해당코드부분으로이동할수있습니다.
[ 그림 5] [ 그림 5] 와같이 Try again 문자열이사용된코드부분으로이동된것을볼수잇습니다. 위를보면 BINGO 라는메시지를사용하는루틴도볼수있습니다. 더위를보면 JNZ SHORT start.00401343를볼수있습니다. 이것은조건부분기코드인데, 어떠한경우라면 00401343으로분기합니다. 그위를보면어떤데이터두개를 push하고특정함수를 call 을하고있습니다. 이상황은, 어떤데이터두개중하나는사용자가입력한값이고나머지하나는패스워드임을추측할수있습니다. 그래서만약사용자가입력한값이패스워드와다르다면 Try again 을출력하는코드로분기하고, 같다면 BINGO 메시지를출력하는코드를실행할것입니다.
[ 그림 6] 지금까지의추측이맞는지실제로확인하기위해서 Push start.00425750과, JNZ SHORT start.00401343 이두부분에 break point(f2) 를걸었습니다. (break point는프로그램이실행될때우리가원하는위치, 즉원하는상태에서의메모리나레지스터에어떤값이들어가는지알기위해서프로그램을 break 해주는기능입니다.) 테스트를위해사용자는 test1234라는패스워드를입력했다고가정하겠습니다. 첫번째 break point에서는입력한값 (test1234) 이 push되는지확인하였고두번째 break point에서는입력한값과패스워드를비교한후어디로점프하는지확인하였습니다. [ 그림 6] 에서와같이사용자가입력한값 (test1234) 가 push되고있고, 입력받은부분과패스워드를비교하는루틴이라는추측이맞았습니다. 그리고두번째 break point(jnz SHORT start.00401343) 부분은, 이미 start.00401343으로점프할것이라명시하고있지만어디로점프하는지직접가보기위해서따라가보면 (Enter key) Wrong message를출력하는루틴으
로이동하는것을확인할수있습니다. 결국이루틴은사용자가입력한값과패스워드를비교하는것이었습니다. 4. Plugin 소개 OllyDbg의장점이자특징중하나인 plugin에대해서설명하겠습니다. plugin은사용자가만들어서배포할수도있으며, plugin을구할수있는대표적인사이트는다음과같습니다. http://www.openrce.org/downloads/browse/ollydbg_plugins http://www.pediy.com/tools/debuggers/ollydbg/plugin.htm 대부분 plugin은 dll 파일이나소스코드형식으로제공됩니다. dll 파일을 Ollydbg 폴더에위치시키고실행시키면 plugin 메뉴에해당 plugin이추가됩니다. Plugin은사용자가직접제작할수도있는데이에대해서는 ollydbg 홈페이지 (http://www.ollydbg.de) 를참고하시기바랍니다. -4.1 MapConv 컴파일을할때여러가지옵션을사용할수있습니다. 그중에서도 /MAP(vc++ 의기능입니다.) 이란 Linker 옵션은컴파일을하면서동시에프로그램의 map 파일을생성해줍니다. Map 파일은모듈이름, 심벌의이름그리고정의된.obj 파일등이표시됩니다. 이런정보들을이용하여우리는 OllyDbg에적용할수있고, 더강력한디버깅정보를제공받게됩니다. MapConv라는 Ollydbg plugin을이용해 map 파일을로드할수있습니다. Plugins -> MapConv -> Replace label을선택한뒤에해당 *.map 파일을선택합니다. Win32 API로작성된부분은 OllyDbg에서 label을출력해줍니다. 하지만 runtime libaray인경우는제대로 label을달아주지못합니다. 아래그림에위치한부분이 CALL start.004014b0에서 CALL start. strcmp로바뀐이유가 runtime library라서 label이제대로출력이되지않았기때문입니다. 실제 Reverse Engineering 환경에서는 map 파일을얻을수없는경우가많기때문에실용적이지는않지만참고할만한 plugin입니다.
[ 그림 7]
[ 그림 8] -4.2 OllyScript OllyDbg에서특정스크립트를실행시킬수있는 plugin입니다. 스크립트에쓰이는언어는어셈블리어와비슷한형식을가집니다. 주로패킹된프로그램의 OEP를찾거나, packer로인한 code obfuscation( 코드난독화, junk code, scrambled code라고도불립니다.) 을제거하는 script가있습니다. 작업을자동화할수있도록도움을주는 plugin이라보시면됩니다. 아래의사이트에서보다많은정보를얻을수있습니다. http://www.openrce.org/downloads/browse/ollydbg_ollyscripts http://www.pediy.com/tools/debuggers/ollydbg/script.htm -4.3 ApiFinder
ApiFinder는해당프로그램에서사용하는 dll들의 API들을 API 이름으로검색한뒤, 해당 API 루틴이위치한 address로이동해 break point를쉽게설치할수있는기능을제공합니다. [ 그림 9] -4.4 Ultra String Reference ASCII와 UNICODE 검색을지원합니다.
[ 그림 10] 5. 마치는말 Ollydbg는비교적간단한 interface임에도강력한기능을제공합니다. 이강좌는초보자를위해연재형식으로진행될것이며향후연재에서는 plugin을직접제작하는방법등보다다양한 ollydbg 팁들에대해다룰것입니다. 프로그램의단축키나보다자세한정보는 ollydbg 홈페이지에서확인하시기바랍니다.