Reverse Engineering Code with IDA Pro By Dan Kaminsky, Justin Ferguson, Jason Larsen, Luis Miras, Walter Pearce 정리 : vangelis(securityproof@gmail.com) 이글은 Reverse Engineering Code with IDA Pro(2008년출판 ) 라는책을개인적으로공부하면서정리한것입니다. 목적이책소개가아니라공부이므로글의내용은형식에국한되지않고경우에따라책의내용일부를편역하거나또는철저하게요약해서올리고, 경우에따라필자가내용을추가할것입니다. 내용에오류및오타가있다면지적부탁드립니다. 이글은이글이필요한사람들을위해정리된것입니다.
4 장 Walkthroughs One and Two 이장에서는구체적인예를통해실제리버싱과정을분석한다. 분석에사용되는프로그램 StaticPasswordOverflow.exe은 Securityproof 게시판에이글과함께첨부할것이다. 참고로필자가사용하는 IDA 버전은 Hex-Rays 플러그인이설치된 5.2.0.908 버전이다. Following Execution Flow 바이너리를리버싱하는첫번째단계는바이너리가어떤일을하고, 어떻게하는지확인하는것이다. 먼저우리가분석할 staticpasswordoverflow.exe라는파일을실행해보자. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Microsoft Windows XP [Version 5.1.2600] (C) Copyright 1985-2001 Microsoft Corp. C:\Documents and Settings\free2\ 바탕화면 \Reversing>staticpasswordoverflow.exe Reverse Engineering with IDA Pro Example #1 & #2 - Static Password in Executable This example demonstrates a binary file with a hardcoded (static) password required to continue past a certain point in execution. [*] Please Provide the password to continue Password: reversing ******* INVALID PASSWORD ******* You failed. Goodbye. C:\Documents and Settings\free2\ 바탕화면 \Reversing> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 결과를보면패스워드를입력해야다음단계로넘어갈수있는것을볼수있다. 그리고만약패스워드가요구하는것과맞지않을경우 You failed. Goodbye. 란말과함께프로그램이종료된다. 이제 IDA로분석을해보기로한다. 다음은 StaticPasswordOverflow.exe 를디스어셈블링한것이다. graph disassembly view 로디스어셈
블링한것을나타낼경우메모리상의주소를바로확인하는것이힘들다. 그래서 text disassembly view로보는것이편리할수있다. 물론전체흐름을한눈에파악하는것에는 graph disassembly view가더편할수있다. 위의 text disassembly view 로본내용을아래와같이발췌했다. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.text:00401270 ; =============== S U B R O U T I N E =======================================.text:00401270.text:00401270 ; Attributes: bp-based frame.text:00401270.text:00401270 ; int cdecl main(int argc, const char **argv, const char *envp).text:00401270 _main proc near ; CODE XREF: tmaincrtstartup+15ap.text:00401270.text:00401270 Dst = byte ptr -80h.text:00401270 argc = dword ptr 8.text:00401270 argv.text:00401270 envp = dword ptr 0Ch = dword ptr 10h.text:00401270.text:00401270 push ebp.text:00401271 mov ebp, esp.text:00401273 sub esp, 80h.text:00401279 push offset areverseenginee ; "Reverse Engineering with IDA Pro nexampl"...
.text:0040127e call sub_401554.text:00401283 add esp, 4.text:00401286 push offset apleaseprovidet ; "[*] Please Provide the password to cont"....text:0040128b call sub_401554.text:00401290 add esp, 4.text:00401293 push 80h ; Size.text:00401298 push 0 ; Val.text:0040129A lea eax, [ebp+dst].text:0040129d push eax ; Dst.text:0040129E call _memset.text:004012a3 add esp, 0Ch.text:004012A6 lea ecx, [ebp+dst].text:004012a9 push ecx.text:004012aa push offset a127s ; "%127s".text:004012AF call _scanf.text:004012b4 add esp, 8.text:004012B7 lea edx, [ebp+dst].text:004012ba push edx ; Str2.text:004012BB call sub_4011c0.text:004012c0 add esp, 4.text:004012C3 movsx eax, al.text:004012c6 test eax, eax.text:004012c8 jge short loc_4012d9.text:004012ca push offset ayoufailed_good ; "You failed. Goodbye. n".text:004012cf call sub_401554.text:004012d4 add esp, 4.text:004012D7 jmp short loc_4012e6.text:004012d9 ; ---------------------------------------------------------------------------.text:004012d9.text:004012d9 loc_4012d9: ; CODE XREF: _main+58j.text:004012d9 push offset ayouwon_goodbye ; "You won. Goodbye. n".text:004012de call sub_401554.text:004012e3 add esp, 4.text:004012E6.text:004012E6 loc_4012e6: ; CODE XREF: _main+67j.text:004012e6 mov eax, 1.text:004012EB mov esp, ebp.text:004012ed pop ebp.text:004012ee retn
.text:004012ee _main endp.text:004012ee ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 위의디스어셈블링한결과는 main 함수부분이다. 앞에서프로그램을실행한후간단히어떤일을하는지알아보았다. 대략보면 main 함수에서사용되고있는함수들은 sub_401554, memset, scanf, sub_4011c0 이렇게 4 개가보이고, jge 명령어를통해 if문이사용되고있음을알수있다. 호출되는함수들중에서 sub_401554와 sub_4011c0는정확한함수명이사용되고있지않다. 그러나 sub_401554 함수의경우 printf 함수인것으로쉽게추측할수있을것같다. 이는앞에서프로그램을실행한후특정문자열들이출력되는것과 IDA로디스어셈블링한부분에서특정문자열이스택에 push된후 sub_401554 함수가호출되는것을보면알수있다. 그리고조건문이사용되고있는것도쉽게알수있다. 이는 View -> Ghraphs -> Flow chart 과정을통해분명하게볼수있다. Flow chart는 F12를클릭하면바로사용할수있다.
위의 flow chart를보면 false일경우 You failed. Goodbye. 가출력되고, true일경우 You won. Goodbye. 란문자열이출력된다. 핵심적인부분은 sub_4011c0 함수부분이다. 이함수를분석하면 실마리를찾을수있을것같다. 대략적으로확인을했으니이제본격적인분석에들어간다. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 ; Attributes: bp-based frame 2 ; int cdecl main(int argc, const char **argv, const char *envp) 3 _main proc near 4 Dst= byte ptr -80h 5 argc= dword ptr 8 6 argv= dword ptr 0Ch 7 envp= dword ptr 10h 8 push ebp 9 mov ebp, esp 10 sub esp, 80h 11 push offset areverseenginee ; "Reverse Engineering with IDA Pro nexampl"... 12 call sub_401554 13 add esp, 4 14 push offset apleaseprovidet ; "[*] Please Provide the password to cont"... 15 call sub_401554 16 add esp, 4 17 push 80h ; Size 18 push 0 ; Val 19 lea eax, [ebp+dst] 20 push eax ; Dst 21 call _memset 22 add esp, 0Ch 23 lea ecx, [ebp+dst] 24 push ecx 25 push offset a127s ; "%127s" 26 call _scanf 27 add esp, 8 28 lea edx, [ebp+dst] 29 push edx ; Str2
30 call sub_4011c0 31 add esp, 4 32 movsx eax, al 33 test eax, eax 34 jge short loc_4012d9 35 push offset ayoufailed_good ; "You failed. Goodbye. n" 36 call sub_401554 37 add esp, 4 38 jmp short loc_4012e6 39 loc_4012d9: ; "You won. Goodbye. n" 40 push offset ayouwon_goodbye 41 call sub_401554 42 add esp, 4 43 loc_4012e6: 44 mov eax, 1 45 mov esp, ebp 46 pop ebp 47 retn 48 _main endp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; int cdecl main(int argc, const char **argv, const char *envp) 이부분은 C 언어의소스에서는다음과같이표현된다. int main(int argc, char *argv[]) 그리고 ; int cdecl main(int argc, const char **argv, const char *envp) 에서 cdecl 는함수를호출하는방법을나타낸다. 보통 Calling Convention라고부르는데, 여기에는 cdecl, pascal, _stdcall, _fastcall 4가지방법이있다. 그중 cdecl는다음과같이 4가지특징이있다.
Arguments Passed from Right to Left Calling Function Clears the Stack this pointer is passed via stack last in case of Programs using OOP Functions using _cdecl are preceded by an _ 이 4가지함수호출방법들사이의큰차이점은스택정리방법에있는데, cdecl은스택정리를위해서 add 명령어가추가된다. 이것의예는 13번째라인의 add esp, 4 에서볼수있다. 라인 3 부분을살펴보자. 3 _main proc near 여기서는 proc라는표준어셈블러의 directive가사용되고있다. 이 proc이라는디렉티브는프로시저의시작을알리는것이다. 참고로프로시저의끝을알리는디렉티브는 endp이다. near는현재세그먼트안에위치한목적지를의미한다. 이와대조적인것이 far로, 이는다른세그먼트의위치를가리킨다. 그래서라인 3은현재세그먼트내에서 main 함수가시작됨을나타낸다. 4 ~ 7번까지는 ebp로부터각값의위치를나타내고있다. 4 ~7까지에서나오는 dword는 32비트를나타내고, ptr은연산과정에서피연산자의크기에구애받지않고자할때사용하는어셈블러연산자이다. 4 Dst= byte ptr -80h // 스택의크기는 128 바이트 5 argc= dword ptr 8 // ebp로부터 8 바이트떨어진위치 6 argv= dword ptr 0Ch // ebp로부터 12 바이트떨어진위치 7 envp= dword ptr 10h // ebp로부터 16 바이트떨어진위치 이를도식도로나타내면다음과같다. buffer (128 바이트 ) saved ebp (4 바이트 ) return address (4 바이트 ) argc (4 바이트 ) argv (4 바이트 ) envp (4 바이트 ) 라인 8 ~ 10번까지를살펴보자. 8 ~ 10번까지는함수의호출과정에서필수인 procedure prelude 과정으로써, 이를통해현재의 stack pointer를새로운 frame pointer로만든다. 하나의새로운스택프레임이만들어지는데, 이과정에서로컬변수를위한공간이할당된다. procedure prelude에대해서는이글을읽는사람들에게별도설명이필요하지않을것이다.
8 push ebp 9 mov ebp, esp 10 sub esp, 80h // 128 바이트 라인 2 ~ 10 까지 C 언어로나타내면대략다음과같을것이다. int main(int argc, char *argv[]) { int array[128]; 이제다음라인들을살펴본다. 11 push offset areverseenginee ; "Reverse Engineering with IDA Pro nexampl"... 12 call sub_401554 13 add esp, 4 14 push offset apleaseprovidet ; "[*] Please Provide the password to cont"... 15 call sub_401554 16 add esp, 4 11 ~ 16 라인은앞에서프로그램을실행했을때보았던 startup header 출력부분이다. 즉, 다음출력부분이다. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Reverse Engineering with IDA Pro Example #1 & #2 - Static Password in Executable This example demonstrates a binary file with a hardcoded (static) password required to continue past a certain point in execution. [*] Please Provide the password to continue Password: reversing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 11라인에서스택에 areverseenginee 부분에해당하는데이터를 push하고있다. 여기서 offset은스택세그먼트를기준으로한다. areverseenginee 부분에마우스를올리면다음과같이해당문자열들이보인다.
자세한내용확인을위해 Enter 를이용해해당위치로이동해보자. areverseenginee에해당되는데이터는.data 섹션.data:0040E1C8 에위치해있는것을볼수있다..data 섹션에있는 startup header 내용을보았다. 다시원래의 entry point로돌아가기위해 Esc키를누른다. 라인 11에서출력될데이터를 push한후, 라인 12에서는 sub_401554라는함수를호출하고있다. 프로그램을실행시켰을때 startup header 내용이출력된것으로보아 sub_401554라는함수는 printf 함수라는것을쉽게추측할수있다. 확인을위해 sub_401554라는서브루틴안으로들어가본다..text:00401554.text:00401554 ; =============== S U B R O U T I N E ========================.text:00401554.text:00401554 ; Attributes: bp-based frame
.text:00401554.text:00401554 sub_401554 proc near ; CODE XREF: sub_401000+65p.text:00401554 ; sub_401000+c0p....text:00401554.text:00401554 var_1c = dword ptr -1Ch.text:00401554 ms_exc = CPPEH_RECORD ptr -18h.text:00401554 arg_0 = dword ptr 8.text:00401554 arg_4 = byte ptr 0Ch.text:00401554.text:00401554 push 0Ch.text:00401556 push offset unk_40d3a0.text:0040155b call SEH_prolog4 내용을보면보면 sub_401554 함수가 printf 함수라는부분은나오지않는다. 그러나쉽게추측할수있고, 앞으로의편의를위해이함수를 printf 함수로이름을수정한다. 수정하지않아도되지만안으로깊게들어갈수록헷갈릴수있으므로여기서아예함수명을수정한다. 수정방법은다음과같다. 수정할함수명위에커서를올려두고, Edit 부분의 Rename을먼저선택한다. 그런후다음창에서수정하면된다.
---> 다음과같이함수명이 printf 로수정되었다. 라인 13에서는라인 12에서함수가호출되고, 그작업이끝나자스택을클리어하는작업이나와있다. 앞에서도살펴보았지만함수호출방법에서 cdecl은스택정리를위해서 add 명령어가추가된다고했다. add esp, 4 부분은 sub_401554 함수가그역할을하기위해필요한주소공간을위해할당된 4 바이트를원상복귀시킨다. 스택은아래로자라므로 4 바이트를 add 했다. 4 바이트를 add 한것은 sub_401554 함수의아규먼트 1개 areverseenginee의주소값때문이다. 라인 14 ~ 16까지는라인 11~13까지와같은형태의작업이므로별도의설명은하지않는다. 이제라인 11 ~ 16을 C 언어로나타내면다음과같을것이다. printf("reverse Engineering with IDA Pro\n" "Example #1 & #2 - Static Password in Executable\n" "\tthis example demonstrates a binary file with a\n" "\thardcoded (static) password required to\n" "\tcontinue past a certain point in execution.\n\n\n"); printf("[*] Please Provide the password to continue\npassword: ");
이제라인 17 ~ 22 까지살펴보자. 17 push 80h ; Size 18 push 0 ; Val 19 lea eax, [ebp+dst] 20 push eax ; Dst 21 call _memset 22 add esp, 0Ch 이부분을보면 memset 함수가사용되고있음을알수있다. 라인 17 ~ 18에서는 memset이초기화할공간배열 array의크기는 80h, 즉 128 바이트 ( push 80h ) 로잡고, 그공간을 null로채운다 ( push 0 ). 이는다음에서살펴보겠지만초기화된 buffer에 scanf 함수를이용해데이터를읽어들이기위한것이다. 라인 19 ~20에서는 ebp+dst의주소를 eax에로딩하여스택에 push한다. Dst의값은라인 4에서볼수있다. memset 함수를호출하는데필요한값들이모두 push된후라인 21에서 memset 함수가호출된다. 이전체과정을 C 언어로나타내면다음과같다. memset(array, 0x00, 128); memset 함수의작업이끝난후다시스택을클리어하기위해라인 22에서 add esp, 0Ch 작업이이루어진다. 여기서 0CH, 즉 12바이트를 add한것은위의그림에서보듯 memset의아규먼트 3개 array(void *Dst), 0x00(int Val), 128(size_t Size) 의주소값때문이다. 이제 scanf 함수가호출되는라인 23 ~ 27 을살펴보자. 23 lea ecx, [ebp+dst]
24 push ecx 25 push offset a127s ; "%127s" 26 call _scanf 27 add esp, 8 먼저 ecx에 ebp+dst의주소를로딩하는데, 이곳은이미 memset 함수에의해 array[] 의공간이 0 으로초기화되어있는곳이다. 그리고그값이저장된 ecx를스택에 push하고, 그런다음 %127s" 를 push한다..data:0040e2dc ; char a127s[].data:0040e2dc a127s db '%127s',0 ; DATA XREF: _main+3ao scanf 함수에서사용되는 '%127s' 에서알수있듯버퍼에 127 바이트를저장한다. 라인 23~27까지를 C 언어로나타내면다음과같다. scanf("%127s", &array); 라인 27의 add esp, 8 를통해스택을클리어한다. 여기서 8 바이트를클리어하는이유는 scanf의아규먼트 2개 "%127s" 와 &array의주소값이들어가있기때문이다. 라인 1 ~ 27 여기까지는실마리를푸는특별한내용은없다. 핵심적인부분인라인 28부터시작된다. 28 ~ 34에서 sub_4011c0 함수와조건문이사용되고있으며, 앞에서 Flow chart를통해살펴보았듯이라인 34에서분기가된다. 일단 28 ~ 34까지의내용을살펴보자. 28 lea edx, [ebp+dst] 29 push edx ; Str2 30 call sub_4011c0 31 add esp, 4 32 movsx eax, al 33 test eax, eax 34 jge short loc_4012d9 라인 28~29에서는 edx에 ebp+dst의주소값을로딩하고, 그값을 push한다. 그런다음라인 30 에서 sub_4011c0 함수가호출된다. 라인 30 이후를보면 sub_4011c0 함수가가장핵심적인역할을
하고있음을알수있다. 그렇다면이제 sub_4011c0 함수에대해알아볼필요가있다. 하지만 sub_4011c0 함수에대해알아보기전에 main 함수전체에대해먼저간단하게알아보는것이분석 을위해더효율적일것이다. 라인 30에서 sub_4011c0 함수가호출되고, 라인 31에서는스택이다시클리어된다. 그런데여기서클리어되는스택이 4바이트이다. 이는 sub_4011c0 함수가아규먼트를주소값하나만가진다는것을알수있다. 이에대해확인을해보면다음과같다. 라인 32에서사용되고있는 movsx 명령은 move with sign-extedn 를의미하며, 레지스터의상위비트들을모두부호로채운다. 여기서는 eax에 al의값을복사한다. main 함수부분만으로는 al의값을알수가없다. al 값은 sub_4011c0 함수에대해알아볼때자세히다루도록하자. sub_4011c0 함수에서해당부분은다음과같다. 라인 33에서 test 명령이수행되는데, test 명령은첫번째와두번째오퍼랜드의 bitwise AND( 논리곱 ) 를수행하고, 그런다음 EFLAGS 레지스터에 flag를설정한다. 피연산자내의각각의비트가 1인지를알아볼때사용된다. test 명령후 jge 명령이실행된다. jge의의미는다음과같다.
라인 34를충족시키면 loc_4012d9로 jump하는데, loc_4012d9의내용은다음과같으며, 라인 39 ~ 42 부분이다. 이부분은스택에 ayouwon_goodbye 부분의데이터인 You won. Goodbye. 을 push하고, printf 함수를호출한다. 그러나라인 33 ~ 34 를충족시키지못하면라인 35 ~38 로분기한다. 35 push offset ayoufailed_good ; "You failed. Goodbye. n" 36 call sub_401554 37 add esp, 4 38 jmp short loc_4012e6 라인 35는스택에 ayoufailed_good의데이터를 push한다. 그런다음 sub_401554를호출하는데, sub_401554 함수는 printf 함수로이름을수정한것이다. sub_401554 함수가호출되면 "You failed. Goodbye." 가호출되고, 다시라인 37에서스택이클리어된다. 그리고라인 38이실행된다. 라인 38에서 loc_4012e6의내용은다음과같다. 이는라인 43 ~ 48까지의내용과일치한다..text:004012E6 loc_4012e6: ; CODE XREF: _main+67j.text:004012e6 mov eax, 1.text:004012EB mov esp, ebp.text:004012ed pop ebp.text:004012ee.text:004012ee _main retn endp 이부분의내용을확인해보면, 먼저 eax에 1을복사하는데, 이는 return(1); 에서사용되기때문이다. 그런다음라인 45 ~ 46에서 procedure epilog가실행되고, return(1); 한다. 그리고프로시저의끝을알리는디렉티브는 endp를통해 main 함수가종료된다.
loc_4012e6 에해당하는부분을 IDA 로본것이다. 이제라인 28 ~ 42 까지를 C 언어로정리하면다 음과같다. if(check_user(&array) < 0) { printf("you failed. Goodbye.\n"); } else { printf("you won. Goodbye.\n"); } return(1); 이상으로 main() 함수전체를살펴보았다. 마지막으로막강한성능을보여주고있는 Hex-Rays 플러그인을이용한 Pseudocode를알아보자. 책에서는 Hex-Rays에대한언급은없다.
결과를보니우리가여태분석한코드를거의그대로보여주고있다. 다소차이가있지만핵심적인부분은변수, 함수의아규먼트등의이름만다를뿐그대로이다. 이는취약점찾기를위해바이너리분석을할때많은도움이될것으로보인다. 이제 sub_4011c0(char *Str2) 함수에대해분석을해보자. 디스어셈블링한결과는책과다소다를수있 다. To be continued.