Table of Content Module 1 RCE 란?...1 1-1. RCE 란?...2 1-2. RCE 관련법규...4 1-3. RCE 응용분야...6 Module 2 RCE 기초...9 2-1. CPU 동작방식...10 2-2. CPU 레지스터...12 2-3. Assembly...17 2-4. STACK 구조...21 2-5. 함수호출규약 (Calling Convention)...23 2-6. SEH(Structured Exception Handling)...26 2-7. C 코드컴파일후어셈코드변환모습...34 2-8. 특정루틴에서사용되는 API 함수...40 Module 3 PE 파일구조...43 3-1. PE 파일포맷...44 3-2. DOS 헤더및 DOS Stub Code...49 3-3. PE File Header...51 3-4. Optional Header...54 3-5. Section Table...59 3-6. Import Table...62 3-7. Export Table...69 Module 4 분석기초...75 4-1. 분석도구설정...76 4-2. CrackMe 분석실습...84 4-3. KeygenMe 분석실습...94 Module 5 MUP(Manual UnPack)...101 5-1. Packing / Unpacking...102 5-2. Packing 종류...103 5-3. MUP 실습...105 5-4. MUP 를위한 Ollydbg script 작성...114 Module 6 Anti-Reverse 기법...121 6-1. 디버거탐지기법...122 6-2. BreakPoint 탐지기법...133 6-3. TLS Callback...145 6-4. Process Attach 기법...150 Module 7 분석실전...155 7-1. 온라인해킹대회문제분석...156 7-2. 악성코드분석 - shadowbot...173 - i -
- ii -
Module 1 RCE 란? Objectives 리버스엔지니어링의기본개념 리버스엔지니어링의법적문제 리버스엔지니어링응용분야 - 1 -
1-1. RCE 란? RCE 란? Reverse Code Engineering 어떠한장치나객체혹은시스템들의구조나기능, 그리고어떻게작동되는지등의기술적인원리를분석함으로써그것들이작동되는과정을알아내는것 작동하는모든순간순간의세세한것들까지분석하는것을포함 기계적인장치 전자적인부품 소프트웨어적인프로그램 Student Notes 리버스엔지니어링은여러분야에서사용되는용어로써인공물로부터설계사상이나지식을추출하는 행위를말한다. 본교재에서는소프트웨어에대한리버스엔지니어링을이야기할것이다. 통상적으로컴파일된바이너리 (EXE, DLL, SYS 등 ) 를디스어셈블러라는도구를이용하여어셈블리코 드를출력한후그것을 C 언어소스형태로다시옮겨적고적당한수정을통해리버스하고있는파일과 동일한동작을하는프로그램을만드는것이있다. 모든어셈블리코드를소스형태로옮기지않고그냥동작방식만을알아낸다거나일정부분만수정하 는것들도리버스엔지니어링이라고할수있는데예를들면바이러스를분석하는일은모든코드를알 아낼필요가없기때문에동작방식만알아내면된다. 그리고크랙처럼일정부분만수정하여사용제한 - 2 -
을푸는것등도이에해당된다. 실행파일을디스어셈블하지않고도그실행파일이만들어내는데이터파일이나패킷등을분석하여똑같이재구현하는것도리버스엔지니어링이다. 예를들면오래전 PC 게임에서많이하던일인데, HEX 에디터등으로세이브파일을분석하여에디트를만들거나게임자체를조작하는것이있고, 당나귀와호환되는이뮬같은프로그램은당나귀프로토콜의패킷을분석하여동일한동작을하도록만들어낸것이다. 리버스엔지니어링에서가장많이사용되는방식은첫번째로이야기했던바이너리를디스어셈블하여 코드를얻어내는것이다. 이것을하기위해서는먼저인텔어셈블리를배워야하고, 물론 C 언어도알아 야된다. 그런데여기서컴파일된바이너리가 VC 나 gcc 등으로컴파일한것이대부분이지만비주얼베이직으로컴파일한것도있고델파이 ( 파스칼 ) 로컴파일한것도있을것이다. 이바이너리들은모두 CPU 에서직접실행되는것들이기때문에디스어셈블해보면모두똑같은방식으로되어있다. 그래서 VC, VB, 델파이 ( 파스칼 ) 등으로컴파일된것도디스어셈블한뒤 C 소스코드로옮길수있다. 물론리버스하는사람이 VB 나파스칼로옮겨적을수도있을것이다. 대부분리버스해서얻어내는것들은프로그램의로직 ( 알고리즘 ) 이기때문에어느언어로표현하든결과는똑같기때문이다. - 3 -
1-2. RCE 관련법규 RCE 관련법규 상호운용성 경쟁 (Competition) 저작권법 (Copyrightlaw) The Digital Millennium Copyright Act (DMCA) RIAA의 Felten 교수연구팀에대한위협 드미트리스킬리아로프의 ebook 사건 SunnComm사의 CD 복제방지시스템 Student Notes 컴퓨터프로그램보호법제 12 조의 2 ( 프로그램코드역분석 ) 1정당한권원에의하여프로그램을사용하는자또는그의허락을받은자가호환에필요한정보를쉽게얻을수없고그획득이불가피한경우당해프로그램의호환에필요한부분에한하여프로그램저작권자의허락을받지아니하고프로그램코드역분석을할수있다. 2제 1 항의규정에의한프로그램코드역분석을통하여얻은정보는다음각호의 1 에해당하는경우에는이를사용할수없다. 1. 호환목적외의다른목적을위하여이용하거나제 3 자에게제공하는경우 2. 프로그램코드역분석의대상이되는프로그램과표현이실질적으로유사한프로그램을개발 제작 판매하거나기타의프로그램저작권을침해하는행위에이용하는경우 [ 본조신설 2001.1.16][[ 시행일 2001.7.17]] - 4 -
위컴퓨터프로그램보호법은 2001 년 1 월 16 일개정되어 7 월 17 일부터시행되는데, 이번개정에서가장중요하게다루어진것이컴퓨터프로그램역분석의허용한계를규정한것이다. 컴퓨터프로그램의역분석은이미 2000 년개정된컴퓨터프로그램보호법에서도규정하고있었는데, 2001 년개정법에서는저작권제한의일종으로리버스엔지니어링을추가하고, 프로그램코드의역분석을규정하고있다. 상호운용성을위한리버스엔지니어링은특정범위안에서는대부분합법적이긴하다. 하지만경쟁사의 코드를사용하는것은분명한저작권위반이될수있다. DMCA(The Digital Millennium Copyright Act) 은인터넷에서의저작권침해행위를단속하기위한미국법이다. DMCA 의보호를받는대상에는 HTML 소스코드, 본문내용, 사진, 이미지, 해킹된코드, 불법복제소프트웨어, 음악, 영화, 웹페이지및이메일등이있다. DMCA 에의해서제재를받은대표적인사례들몇가지를예로들어보겠다. DMCA 가 Sony-BMG 의 " 루트킷 " 취약성의공개를늦추다 Sony-BMG 의 CD 복제방지기술인 " 루트킷 (root kit)" 이이용자의컴퓨터에보안문제를야기하는것을발견한프린스턴의대학원생알렉스해더맨은저작권침해여부를판단하기위해서수주간발표를연기할수밖에없었고때문에많은이용자가위험에노출된상태로오랫동안있었음 펠튼교수연구팀위협을받다 Secure Digital Music Initiative(SDMI) 라는다산업집단이디지털음악을보호하는특정한워터마킹기술을깨는경연대회를공개적으로열었고, 프린스턴대학의에드워드펠튼과프린스턴과라이스대학그리고제록스사의연구자들이깼다. 이결과를학회에서발표하려는것을 SDMI 가저작권법을근거로책임을묻겠다는협박으로막았다. 연구자들이소송을제기하고나서야위협을철회했다. 썬콤사대학원생위협하다 SunnComm 사가개발한 CD 복제방지시스템을단지컴퓨터의쉬프트키만누르고있으면무력화된다는사실을발견한프린스턴대학원생알렉스핼더만이발표한보고서에대해서 SunnComm 사가소송을제기하겠다는위협을함. 후에 SunnComm 사는위협을포기함. 드미트리스킬리아로프체포되다 러시아프로그래머인드미트리스킬리아로프는어도비사의이북포맷의전자책을 pdf 파일로바꾸는소프트웨어를개발했다는이유로수주간감옥살이를하고다섯달동안미국에서유치장생활을했다. 전자책을 pdf 로바꾸는과정에서이북포맷에포함된이용제한조치가깨졌다는이유에서다. 스킬라로프는직접적으로어떤저작권침해를하지도않았고저작권침해를돕지도않았지만미국라스베가스에서열린학회에서그소프트웨어를발표한것으로이런생활을한것이다. 결국은검찰의기소는각하되었다. < 출처 : http://www.eff.org/ip/dmca/unintended_consequences.php> - 5 -
1-3. RCE 응용분야 RCE 응용분야 악성코드분석 DRM 등보호알고리즘분석 개발효율성증가 안전성테스트 응용프로그램패치 RCE 의핵심은 사용자가원하는대로 프로그램이동작하게만드는것 Student Notes 리버스엔지니어링은흔히불법이라는생각에응용분야또한불법인것이아닌가하는생각을할수있다. 하지만실제는정반대의상황이발생할수있다. 현재인터넷을사용하는모든사용자가가장신경을많이쓰는부분은바이러스 / 웜이나악성코드일거라생각한다. 몰론스팸도만만치않겠지만바이러스 / 웜이나악성코드 ( 이하 악성코드 라함 ) 의경우직접적으로내가사용하고있는컴퓨터에영향을미치기때문에더많은신경을쓰고백신프로그램도설치하고할것이다. 이러한악성코드는대부분확장자가 exe 인실행파일의형태로배포가되고감염자 PC 에서실행되면서악성코드제작자의의도대로악의적인행동을하게된다. 그렇다면이러한악성코드가내 PC 에서실행이되면어떤행동들을하는지정확하게파악할수있어야지만악성코드를찾아내서내 PC 를안전하게할수있다. 이러한일련의행동들을우리는리버스엔지니어링기법을통해서파악할수있다. 물론 - 6 -
악의적인목적으로운영체제나소프트웨어들의취약점을찾아서공격하기위해리버스엔지니어링기법을사용할수도있겠지만좋은의미에서리버스엔지니어링을사용할수도있다는것이다. 대표적인예로시그내처기반으로동작하는백신의경우새로운악성코드발견시전문가들에의해서악성코드의일련의행동들을알아낸후백신데이터베이스를업데이트한다. 이때도리버스엔지니어링기법을사용하게된다. 혹은최근배포되고있는악성코드들이암호알고리즘을사용하여악성코드자체를보호하는경우가있 는데이러한경우에도리버스엔지니어링기법을사용한다. DRM 의경우소프트웨어복제방지기술과유사한데차이점이라면소프트웨어복제방지기술은소프트웨어자체를보호하는것이고 DRM 은컨텐츠를보호하는것이라는점이다. 예를들어온라인에서음악을들을수있는사이트가있고이사이트는클라이언트들이음악을듣기위한프로그램설치를요구할수있다. 한악의적인사용자가이클라이언트프로그램을리버스엔지니어링을통해서음원을다운받을수있게할수있다는것이다. 상용소프트웨어와상호호환이되는소프트웨어를개발하는경우소프트웨어라이브러리나 API 에대한매뉴얼의내용이부족한경우가많은데이러한문제점을해소하기위해서리버스엔지니어링기법을사용하거나소프트웨어개발시아무것도없는상태에서개발하는것은힘들기때문에잘만들어진소프트웨어에대해서리버스엔지니어링기법을사용하여재사용을하기도한다. 물론공개소프트웨어의경우는소스코드를활용할수있겠지만공개되지않은소프트웨어의경우는리버스엔지니어링과정을거쳐야할것이다. 마지막으로소프트웨어의안전성과취약점을평가하기위해리버스엔지니어링기법을사용할수있다. 개발된소프트웨어에대해리버스엔지니어링기법을적용하여소프트웨어에결함이있지는않은지품 질평가를할수있다. 그리고응용프로그램에대한패치도리버스엔지니어링의응용분야이다. 취약점이알려진프로그램이 있는데소스코드가없어서재컴파일할수없을경우바이너리를패치해야하고이때리버스엔지니어 링을하게된다. 물론이런점을악용하면크랙을하는것이될수도있다. 하지만리버스엔지니어링에서가장중요한건개발자의의도가아닌사용자의의도대로프로그램이동 작하게하려고한다는것이다. - 7 -
- 8 -
Module 2 RCE 기초 Objectives CPU 동작방식 CPU 레지스터 어셈블리 / 디스어셈블리 STACK 구조 함수호출규약 (Calling Convention) SEH(Structured Exception Handling) C 코드컴파일후어셈코드변환모습 특정루틴에서사용되는 API 함수 - 9 -
2-1. CPU 동작방식 CPU 동작방식 CPU (Central Processing Unit) ALU (Arithmetic Logic Unit) Register Set Control Unit BUS Interface Memory I/O BUS Keyboard Monitor NIC HDD Student Notes CPU 는소프트웨어명령의실행이이루어지는컴퓨터의부분, 혹은그기능을내장한칩을말한다. CPU 는기계어로쓰여진컴퓨터프로그램의명령어를해석하여실행한다. CPU 는프로그램에따라외부에서정보를입력하고, 기억하고, 연산하고, 외부로출력한다. CPU 는컴퓨터부품과정보를교환하면서컴퓨터전체의동작을제어한다. 기본구성으로는레지스터, 산술논리연산장치 (ALU: arithmetic logic unit), 제어부 (control unit) 와내부버스등이있다. 각종전자부품과반도체칩을하나의작은칩에내장한전자부품을마이크로프로세서라고한다. 마이크로프로세서는전기밥통에쓰이는낮은성능의제품부터컴퓨터에쓰이는높은성능의제품까지매우다양하다. 마이크로프로세서들가운데가장복잡하고성능이높은제품은컴퓨터의연산장치로쓰인다. 이것을중앙처리장치라고한다. - 위키백과사전 - - 10 -
프로그램이실행되면실행프로그램이메모리에적재되고메모리의내용을 CPU 에서가져와레지스터 에저장한후 Control Unit 을통해 ALU 에전달된후 ALU 에서산술연산을하게된다. ALU( 산술연산장치 ) : 두숫자의 ( 덧셈, 뺄셈같은 ) 산술연산과 ( 배타적논리합, 논리곱, 논리합같은 ) 논리연산을계산하는디지털회로이다. 산술논리장치는컴퓨터중앙처리장치의기본설계블럭이다. Register : 컴퓨터의프로세서내에서자료를보관하는아주빠른기억장소이다. 일반적으로현재계산을수행중인값을저장하는데사용된다. 대부분의현대프로세서는메인메모리에서레지스터로데이터를옮겨와데이터를처리한후그내용을다시레지스터에서메인메모리로저장하는로드-스토어설계를사용하고있다. - 11 -
2-2. CPU 레지스터 CPU Register Student Notes CPU 에서사용되는레지스터는크게범용레지스터, 세그먼트레지스터, EFLAGS 레지스터, CIP 레지스 터네가지로구분된다. 기본적으로 32 비트 (4 바이트 ) 의크기를갖고있으며 16 비트나 8 비트형태로나 누어서사용되기도한다. - 12 -
레지스터에저장되는데이터들은부호가없는 00000000 부터 FFFFFFFF 까지이다. Reverse Engineering 범용레지스터 (General-Purpose Register) 범용레지스터는논리, 산술연산에사용되는오퍼랜드나주소계산을위한오펀랜드, 메모리포인터에 사용된다. EAX(Extended Accumulator Register) : 산술연산에사용 EBX(Extended Base Register) : DS 세그먼트에데이터를가리키는역할 ( 주소지정을확대하기위한인덱스로사용 ) ECX(Extended Counter Register) : 루프의반복횟수나좌우방향시프트비트수기억 EDX(Extended Data Register) : 입출력동작에서사용 ESI(Extended Source Index) : 출발지인덱스에대한값저장 EDI(Extended Destination Index) : 다음목적지주소에대한값저장 ESP(Extended Stack Pointer) : 스택포인터, 스택의 TOP 을가리킴 EBP(Extended Base Pointer) : 스택내의변수값을읽는데사용, 스택프레임의시작점을가리킴 세그먼트레지스터 (Segment Register) 세그먼트레지스터는 16 비트세그먼트선택자를가지고있다. 세그먼트선택자는메모리에서세그먼트 를확인하는특별한포인터다. - 13 -
CS : 코드세그먼트의시작주소를저장 - 프로그램에서명시적으로로드될수는없으나프로그램제어를변경하는명령또는내부프로세서조작에의해묵시적으로로드될수있다. SS : 스택으로사용할메모리영역의시작주소를저장 - 명시적으로로드될수있기때문에프로그램에서이레지스터를통해여러개의스택을셋업하여그들간을상화전환할수있다. DS : 데이터세그먼트의시작주소를저장 ES : 여분의데이터세그먼트용, 문자열처리명령시목적지데이터저장 FS/GS : 여분의세그먼트용, IA32 프로세서에서만사용가능 세그먼트레지스터가어떻게사용되는지는사용하는메모리관리모델에따라달라질수있다. 메모리 관리모델은크게 Flat 메모리모델과 Segmented 메모리모델이있다. 그중윈도우환경에서사용되며일 반적으로많이사용되는메모리모델은 Segmented 메모리모델이다. EFLAGS 레지스터 EFLAGS 레지스터는상태플래그, 제어플래그, 시스템플래그그룹으로구성되어있으며 1, 3, 5, 15, 22 ~ 31 비트는예약되어있으므로사용할수없다. - 14 -
LAHF, SAHF, PUSHF, PUSHFD, POPF, POPPFD 등의명령으로프로시저스택혹은 EAX 레지스터로플래그의그룹을이동할수있다. EFLAGS 레지스터의내용은비트조작명령 (BT, BTS, BTR, BTC) 을사용해플래그를검사및변경할수있다. 상태플래그 (Status Flags) : 산술연산 (ADD, SUB, MUL, DIV) 을하는명령의결과를가리킨다. CF(bit 0) Carry flag : 부호없는정수끼리의산술연산후오버플로우가발생했는지확인 - 0 : 오버플로우가발생하지않은경우 - 1 : 오버플로우가발생한경우 PF(bit 2) Parity flag AF(bit 4) Adjust flag ZF(bit 6) Zero flag : 산술연산또는비교동작의결과를나타냄 - 0 : 결과가 0 이아닌경우 - 1 : 결과가 0 인경우 SF(bit 7) Sign flag OF(bit 11) Overflow flag : 부호있는수끼리의산술연산후오버플로우가발생했는지확인 - 0 : 오버플로우가발생하지않은경우 - 1 : 오버플로우가발생한경우 ( 너무큰정수나너무작은음수인경우 ) 제어플래그 (Control Flags) DF(bit 10) Direction flag 시스템플래그 (System Flags) TF(bit 8) Trap flag IF(bit 9) Interrupt enable flag IOPL(bit 12, 13) I/O privilege level field NT(bit 14) Nested task flag RF(bit 16) Resume flag VM(bit 17) Virtual-8086 mode flag AC(bit 18) Alignment check flag VIF(bit 19) Virtual interrupt flag VIP(bit 20) Virtual interrupt pending flag ID(bit 21) Identification flag 리버스엔지니어링에서필요한플래그는 ZF, OF, CF 세가지플래그이다. - 15 -
EIP 레지스터현재프로세서가실행하고있는명령바로다음에실행할명령어의오프셋을저장한다. 수행한명령어길이만큼증가된 EIP 는메모리내의다음실행할명령어를가리킨다. 실제모드로동작할때는 16 비트 EIP( 상위 16 비트는 0) 로, 보호모드로동작할때는 32 비트 EIP 로동작한다. EIP 는소프트웨어의의해조작할수없고 CALL, JMP, RET 와같은 control-transfer 명령에의해서만영 향을받는다. - 16 -
2-3. Assembly Assembly Arithmetic Instruction Data Transfer Instruction Logical Instruction String Instruction Control Transfer Instruction Processor Control Instruction Student Notes 어셈블리어는리버스엔지니어링을하는데중요한기초지식중하나이다. 어셈블리어명령어들이어떤 역할을하는지에대해서이해하고한줄한줄의의미보다는여러라인이어떠한하나의의미를가지는 지를파악하는것이중요하다. 이번장에서는여러가지어셈블리명령어에대해서알아보도록하겠다. - 17 -
Arithmetic Instruction 명령 ADD SUB ADC SBB CMP INC DEC NEG AAA DAA AAS DAS MUL IMUL AAM DIV IDIV AAD CBW CWD 설명캐리를포함하지않은덧셈캐리를포함하지않은뺄셈캐리를포함한덧셈캐리를포함한뺄셈두개의오퍼랜드비교오퍼랜드내용을 1 증가오퍼랜드내용을 1 감소오퍼랜드의 2 의보수, 즉부호반전덧셈결과 AL 값을 UNPACK 10 진수로보정덧셈결과의 AL 값을 PACK 10 진수로보정뺄셈결과 AL 값을 UNPCAK 10 진수로보정뺄셈결과의 AL 값을 PCAK 10 진수로보정 AX 와오퍼랜드를곱셈하여결과를 AX 또는 DX:AX 에저장부호화된곱셈곱셈결과 AX 값을 UNPACK 10 진수로보정 AX 또는 DX:AX 내용을오퍼랜드로나눔. 몫은 AL, AX 나머지는 AH, DX 로저장부호화된나눗셈나눗셈결과 AX 값을 UNPACK 10 진수로보정 AL 의바이트데이터를부호비트를포함하여 AX 워드로확장 AX 의워드데이터를부호를포함하여 DX:AX 의더블워드로변환 Data Transfer Instruction 명령 설명 MOV 데이터이동 ( 전송 ) PUSH 오퍼랜드내용을스택에쌓음 POP 스택으로부터값을가져옴 XCHG 첫번째오퍼랜드와두번째오퍼랜드교환 XLAT BX:AL 이지시한테이블의내용을 AL 로로드 LEA 메모리오프셋값을레지스터로로드 LDS REG (MEM), DS (MEM+2) LES REG (MEM), ES (MEM+2) LAHF 플래그의내용을 AH 의특정비트로로드 SAHF AH 의특정비트가플래그레지스터로전송 PUSHF 플래그레지스터의내용을스택에쌓음 POPF 스택으로부터플래그레지스터로가져옴 - 18 -
Logical Instruction 명령 설명 NOT 오퍼랜드의 1 의보수, 즉비트반전 SHL/SAL 왼쪽으로오퍼랜드만큼자리이동 ( 최하위비트는 0) SHR 오른쪽으로오퍼랜드만큼자리이동 ( 최상위비트는 0) SAR ROL/ROR RCL/RCR AND TEST OR XOR 오른쪽자리이동, 최상위비트는유지왼쪽 / 오른쪽으로오퍼랜드만큼회전이동캐리를포함하여왼쪽 / 오른쪽으로오퍼랜드만큼회전이동논리 AND 첫번째오퍼랜드와두번째오퍼랜드를 AND 하여그결과로플래그세트논리 OR 배타논리합 (OR) String Instruction 명령 REP MOVS COMPS SCAS LODS STOS 설명 REP 뒤에오는스트링명령을 CS 가 0 이될때까지반복 DS:DI 가지시한메모리데이터를 ES:DI 가지시한메모리로전송 DS:DI 와 ES:DI 의내용을비교하고결과에따라플래그설정 AL 또는 AX 와 ES:DI 가지시한메모리내용비교하고결과에따라플래그설정 SI 내용을 AL 또는 AX 로로드 AL 또는 AX 를 ES:DI 가지시하는메모리에저장 Control Transfer Instruction 명령 설명 플래그의변화 CALL 프로시저호출 JMP 무조건분기 RET CALL 로스택에 PUSH 된주소로복귀 JE/JZ 결과가 0 이면분기 ZF=1 JL/JNGE 결과가작으면분기 ( 부호화된수 ) SF!= OF JB/JNAE 결과가작으면분기 ( 부호화안된수 ) CF=1 JLE/JNG 결과가작거나같으면분기 ( 부호화된수 ) ZF=1 or SF!= OF JBE/JNA 결과가작거나같으면분기 ( 부호화안된수 ) CF=1 or ZF=1 JP/JPE 패리티플래그가 1 이면분기 PF=1 JO 오버플로우가발생하면분기 OF=1 JS 부호플래그가 1 이면분기 SF=1-19 -
JC 캐리가발생하면분기 CF=1 JNE/JNZ 결과가 0 이아니면분기 ZF=0 JNL/JGE 결과가크거나같으면분기 ( 부호화된수 ) SF=OF JNB/JAE 결과가크거나같으면분기 ( 부호화안된수 ) CF=0 JNLE/JG 결과가크면분기 ( 부호화된수 ) ZF=0 and SF=OF JNBE/JA 결과가크면분기 ( 부호화안된수 ) CF=0 and ZF=0 JNP/JPO 패리티플래그가 0 이면분기 PF=0 JNO 오버플로우가아닌경우분기 OF=0 JNS 부호플래그가 0 이면분기 SF=0 JNC 캐리가아닌경우분기 CF=0 LOOP CX 를 1 감소, 0 이될때까지지정된라벨로분기 LOOPZ/LOOPE CX 가 0 이아니면지정된라벨로분기 ZF=1 LOOPNZ/LOOPNE CX 가 0 이아니면지정된라벨로분기 ZF=0 JCXZ CX 가 0 이면분기 CX=0 INT 인터럽트실행 INTO 오버플로우가발생하면인터럽트실행 IRET 인터럽트복귀 ( 리턴 ) Processor Control Instruction 명령 CLC CMC CLD CLI HLT STC NOP STD STI WAIT ESC 캐리플래그클리어캐리플래그를반전디렉션플래그를클리어인터럽트플래그를클리어정지캐리플래그셋아무동작하지않음디렉션플래그셋인터럽트플래그셋프로세서를일시정지상태로한다이스케이프명령 설명 - 20 -
2-4. STACK 구조 STACK 구조 Student Notes 스택은한쪽끝에서만데이터를넣거나뺄수있는선형구조로되어있다. 자료를넣는것을 PUSH 라 고하고데이터를꺼내는것을 POP 이라고하는데이때꺼내지는데이터는가장최근에넣은데이터가 된다. 이처럼나중에넣은값이먼저나오는것을 LIFO(Last In First Out) 구조라고한다. 스택은구현방법에따라네가지로구분할수있다. Full Stack : TOP 이마지막으로 PUSH 된데이터를가리킴 Empty Stack : TOP 이다음데이터가들어올곳을가리킴 Ascending Stack : 낮은메모리주소에서시작하여높은메모리주소방향으로자람 Descending Stack : 높은메모리주소에서시작하여낮은메모리주소방향으로자람 - 21 -
인텔아키텍처의스택은 Full Descending Stack 의형태를가지고있다. 즉, 스택의 TOP 이마지막으로들어온데이터를가리키고있으며높은메모리주소에서시작하여낮은메모리주소방향으로자란다는뜻이다. 스택에서주로사용되는세개의레지스터에는 EBP, ESP, EIP 가있다. EBP 는스택프레임의시작점을가리키는데함수내에서 ESP 를통해스택의크기를늘리고줄일때기존의 ESP 값을백업하는용도로사용된다. 그리고 ESP 는스택의 TOP 을가리키는데스택포인터이동시에사용된다. EIP 는다음에실행할명령의오프셋을가지고있는데보통함수호출후복귀주소를나타내는레지스터로써소프트웨어에의해조작될수없다. 다음그림은시스템에서프로그램이실행되고함수가호출될때스택의변화를나타낸그림이다. 그림에서보는것처럼메인프로그램이실행되는도중에 F1 이라는함수가호출이되면새로운 func1 함수에게새로운스택을할당한다. 이때 func1 함수의실행이끝나면다시메인함수로돌아오기위해서복귀주소를먼저스택에넣어둔다. 이것은스택의 func1 함수의실행이끝나면 func1 함수가사용하던스택에있던데이터를전부비운후마지막으로복귀주소를보고메인함수로돌아오기위함이다. 그리고 func1 함수내에서 func2 함수를호출하는경우도같은방법을사용한다. - 22 -
2-5. 함수호출규약 (Calling Convention) 함수호출규약 (Calling Convention) 함수호출방법에대한규약 Argument 전달방법 Stack : C 표준라이브러리, cdecl, stdcall, Pascal Register : fastcall Argument 전달순서 Right to Lest : cdecl, stdcall Left to Right Stack Clearing 방법 Caller : cdecl Callee : stdcall Return Value 주요함수호출규약 stdcall, cdecl, fastcall Student Notes 함수호출규약은프로그램이함수에게파라미터를전달하고결과값을다시받는일련의표준화된방법 이다. 즉, 함수호출을위해서밟는절차를정해둔것이라고생각하면되겠다. 함수의호출을구현하기위해서는플랫폼이나프로그래밍언어에따라다를수있겠지만기본적으로실행할함수의코드위치는갖고있는포인터나그값을가져올수있는함수이름, 현재처리하고있는함수의정보를저장할공간, 함수의실행이끝난뒤리턴값을돌려받을공간, 함수의실행에필요한인자를넘겨줄공간등이필요하다. 함수호출규약을이해하기위해서먼저스택프레임 (Stack Frame) 에대해서알아보도록하겠다. 스택프 레임은호출된함수가실행되는동안필요한지역변수나호출한함수의실행에필요한정보가손실되 - 23 -
지않도록스택에저장할때사용하는구조를의미한다. 이렇게저장해야하는정보에는다음과같은것 들이있다. 함수의실행이종료된후에리턴할주소 지역변수 자신을호출한함수의스택프레임위치 레지스터같은기계상태 예외처리리스트등 스택프레임은기본적으로함수가호출될때마다새로설정이되고 Fame Pointer( 또는 Base Pointer) 를통 해서참조할수있다. 프레임포인터는현재실행되고있는함수의스택프레임이스택에서어느주소위 치에있는지가리키는값으로인텔아키텍처에서는 EBP 레지스터에저장된다. 함수호출규약에는총 5 가지규약이있지만 (stdcall, cdecl, thiscall, fastcall, naked) 여기서는자주사용되 는 stdcall 과 cdecl 호출규약에대해서만알아보도록하겠다. stdcall stdcall 호출규약은윈도우플랫폼에서시스템 API 를호출할때사용하는규약이다. stdcall 호출규약에서컴파일러는멤버함수의오른쪽인자부터왼쪽인자순으로스택에집어넣고, 함수호출이종료되면호출된함수 (Callee) 가인자를스택에서제거하며, 리턴값이있는경우레지스터 eax 를통해서리턴값을돌려받는다. 호출된함수가스택에서제거해야할인자의개수를정확히알아야하기때문에가변개수의인자를전달할수없다. 보통 call 문다음에 ret n 을이용하여스택을정리한다. - 24 -
cdecl cdecl 호출규약은 C 컴파일러혹은 C++ 컴파일러에서전역함수나전역스태틱함수, 스태틱멤버함수호출에기본적으로사용하는규약이다. cdecl 호출규약에서컴파일러는멤버함수의오른쪽인자부터왼쪽인자순으로스택에집어넣고, 함수호출이종료된뒤함수호출자 (Caller) 는스택에집어넣은인자를제거하며, 리턴값이있는경우레지스터 eax 를통해서리턴값을돌려받는다. cdecl 호출규약에서는호출자가넣은인자를제거하게되어있으므로, 함수가가변개수의인자를가져도안전하게스택을정리할수있다. 보통 call 문다음에 add esp, n 을이용하여스택을정리한다. 가장자주사용되는함수호출규약인 stdcall 과 cdecl 의가장큰차이점은누가스택을정리하냐하는것 이다. stdcall 호출규약의경우호출된함수 (Callee) 가스택을정리하기때문에호출하는함수 (Caller) 와 Callee 모두파라미터의크기를알고있어야정상적인처리가가능하지만 cdecl 호출규약의경우 Caller 가스 택을정리하기때문에 Callee 는파리미터의크기를정확히몰라도된다. - 25 -
2-6. SEH(Structured Exception Handling) SEH (Structured Exception Handling) FS TIB FS:[0] FS:[1] FS:[2]... ERR NEXT SEH ERR FFFFFF SEH TIB (Thread Information Block) : Thread에대한중요한정보를가지고있는구조체 ERR 구조체 Next ERR 구조체 Exception Handler 시작주소 Student Notes SEH(Structured Exception Handling) 은윈도우에서제공하는예외처리방식이다. 예외 란예기치못하거나해당프로세스의정상적인실행을방해하는이벤트이다. 예외를발생시키는상황은여러가지가있는데대표적인것들을 0 으로나누기나숫자형식의오버플로우, 잘못된메모리주소에대한읽기나쓰기를시도하는것들이있다. 예외발생시운영체제는 TIB 에서가리키고있는주소로이동하여예외를처리한다. TIB 는쓰레드에대한중요한정보를가지고있는구조체인데 TIB 의첫번째멤버인 FS:[0] 은 ERR(Exception Registration Record) 구조체를가리키고있는포인터이다. ERR 구조체는다음 ERR 구조체의주소와 Exception Handler 시작주소의두개의멤버를가지고있다. - 26 -
Exception Handler 의프로토타입은다음과같다. cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ); _CONTEXT 는예외발생시모든레지스터의값을저장한다. 그리고 EAX 0, ret 시사용하게된다. +0 context 플래그세그먼트레지스터 (GetThreadContext API 를이용할때사용 ) 디버그레지스터 +4 debug register #0 +8 debug register #1 +C debug register #2 +10 debug register #3 +14 debug register #6 +18 debug register #7 +8C gs register +90 fs register +94 es register +98 ds register 일반레지스터 +9C edi register +A0 esi register +A4 ebx register +A8 edx register +AC ecx register +B0 eax register - 27 -
FPU / MMX 레지스터 +1C ControlWord +20 StatusWord +24 TagWord +28 ErrorOffset +2C ErrorSelector +30 DataOffset +34 DataSelector +38 FP registers * 8 ( 각각 10 바이트차지 ) +88 Cr0NpxState 제어레지스터 +B4 ebp register +B8 eip register +BC cs register +C0 eflags register +C4 esp register +C8 ss register Winnt.h 에정의되어있는 EXCEPTION_RECORD 는다음과같다. typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; UINT_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; ExceptionCode : 발생한예외의종류 C0000005h : 접근할수없는메모리를 Read/Write 하려할때 C0000094h : 0 으로나누려할때 C0000095h : DIV 할때발생하는오버플로 C00000FDh : 스택의최대크기를넘을때 80000001h : Guard Page 플래그가셋팅된메모리주소를접근하려할때 C0000025h : 더이상처리할수없는 Exception C0000026h : 예외처리도중시스템에의해사용되어지는 ExceptionCode 80000003h : INT3 80000004h : Trap 플래그세트 ExceptionFlags : 발생한예외에대한몇가지부가적인정보를포함 0 : 처리할수있는 Exception 1 : 처리할수없는 Exception 2 : 스택이 Unwinding 하고있을때 예제를통해서 SEH 에대해서알아보도록하겠다. - 28 -
먼저예제프로그램을 Ollydbg 에서열고실행을시켜보면아래쪽상태바에서다음과같은메시지를만 나게된다. 예외가발생한것을볼수있다. 이때 SEH chain 을확인하거나 Stack 정보를확인하면예외를처리할부 분이어느곳인지확인할수있다. 현재 ERR 구조체를스택에서확인해보면 Next SEH 는 0007FFE0 에위치해있고현재예외를처리하는 곳은 00B13313 에있다. 00B13313 에브레이크포인트를걸고 SEH 에서어떠한행동을하는지확인해보 겠다. 브레이크포인트설정후 Shift + F9 를눌러진행한다. 브레이크포인트설정후 Shift + F9 를눌러진행한다. 00B13313 에서멈춘것을확인할수있다. 이부분부터가예외발생시처리하는부분이다. 스택의내용을확인해보도록하자. 예외처리루틴으로들어가게되면앞서살펴본것과같이 ExceptionRecord, EstablishedFrame, Context, DispatchContext 순서로스택에저장된다. - 29 -
0007FBC8 7C968752 RETURN to ntdll.7c968752 // Return 주소 0007FBCC 0007FCA8 // ExceptionRecord, 환경저장 0007FBD0 0007FF90 // Frame, ERR 구조체 0007FBD4 0007FCC4 // Context 0007FBD8 0007FC84 // DispatchContext 현재위치에서부터아래쪽으로코드들을확인해보면뭔가잘못되었다는것을알수있다. 00B13321 에서 JMP SHORT 00B13324 로되어있으므로 00B13324 로점프를해야하는데 00B13324 가 보이지않는데이것은디버거가해석을잘못했기때문이다. 이렇게실제실행되는내용들이쓰레기코 드때문에보이지않는데이런부분들을찾아서필요없는코드들을전부 NOP(0x90) 처리해준다. 코드를수정할부분에서바이너리에디트를해서 ( 단축키 CTRL + E) 다음과같이수정한다. - 30 -
위와같이수정하면해당부분의코드가다음과같이변경된다. 이런코드들을전부수정하면다음과같다. 수정된코드를분석해보면 SEH 발생시어떤행동들을하는지확인할수있다. /*B13313*/ PUSH 0B1331C /*B13318*/ INC DWORD PTR SS:[ESP] /*B1331B*/ RETN /*B1331C*/ NOP 스택에 0B1331C 를넣은후 ESP 에있는값을 1 증가시킨다. 이것은 JMP 0B1331D 와같은의미이다. /*B1331D*/ MOV EAX,DWORD PTR SS:[ESP+C] ESP+C 의주소에있는값을 EAX 에저장한다. ESP+C 는 0007FD4 가되고이주소에있는값은 0007FCC4 이다. 이곳은 CONTEXT 내용있는곳이다. - 31 -
/*B13321*/ JMP SHORT 00B13324 /*B13324*/ ADD DWORD PTR DS:[EAX+B8],2 EAX+B8 은 CONTEXT 구조체를확인해보면 EIP 레지스터를나타낸다. 즉, EIP 를 2 만큼증가한다. /*B1332B*/ JMP SHORT 00B13347 00B13347 로점프 /*B1332D*/ MOV ESP,EBE817EB /*B13332*/ ADC AL,0E8 /*B13334*/ JMP SHORT 00B13347 /*B13336*/ CALL EC994226 /*B1333B*/ OR EBP,EAX /*B1333D*/ JMP SHORT 00B13347 /*B1333F*/ INT 20 /*B13341*/ JMP SHORT 00B13347 앞에서 00B13347 로점프하기때문에의미없는코드들 /*B13347*/ XOR EAX,EAX /*B13349*/ RETN EAX 값을초기화하고 SEH 처리완료한다. 다음 SEH 를호출할필요없이바로복귀 SEH 처리루틴이끝나면다음과같은코드를만나게된다. 이것은 SEH 체인에서상위 SEH 를제거하는과정이다. 그런후 F9 를눌러계속진행하면된다. 지금까지의과정은 SEH 에서어떤행동들을하는지확인한것이었다. 이예제와같이다른행동을하지 - 32 -
않고 EIP 만변경하는것이라면문제가없겠지만 SEH 처리중간에다른행동들을하게끔작성된코드들 도있을수있다. SEH 를하나씩처리해나가다보면마지막 SEH 처리에서다음과같은모습을볼수있다. 마지막 SEH 는 EIP 를 2 증가하고 SEH 내부에서하드웨어브레이크포인트를클리어하는작업을하는 것을볼수있다. XOR EAX, EAX 는 EAX 값을 0 으로설정하는것이고이것은현재 SEH 가발생한익셉 션을정상적으로처리했다는것을의미한다. - 33 -
2-7. C 코드컴파일후어셈코드변환모습 C 코드컴파일후어셈코드변환모습 for, while, if String 관련함수 strcpy strcmp strlen 컴파일러는자동화된도구이기때문에규칙을가지고컴파일을하기때문에특정프로그램을컴파일한후어셈블리어코드는일정한패턴을갖는다. Student Notes C 코드를컴파일한후실행파일을디버거로분석하면어셈블리어로표현이되는것을볼수있고특정함수의경우동일한또는비슷한패턴의어셈블리어로표현되는것을볼수있다. 우리는이런어셈블리어로된코드들을보고분석을하게된다. 숙련된리버서들은여러패턴의코드들을보기때문에어셈블리어코드만보고도금방어떤행동들을하는코드인지식별하는것이가능하지만덜숙련된리버서들은조금힘들수있다. 이번장에서는몇가지함수들을예제로하여어셈블리어코드와비교하면서 C 코드가컴파일된후에는 어떤어셈블리어코드로변하는지알아보도록하겠다. - 34 -
Example #1. for #include <stdio.h> main(int argc, char *argv[]) { int i=0; int sum=0; for(i=0; i<10; i++) { sum+=i; } printf("%d, %d\n", i, sum); } 위코드를컴파일하여실행파일을만든후실행파일을디버거로확인하면다음과같은코드들을볼수 있다. 루프문을확인하는가장확실한방법은 v 표시와 ^ 표시, 그리고 > 표시를확인하는것이다. 0040103D 에서 00401048 로분기하고조건이만족할경우 (CMP 결과 ) 00401059 로그렇지않을경우계속코드를 진행하다가 00401057 에서 0040103F 로분기한다. - 35 -
Example #2. if, strcpy, strcmp, strlen 이번예제는 if 문과함께스트링관련함수들 (strcpy, strcmp, strlen) 이있는예제를살펴보겠다. #include <stdio.h> void main( int argc, char *argv[] ) { char src[] = "ForEducationbydemantos"; char *dst=(char *)malloc( 30 * sizeof(char)); int cnt=0; printf("input the password : "); fgets(dst, 30, stdin); dst[strlen(dst)-1] = '\0'; if( strlen(dst)!= strlen(src) ) { printf("not match!!\n"); exit(0); } else { if(!strcmp(dst, src) ) { printf("good\n"); exit(0); } else printf("not match!!\n"); exit(0); } } 위예제는비교할패스워드를위한메모리공간을할당한후입력된값의길이를비교한후맞을경우입 력된값과실제패스워드의문자열을비교하는프로그램이다. 먼저문자의길이를구하는부분이다. - 36 -
4 글자씩잘라내면서문자의길이를구한다. 함수의실행결과는 EAX 레지스터에저장이되고 F8 로한스텝씩진행하면서 EAX 레지스터를살펴보면길이가저장되는것을확인하수있다. 아래코드는입력된문자와비교할문자의길이를구하는코드들이다. 0040109E 에서입력된문자의길이와비교할문자의길이를비교해서같을경우 004010B9 으로분기하 고그렇지않을경우 Not match!! 라는메시지를출력하는루틴으로분기하게된다. 문자의길이가같 을경우입력한문자와비교할문자가같은지비교하는루틴으로분기한다.(004010B9) 사용자가입력한문자를 EAX 에저장하고비교할문자를 EDX 에저장한후 strcmp 함수를호출하고있 다. strcmp 함수내부로들어가보면 (F7 을눌러진행 ) 다음과같은코드를볼수있다. 함수내부에서는 4 글자씩분할한후비교를하고있다. 먼저첫번째글자를비교하는부분이다. - 37 -
두번째글자를비교하는부분이다. 두글자비교후 SHR 을이용해서쉬프트한후다시두문자를비교하는부분이다. 0x10 은 10 진수로 16 이고이것은 16 비트를의미한다. 즉, 2 바이트만큼오른쪽으로쉬프트하므로 4 글자중앞에서비교했던 두글자를없애고나머지두글자로다음글자를비교하는것이다. 처음 4 글자가같을경우 ECX 와 EDX 값을 4 씩더해서다음 4 글자를비교하게된다. 위코드를 F8 진행하면서확인해보아야할부분은레지스터창과상태창이다. RETN 전코드에브레이크 포인트를설정하고 CTRL+F8 로실행을해보면레지스터창에서 4 글자씩감소하는것을확인할수있고 상태창에서는현재비교할문자들을보여주는것을확인할수있다. 이런반복문이나기타여러함수들에대한어셈블리어코드들은자주보면서눈에익숙하게하는것이 가장좋은방법이다. 자주이런코드들을접하다보면자연스럽게코드들이눈에들어오게된다. - 38 -
Example #3. while #include <stdio.h> #include <conio.h> int main(void) { char ch; ch = getche(); } while(ch!='q') { ch=getche(); } printf("found the q"); return 0; getche 함수에의해서사용자의입력을받아들이고 q 라는글자가입력이되면 found the q 라는메시 지를출력하면서끝난다. while 에의해서생성된어셈코드는아래와같다. 00401034 에서비교를하고같을경우 00401043 으로분기하여루프문을빠져나오지만그렇지않을경 우계속입력을받아들인다. Hex dump 부분에보이는 v 표시와 ^ 표시, 그리고 > 표시를잘보도록하자. 빨간색으로화살표가그려질경우분기를한다는것이고회색으로표시되면분기되지않는다는것이다. - 39 -
2-8. 특정루틴에서사용되는 API 함수 특정루틴에서사용되는 API 함수 파일및디렉토리관련 CreateFile, ReadFile, WriteFile, SetFilePointer, CopyFile, GetFileAttribute, SetFileAttribute, FindFirstFile, FindNextFile, GetModuleFileName, GetSystemDirectory, GetWindowsDirectory, GetCommandLine, SetCurrentDirectory 레지스트리관련 RegCreateKey, RegOpenKeyEx, RegSetValueEx, RegQueryValueEx 네트워크관련 WSAStartup, WSAAPI socket, recv, send, listen, accept, gethostbyname, ntohs, inet_addr, WNetOpenEnum, WNetEnumResource, InternetGetConnectedState, ioctlsocket 메모리관련 RtlZeroMemory, GlobalAlloc 기타 ShellExecute, GetProcAddress, CreateThread Student Notes 윈도우환경에서 exe 파일을분석할경우 API 함수들을많이알고있는것이분석에도움이된다. 디버거를사용하여현재프로그램에서사용되는 API 함수들을나열할수있는데함수들의기능이나리턴값을알아야만프로그램을디버깅하는과정에서브레이크포인트를지정할곳을쉽게찾아낼수있다. 이번장에서는악성코드에서자주사용되거나필히알아두어야할 API 함수들에대해서살펴보도록하겠다. 파일, 디렉토리관련함수, 레지스트리관련함수, 네트워크관련함수, 메모리관련함수들로구분지어 서보도록하겠고기타함수들에대해서도간단히살펴보도록하겠다. - 40 -
파일및디렉토리관련함수 CreateFile : 파일을생성하거나연다. ReadFile : 파일의내용을읽는다. WriteFile : 파일에내용을쓴다. SetFilePointer : 파일포인터를이동시킨다. CopyFile : 파일을복사한다. GetFileAttribute : 파일이나디렉토리의속성을저장한다. SetFileAttribute : 파일이나디렉토리의속성을설정한다. FindFirstFile : 디렉토리에서파일을찾는다. FindNextFile : 파일검색을계속한다. FindFirstFile 함수의호출에이어서검색을계속한다. GetModuleFileName : 파일의절대경로를저장한다. GetSystemDirectory : 윈도우가설치된디렉토리를저장한다. GetWindowsDirectory : 윈도우가설치된디렉토리를알아낸다. GetCommandLine : 현재프로세스의 commandline 문자열을저장한다. SetCurrentDirectory : 현재프로세스에대해현재디렉토리를바꾼다. 레지스트리관련함수 RegCreateKey : 레지스트리키를생성하거나연다. RegOpenKeyEx : 레지스트리키를연다. RegSetValueEx : 레지스트리값을설정한다. RegQueryValueEx : 타입과데이터를저장한다. 네트워크관련함수 WSAStartup : WS2_32.DLL 을사용하기위해초기화한다. socket : 소켓을생성한다. recv : 연결되거나바운드된소켓으로부터데이터를받는다. send : 연결된소켓으로데이터를보낸다. listen : 연결요청이들어오는것을기다린다. accept : 소켓을통해들어오는연결을허용한다. gethostbyname : 호스트정보를저장한다. - 41 -
ntohs : network byte order 를 host byte order 로변환한다. inet_addr : IN_ADDR 구조체로주소를변환한다. WNetOpenEnum : 열거할네트워크자원에대해목록화한다. WNetEnumResource : WNetOpenEnum 에서나열된데이터를반복적으로가져온다. InternetGetConnectedState : 로컬시스템의연결상태를저장한다. ioctlsocket : 소켓의입출력모드를제어한다. 메모리관련함수 RtlZeroMemory : 한메모리블록을 0 으로채운다. GlobalAlloc : Heap 메모리를할당한다. 기타함수 ShellExecute : 프로그램내에서다른프로그램을실행시키기위해사용된다. GetProcAddress : SLL 로부터익스포트된함수의주소를저장 CreateThread : 실행할쓰레드를생성한다. 이외에도여러가지함수들을알고있어야하며함수들에대한자세한내용은 WIN32.HLP 파일을참조 하면된다. - 42 -
Module 3 PE 파일구조 Objectives PE 파일포맷 DOS 헤더및 DOS Stub Code PE File Header Optional Header Section Tables Import Table Export Table - 43 -
3-1. PE 파일포맷 PE 파일포맷 DOS Header DOS Stub Code ImageBase DOS Header DOS Stub Code PE Header 248byte 24 + optional header( 기본 224) PE Header Section Table 40byte x 섹션개수 Section Table Section #1 Section #2... Section #n DISK Section #1 Section #2... Section #n MEMORY Student Notes PE 파일형식 (Portable Executable File Format) 이란파일 (File) 에담겨다른곳에옮겨져 (Portable) 도실행시킬수있도록 (Executable) 규정한형식 (Format) 이란뜻이다. Win32 의기본적인파일형식이며윈도우운영체제에서실행되는프로그램이모두 PE 파일형식을가지고있다. 이런실행프로그램또는응용프로그램은 EXE 파일확장자를가지는데이런 EXE 파일이 PE 파일의대표적인예이다. 그리고동적링크라이브러리인 DLL 파일도 PE 파일형식을가지고있다. PE 파일은운영체제와상관없이 Win32 플랫폼에서는공통으로사용할수있다. 즉, 인텔 CPU 를사용하 지않는경우에도어떠한 Win32 플랫폼의 PE 로더도이파일형식을사용할수있다는것이다. Win32 Platform SDK 의 Winnt.h 헤더파일에보면 PE 관련구조체들이선언되어있다. 이구조체에서파 - 44 -
일이라는명칭대신이미지 (Image) 라는단어를사용하는데이것은 PE 파일이하드디스크에파일로존재하지만실행되기위해메모리로로드되기때문이다. 이번모듈을학습하는과정에서꼭기억해야할것은 PE 파일은디스크에서의모습과메모리에서의모습이거의같다 는것이다. PE 구성요소 DOS Header DOS Stub Code PE Header Section Table Section DOS Header 디스크상에서 PE 파일은 DOS 헤더로시작한다. 메모리상에서는 ImageBase 에서시작하기때문에 DOS 헤더는 ImageBase 에서찾을수있다. 파일의정보는 Stub_PE 프로그램을이용하였다. 파일상의가장첫부분과메모리상에서는 ImageBase 의위치인 0x01000000 과일치하는것을확인할수 있다. DOS 헤더는 4D 5A(MZ) 로시작한다. MZ 는 DOS 개발자인 Mark Zbikowski 라는사람의이니셜로 DOS 시그내처로사용된다. - 45 -
PE Header PE 헤더는 PE 파일의정보를가지고있다. 섹션의개수나파일의속성, optional 헤더의크기를비롯하여 ImageBase, 엔트리포인트주소, 메모리상에각섹션이차지하는크기, 디스크상에서의섹션정렬, PE 파 일의총사이즈, 디스크상에서의헤더의총사이즈등과같은정보를가지고있다. PE 헤더의위치는 DOS 헤더에있는 e_lfanew 값을이용하여확인할수있다. e_lfanew 는 DOS 헤더 의마지막 4byte 에위치하고있다. Section Table 섹션테이블은섹션의이름, 섹션의파일상에서의위치및사이즈, 메모리상에서의위치및사이즈, 속성값에대한정보를가지고있다. 섹션테이블은 PE 헤더바로뒤에위치하며섹션테이블의위치는 PE 헤더시작주소에서 PE 헤더의사이즈를더해주면된다. PE 헤더는기본적으로 248bytes 의크기를가지면경우에따라서는변할수있다. 섹션테이블은각섹션별로 40bytes 의크기를갖는다. 따라서섹션의개수가 4 개일경우섹션테이블의크기는 40bytes * 4 = 160bytes 가된다. Section 섹션은실제데이터가위치하는공간이다. 각섹션의위치는섹션테이블에있는섹션헤더에서확인할 수있다. 섹션헤더에는 VirtualAddress 값과 PointerToRawData 라는값이저장되어있다. 둘다 offset 값 - 46 -
으로메모리상에서의위치와디스크상에서의위치를가리키는값이다. 이렇게두개의값이별도로존 재하는이유는디스크상에서의위치와메모리상에서의위치가다를수있다는의미이다..text 섹션의디스크상에서의 offset 이 0x400 임을확인할수있고.text 섹션이파일의시작점부터 0x400 만큼떨어진곳에있다는것을의미한다. Winhex 에서 0x400 의위치를확인하면다음과같다. 위정보를통해섹션헤더의 PointerToRawData 값은해당섹션의파일상에서의시작위치를확인하였다. 이번에는메모리상에서는어디에위치하는지확인해보도록하겠다. 메모리상에서의.text 섹션의위치는 VirtualAddress 에서확인할수있다. VirtualAddress 의값이 0x1000 인것을확인할수있고이값은 ImageBase 로부터의 offset 값이다. 이런 offset 을 RVA(Relative Virtual Address) 라고부른다. 따라서.text 섹션의메모리상에서의위치는 ImageBase 와 VirtualAddress 을합한값인 0x01001000 이된다. 0x01001000 의위치로이동하면다음과같은 HEX 값을확인할수있다. - 47 -
파일상에서.text 섹션의시작점의데이터는 493AD377 이었는데메모리상에서.text 섹션의시작점데이 터는 FAF4F377 로서로다른값인것을확인할수있다. 이렇게파일상에서의섹션의내용과메모리상 에서의섹션의내용이다를수있다는것을확인하였다. PE 파일은메모리상에서와파일상에서거의같다는것을확인했다. 차이가발생하는부분은섹션인데 이것은 SectionAlignment 와 FileAlignment 와연관이있다. 이것은디스크상에서의정렬단위와메모리 상에서의정렬단위가다르다는것을의미한다. 정렬단위는뒤에서나오는 Optional Header 에서자세히보도록하고간단하게두가지만알아보겠다. 디스크상의섹션은 FileAlignment 의배수가되는주소에서시작 메모리상의섹션은 SectionAlignment 의배수가되는주소에서시작 - 48 -
3-2. DOS 헤더및 DOS Stub Code DOS 헤더및 DOS Stub Code DOS Header MZ DOS Stub Code PE signature PE File Header DOS Header e_lfanew IMAGE_DOS_HEADER (64byte) Optional Header DOS Stub Code Section Table Section #1 Section #2... Section #n PE signature Student Notes DOS 헤더는 DOS 와호환을위해사용되는헤더이다. PE 파일은 DOS 헤더로시작하고 DOS 헤더는항 상 64bytes 의크기를갖는다. 우리가알아야할부분은가장처음 2byte 를차지하는 e_magic 과마지막 4byte 를차지하는 e_lfanew 이다. Winnt.h 에선언되어있는 IMAGE_DOS_HEADER 는다음과같다. typedef struct _IMAGE_DOS_HEADER { // DOS.EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed - 49 -
WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; e_magic 은 DOS 헤더의시그내처로 4D 5A(MZ) 값을가진다. e_lfanew 는 PE 헤더의시작점을가리 키는오프셋값이다. DOS Stub Code 는크게중요하지않는부분이다. 이부분이없더라도프로그램이실행되는데는아무런문제가없다. 이부분에있는내용은윈도우용응용프로그램을도스모드에서실행시켰을경우 This program cannot be run in DOS mode 라는메시지를출력하고프로그램이종료되는코드가삽입되어있다. 그리고응용프로그램을제작할경우오브젝트파일을링킹할때 STUB 옵션을사용하여원하는스텁코드를삽입할수도있다. - 50 -
3-3. PE File Header PE File Header DOS Header DOS Stub Code PE signature PE File Header Optional Header Section Table PE\0\0 Machine NumberOfSections TimeDateStamp PointerToSymbolTable NumberOfSymbols SizeOfOptionalHeader Characteristics 4byte IMAGE_FILE_HEADER (20byte) Section #1 Section #2... Section #n Student Notes Winnt.h 에선언되어있는 IMAGE_NT_HEADERS 는다음과같다. typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; IMAGE_NT_HEADERS 에는 PE 시그내처와 IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER 로구성되어있다. 첫번째로 PE 시그내처는 50 45 00 00 으로고정되어있고두번째나오는 IMAGE_FILE_HEADER 도 20bytes 의고정사이즈를갖는다. 마지막에오는 IMAGE_OPTIONAL_HEADER 는 224bytes 의사이즈를갖지만원칙적으로는가변사이즈이다. 이는 - 51 -
Data Directory 때문인데 Data Directory 는 Optional 헤더부분에서보도록하겠다. Winnt.h 에선언되어있는 IMAGE_FILE_HEADER 는다음과같다. typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; Winhex 에서보면다음과같다. PE 시그내처는 DOS 헤더의가장마지막에있는 e_lfanew 가가리키고있는주소에있다. File 헤더에서꼭알아두어야할필드는 4 가지이다. Machine : CPU ID 를나타낸다. IA32 의경우 0x14C 가되고 IA64 일경우 0x200 이되어야한다. NumberOfSections : 섹션의개수를의미한다. SizeOfOptionalHeader : 파일헤더뒤에오는 optional 헤더의크기를나타낸다. 기본적으로 224bytes 의사이즈를갖지만 data directory 가생략될경우 96bytes 의사이즈를갖게된다. - 52 -
Characteristics : 파일의속성을나타낸다. 일반적인실행파일의경우 0x10F 의값을가진 다. Winnt.h 에다음과같은속성이정의되어있다. #define IMAGE_FILE_RELOCS_STRIPPED #define IMAGE_FILE_EXECUTABLE_IMAGE #define IMAGE_FILE_LINE_NUMS_STRIPPED #define IMAGE_FILE_LOCAL_SYMS_STRIPPED #define IMAGE_FILE_AGGRESIVE_WS_TRIM #define IMAGE_FILE_LARGE_ADDRESS_AWARE #define IMAGE_FILE_BYTES_REVERSED_LO #define IMAGE_FILE_32BIT_MACHINE #define IMAGE_FILE_DEBUG_STRIPPED #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP #define IMAGE_FILE_NET_RUN_FROM_SWAP #define IMAGE_FILE_SYSTEM #define IMAGE_FILE_DLL #define IMAGE_FILE_UP_SYSTEM_ONLY #define IMAGE_FILE_BYTES_REVERSED_HI 0x0001 0x0002 0x0004 0x0008 0x0010 0x0020 0x0080 0x0100 0x0200 0x0400 0x0800 0x1000 0x2000 0x4000 0x8000 일반적으로 0x10F 의값을가진다고했는데이는 IMAGE_FILE_RELOCS_STRIPPED(0x0001), IMAGE_FILE_EXECUTABLE_IMAGE(0x0002), IMAGE_FILE_LINE_NUMS_STRIPPED(0x0004), IMAGE_FILE_LOCAL_SYMS_STRIPPED(0x0008), IMAGE_FILE_32BIT_MACHINE(0x0100) 의속성이설정되어있는것이다. - 53 -
3-4. Optional Header Optional Header DOS Header DOS Stub Code PE signature PE File Header Optional Header Section Table Section #1 Section #2... Section #n Magic AddressOfEntryPoint ImageBase SectionAlignment FileAlignment SizeOfImage SizeOfHeader NumberOfRvaAndSizes... IMAGE_DATA_DIRECTORY #0 IMAGE_DATA_DIRECTORY #1... IMAGE_DATA_DIRECTORY #15 IMAGE_OPTIONAL _HEADER (224byte) Data Directory (128byte) Student Notes Optional 헤더는 PE 파일에서가장중요한부분이라고할수있다. 명칭은옵션이지만절대옵션의성격을갖고있지않고프로그램이메모리상에로드되었을때시작할주소라든지메모리상에서의정렬단위와같이중요한정보들을다수포함하고있다. optional 헤더는 30 개의필드와 1 개의데이터디렉토리를가지고있다. 하지만모든필드를알아야할필요는없다. 여기서는중요한필드에대해서만알아보도록하겠다. 먼저 Winnt.h 에선언되어있는 IMAGE_OPTIONAL_HEADER 를보도록하자. typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; - 54 -
DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; Winhex 로보면다음과같다. - 55 -
Machine : optional 헤더의시작위치에존재하는필드로 optional 헤더를구분하는시그내처로사용된다. 0x10B 로고정되어있다. AddressOfEntryPoint : 엔트리포인트는 PE 파일이메모리에로드된후맨처음으로실행되는코드의주소를가지고있다. 이부분에지정된주소값은가상주소가아닌 RVA 값이다. 즉, ImageBase 에서부터의오프셋이다. ImageBase : PE 파일이로더에의해서로드되는위치이다. EXE 파일의경우가상메모리공간에가장처음로드되므로항상 ImageBase 에로드되지만 DLL 의경우 ImageBase 로지정된주소공간이다른모듈에의해서이미사용중인상황이발생할수있다. 이런경우해당 DLL 을다른곳에로드하고재배치작업을수행하게된다. 대부분의링커는 EXE 파일일경우 0x00400000 으로, DLL 일경우 0x10000000 으로설정한다. SectionAlignment : 각섹션이메모리상에서차지하는최소단위이다. 각섹션의시작주소는언제나이 SectionAlignment 에서지정된값의배수가되어야한다. 디폴트값은 0x1000(4096byte) 이다. FileAlignment : 디스크상에서섹션이차지하는최소단위이다. 각섹션의시작주소는 FileAlignment 필드에지정된값의배수가되어야한다. 디폴트값은 0x200(512byte) 이고 2 의 n 승의형태의값을사용해야한다. SizeOfImage : 메모리상에로드된 PE 파일의총사이즈이고 SectionAlignment 의배수가되어야한다. SizeOfHeader : : 디스크상에서의헤더의총사이즈이고 FileAlignment 의배수가되어야한다. - 56 -
Data Directory 데이터디렉토리는 Optional 헤더다음에오는 128bytes 사이즈의 IMAGE_DATA_DIRECTORY 구조체 의배열이다. Winnt.h 에다음과같이선언되어있다. typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 마지막에정의된이배열의엔트리의개수는 Optional 헤더의마지막필드인 NumberOfRvaAndSize 에정의되어있던값과같은값으로총 16 개의엔트리를가지고있다. 데이터디렉토리의각엘리먼트들은 Export table, Import table 등 PE 파일에서중요한역할을하는개체들의주소 (VirtualAddresS) 와크기 (Size) 에대한정보를가지고있다. 총 16 개의엘리먼트중 15 개가실제정보를가지고있고마지막은 0x00 으로채워진비어있는엔트리 이다. 15 개의엘리먼트중몇가지엘리먼트에대해서만간단히알아보도록하겠다. IMAGE_DIRECTORY_ENTRY_EXPORT : EXPORT 테이블의메모리상에서의시작점과크기에 대한정보를가지고있다. EXPORT 테이블은대부분 DLL 에존재한다. - 57 -
IMAGE_DIRECTORY_ENTRY_IMPORT : IMPORT 테이블의메모리상에서의시작점과크기에대한정보를가지고있다. IMAGE_DIRECTORY_ENTRY_BASERELOC : 기준재배치정보를가리킨다. 재배치란로더가실행모듈을원하는위치에위치시키지못했을때코드상의포인터연산과관련된주소를다시갱신해야하는경우를말한다. IMAGE_DIRECTORY_ENTRY_TLS : Thread Local Storage 초기화섹션에대한포인터이다. 안티리버싱기법중 TLS Callback 에서사용되기때문에알아두는것이좋다. IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT : DLL 바인딩과관련된정보를가지고있다. IMAGE_DIRECTORY_ENTRY_IAT : 첫번째임포트주소테이블 (IAT) 의시작주소를가리킨다. 임포트된각각의 DLL 에대한 IAT 는메모리상에서연속적으로나타난다. IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT : 지연로딩정보에대한포인터이다. - 58 -
3-5. Section Table Section Table DOS Header DOS Stub Code PE signature PE File Header Optional Header Section Table Section #1 Section #2... Section #n Name VirtualAddress SizeOfRawData PointerToRawData Characteristics Name VirtualAddress SizeOfRawData PointerToRawData Characteristics... Name VirtualAddress SizeOfRawData PointerToRawData Characteristics Section #1 Section #2 Section #n Student Notes 섹션테이블은 IMAGE_SECTION_HEADER 타입의엘리먼트로구성된배열이다. 섹션헤더는로더가 각섹션을메모리에로드하고속성을설정하는데필요한정보들을가지고있다. 섹션은동일한성질의데이터가저장되어있는영역이다. 섹션은윈도우에서사용하는메모리프로텍션매커니즘과연관이있는데윈도우의경우메모리프로텍션의최소단위가페이지이고페이지단위로여러속성을설정해두고속성에위배되는행동을시도할때 access violation 을발생시켜메모리를보호한다. 즉, 페이지의일부는읽기만가능하고, 페이지의일부는읽고, 쓰기가가능하도록설정하는방식의프로텍션은허용하지않는다는말이다. 이것은성질이다른데이터들을하나의페이지에담을수없다는것을의미한다. 그렇다보니프로그램 - 59 -
에포함된데이터들중읽기와실행이가능해야하는데이터인실행코드와읽고쓰기가가능한데이터, 읽기만가능한데이터들을별도의페이지에두어야하는데로더의입장에서는이를구분할방법이없으므로섹션이라는개념을두어실행파일생성단계에서구분해놓도록하는것이다. Winnt.h 에선언되어있는 IMAGE_SECTION_HEADER 는다음과같다. typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; Winhex 에서보면다음과같다. Name : 섹션의이름이다. 8byte 의크기를가지고있지만이름이 NULL 이거나 8byte 보다길어도상관없다. 8byte 보다길경우엔 8byte 만큼만이름으로사용하고나머지는버리게된다. VirtualAddress : 섹션이로드될가상주소 (RVA 값 ) 이다. SizeOfRawData : 파일상에서의섹션의사이즈이다. FileAlignment 의배수이어야한다. PointerToRawData : 파일상에서의섹션의시작위치를나타낸다. Characteristics : 섹션의속성값이다. 로더는 PointerToRawData 가지정한곳에서부터 SizeOfRawData 만큼데이터를읽어들여 VirtualAddress - 60 -
에맵핑한후 Characteristics 에설정된속성을이용하여페이지프로텍션을적용한다. Reverse Engineering - 61 -
3-6. Import Table Import Table IMPORT TABLE IMAGE_THUNK_DATA VirtualAddress Size IMAGE_DIRECTORY_ENTRY_IMPORT OriginalFirstThunk TimeDateStamp ForwarderChain ILT #1 IMAGE_IMPORT_BY_NAME hint func1 Name FirstThunk IMAGE_THUNK_DATA Binding 전 OriginalFirstThunk ILT #2 TimeDateStamp IAT #1 USER32.DLL IMAGE_IMPORT_DESCRIPTOR ForwarderChain Binding 후 Name FirstThunk KERNEL32.DLL IAT #2 func1 NULL Student Notes Import 테이블에는 PE 파일이실행될때외부로부터가져와서사용하는함수들 (DLL) 의목록이들어있 다. 위그림에서는 USER32.DLL 과 KERNEL32.DLL 을임포트한모습을예로한것이다. DLL 들은함수를익스포트하고 EXE 가그 DLL 로부터함수를임포트한다. 익스포트된함수를가져온다는것은결국해당 DLL 과사용하는함수에대한정보를어딘가에저장한다는것을의미하고사용하고자하는익스포트함수들과그 DLL 에대한정보를저장하고있는곳이 Import Table 이다. 일반적으로 PE 파일의섹션테이블에는.idata 라는이름으로지정된다. 각 IMAGE_IMPORT_DESCRIPTOR 엔트리는임포트한 DLL 의정보를가지고있으며마지막은임포트 테이블의끝을알리기위해 NULL 로채워져있다. - 62 -
Winnt.h 에정의되어있는 IMAGE_IMPORT_DESCRIPTOR 와관련된구조체는다음과같다. typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; OriginalFirstThunk : ILT(Import Lookup Table) 을가리키는 RVA 값이다. 앞의그림에서보이는것처럼 ILT 는 IMAGE_THUNK_DATA 로구성된배열이다. IMAGE_THUNK_DATA 는상황에따라서 IMAGE_IMPORT_BY_NAME 을가리키기도하고함수의주소를가리키기도하며오디널값으로사용되거나포워더로사용되기도한다. TimeDateStamp : 바인딩전에는 0 으로설정되며바인딩후에는 -1 로설정된다. ForwarderChain : 바인딩전에는 0 으로설정되며바인딩후에는 -1 로설정된다. Name : 임포트한 DLL 의이름을가리키는 RVA 값이다. FirstThunk : IAT(Import Address Table) 의 RVA 주소값을가지고있다. IAT 역시 ILT 처럼 IMAGE_THUNK_DATA 배열이고바인딩전에는 ILT 와동일한모습을갖는다. 하지만 PE 파일이메모리에로드된후에는로더가임포트테이블의각엔트리의이름정보를확인한후해당 DLL 의익스포트테이블을참조하여함수의실제주소를알아낸다. 그리고나서 IAT 를실제함수주소로업데이트한다. IMAGE_THUNK_DATA 는다음과같이정의되어있다. typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function; DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData; } u1; } IMAGE_THUNK_DATA32; IMAGE_THUNK_DATA 와 ILT, IAT 의관계를정리해보면다음과같다. ILT - OriginalFirstThunk 가가리키고있다. - 바인딩전후의모습이변경되지않는다. - 63 -
- IMAGE_IMPORT_BY_NAME 구조체를가리키거나 Ordinal 로사용되기도한다. - 임포트한함수에대한 Ordinal 값을저장하고있는배열이거나임포트한함수에대한이름을저장하고있는 IMAGE_IMPRT_BY_NAME 구조체의 RVA 값으로이루어진배열이다. - 함수의의미로사용되지않는다. IAT - FirstThunk 가가리키고있다. - 바인딩전에는 AddressOfData 의의미로사용되거나 Ordinal 의의미로사용된다. - IMAGE_THUNK_DATA 가 IMAGE_IMPORT_BY_NAME 을가리키고있으면 AddressOfData 의의미로사용된것이다. IMAGE_IMPORT_BY_NAME 은임포트할수있는함수의이름을저장하고있는구조체이다. - 최상위비트값이 1 이면 ordinal 로사용된것이고, 0 이면 AddressOfData 로사용된것이다. - 바인딩후에는함수의실제주소를가리킨다. 다음그림은 IMAGE_THUNK_DATA 가 AddressOfData 로사용될경우를도식화한것이다. 다음그림은 IMAGE_THUNK_DATA 가 Ordinal 로사용될경우를도식화한것이다. - 64 -
Winhex 에서임포트테이블을확인해보면다음과같다. 앞서살펴보았듯이임포트테이블의마지막은 NULL 로채워져있다. 현재이파일은 6 개의 DLL 을임 포트한다는것을알수있다. Import Table 의위치 임포트테이블을메모리상에서찾을때는 IMAGE_DIRECTORY_ENTRY_IMPORT 에지정된 - 65 -
VirtualAddress 를찾아보면확인할수있다. 하지만파일상에서즉, 메모리에로드되기전에임포트테이블이어디에있는지에대한정보는 PE 파일에저장되어있지않다. Stud_PE 로확인해보면임포트테이블에대한정보를확인할수있다. 재미있는건실제 PE 파일에존재하지않는정보까지확인할수있다는것이다. Import Table 에서 Raw 라는항목은파일상에서의임포트테이블의위치를나타내는것이다. 실제 PE 파일에서데이터디렉토 리에는 RVA 값인 VirtualAddress 와 Size 만가지고있다. 임포트테이블의 RVA 값은 0x12FD4 이므로.text 섹션에있다는것을알수있다..text 섹션의 RawOffset 이 0x400 이므로파일상에서의위치는 0x123D4 가된다. 간단하게공식을만들자면파일상에서임포트 테이블의위치는 ( 임포트테이블의 RVA) ( 임포트테이블이위치한섹션의 VirtualOffset) + RawOffset - 66 -
보통임포트테이블은임포트섹션의시작점에위치하지만항상그런것은아니다. 앞의예제인 calc.exe 에서도임포트테이블이임포트섹션이아닌.text 섹션에위치하고있는것을확인할수있다. 섹션헤더에포함된임포트섹션에대한정보는파일을메모리에로딩하는과정에서파일상에서임포트섹션을구분하고메모리에로딩하기위해서사용하는것뿐이다. 그리고 PE 파일이메모리에로딩되고나면 IAT(Import Address Table) 을수정해주어야한다. 따라서로 더는메모리상에서임포트테이블의위치를알수있어야하는데이정보는데이터디렉토리중두번째 인 IMAGE_DIRECTORY_ENTRY_IMPORT 에저장되어있다. 임포트된 DLL 확인 앞서살펴보았던 IMAGE_IMPORT_DESCRIPTOR 구조체에임포트한 DLL 의정보를가지고있었다. 먼저임포트테이블의파일상에서의위치를찾아보도록하자. 임포트테이블의 RVA 가 0x12FD4 이고임포트테이블의위치는.text 섹션에속해있었다. 즉, ( 0x12FD4 0x1000 ) + 0x400 = 0x123D4 가된다. Winhex 를통해파일상에서확인해보도록하겠다. - 67 -
calc.exe 프로그램이임포트할 DLL 의이름을가리키고있는 RVA 값은 0x134D6 이다. 이값은파일상에서의오프셋이아니라메모리에로드된후에찾아갈 RVA 값이므로파일상에서는다른위치에있을것이다. Stud_PE 를이용하여섹션테이블을살펴보면 0x134D6 는.text 섹션안에있는것을확인할수있다. 결국 Name 은 0x134D6 0x1000 + 0x400 = 0x128D6 에서확인할수있다. 임포트디렉토리의첫번째엔트리는 KERNEL32.dll 인것을알수있다. - 68 -
3-7. Export Table Export Table EXPORT TABLE VirtualAddress Size IMAGE_DIRECTORY_ENTRY_EXPORT Name Base NumberOfFunctions NumberOfNames AddressOfFunctions AddressOfNames AddressOfNameOrdinals... MYDLL.DLL RVA RVA RVA code code Kernel32.OpenProcess Name Base NumberOfFunctions NumberOfNames RVA RVA MyFunc1 Myfunc2 AddressOfFunctions AddressOfNames AddressOfNameOrdinals... ordinal ordinal Student Notes Import 테이블이외부로부터가져오는함수들의목록이들어있다면 Export 테이블은다른프로그램을위한기능을제공하기위해노출하는함수들의목록이들어있다. Export 테이블은 Data Directory 중에서 0 번째인덱스인 IMAGE_DIRECTORY_ENTRY_EXPORT 에서 VirtualAddress 와 Size 를확인할수있다. Export 테이블은 Import 테이블에비해비교적직관적이고간단한구조로되어있다. Winnt.h 에는다음과같이정의되어있다. typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; - 69 -
DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; Name : DLL 이름을나타내는 ASCII 문자열의위치를지시하는 RVA 값 Base : 익스포트된함수들에대한서수의시작번호 NumberOfFunctions : AddressOfFunctions 가가리키는 RVA 배열의개수 NumberOfNames : AddressOfNames 가가리키는 RVA 배열의개수 AddressOfFunctions : 함수의실제주소가담긴배열 (RVA 값 ) AddressOfNames : 함수의심볼을나타내는문자열배열 (RVA 값 ) AddressOfNameOrdinals : 함수의서수값의배열위치. 실제해당함수의서수는 Base 값을더해야한다. 구조체에선언된것과같이익스포트테이블은내보내지는 DLL 이가지고있는함수들의이름과위치 (RVA 값 ), 그리고서수를가지고있다. Export Table 의위치 파일상에서익스포트데이블을찾는것은임포트테이블을찾는방법과동일하다. 1. IMAGE_DIRECTORY_ENTRY_EXPORT 에지정된 VirtualAddress 확인 VirtualAddress 는 0x00016A1C 이고 Size 는 0x00004B9A 이다. 2. VirtualAddress 를이용하여 RawOffset 계산 ( 익스포트테이블의 RVA) ( 익스포트테이블이위치한섹션의 VirtualOffset) + RawOffset 0x00016A1C 0x00001000 + 0x00000400 = 0x00015E1C - 70 -
익스포트테이블의 RVA 값은 0x16A1C 이므로.text 섹션에있다는것을알수있다..text 섹션의 RawOffset 이 0x400 이므로파일상에서의위치는 0x15E1C 가된다. Winhex 에서보면다음과같다. 그렇다면함수의이름과서수를가지고함수주소를찾는방법에대해서알아보도록하겠다. Name : 0x000186D2 Base : 1 NumberOfFuntions : 0x000002DB NumberOfNames : 0x000002DB AddressOfFunctions : 0x00016A44 AddressOfNames : 0x000175B0 AddressOfNameOrdinals : 0x0001811C 여기에서사용되는 offset 값은전부 RVA 이므로파일상에서찾을때는앞서했던계산과정을거쳐야 한다. - 71 -
Name : 0x000186D2 0x1000 + 0x400 = 0x00017AD2 이름이 USER32.dll 이라는것을알수있다. 이름의마지막은 NULL ( \0 ) 로끝난다. 서수의시작번호는 1 이고 (Base : 1), 함수의개수는 731 개이고 (NumberOfFuntions : 0x2DB), 함수이름 의개수도 731 개이다. (NumberOfNames : 0x2DB) 실제함수의이름이있는곳은 AddressOfNames 이다. AddressOfNames : 0x000175B0 0x1000 + 0x400 = 0x000169B0 0x000186DD 은 RVA 값이므로다시파일상에서의 offset 으로변환하면 0x00017ADD 이다. 첫번째실제함수의이름은 ActivateKeyboardLayout 이라는것을확인할수있다. 그리고이함수의구현 코드가있는곳인 AddressOfFunctions 의파일상의위치는 0x00016A44 0x1000 + 0x400 = 0x00015E44 이다. - 72 -
AddressOfNameOrdinals : 0x0001811C 0x1000 + 0x400 = 0x0001751C 0x1751C 의위치에서부터 NumberOfNameOrdinals 가시작된다. 모든함수들의서수를가지고있는데이서수배열의최초원소의값은항상 1 이고그리고이배열값에 Base 의값을더하면정확한서수값을획득할수있다. 현재우리가보고있는 USER32.dll 파일은총 731(0x2DB) 개의함수이름을가지고있기때문에 AddressOfNameOrdinals 또한 731(0x2DB) 개를가지고있다. 위과정을도식화하면다음과같다. - 73 -
- 74 -
Module 4 분석기초 Objectives 분석도구설정 CrackMe / KeygenMe 실습 - 75 -
4-1. 분석도구설정 분석도구설정 Debugger Ollydbg Disassembler IDA W32DASM File Analyzer Stud PE PEiD ImportREConstruction HEX Editor WinHex Student Notes 분석도구에는디버거, 디스어셈블러, 파일분석기, 헥스에디터등다양한도구들이존재한다. 도구들의 사용은분석을좀더용이하게해주기때문에자주사용이되며본인에게가장잘맞는도구를선택하는 것도중요하다. 여기서는대표적인도구들의사용법을위주로소개하고자한다. - 76 -
Ollydbg http://www.ollydbg.de/ 에서무료로배포하고있고, 바이너리분석에사용된다. File : 디버깅할파일혹은프로세스를지정합니다. View : 디버깅대상에대한각종정보를출력합니다. Debug : 디버깅에관련된기능들입니다. Plugins : 디버깅에유용한각종유틸리티를플러그인형태로제공합니다. Options : 각종옵션을변경합니다. Window, Help : 화면 ( 창 ) 설정과도움말입니다. 프로그램을 restart, close, run 시키거나실행과정을조작할수있는기능들을제공한다. 어셈코드를한줄씩실행하거나혹은특정상황이발생할때가지실행하거나할수있다. 디버깅을하는데필요한중요한정보들을보여준다. 자주사용하는것들은 E : Executable modules, 모듈정보 M : Memory map, 현재메모리정보 H : Handles, 핸들정보 C : CPU main htread, 메인디버깅화면 / : Patches, 기계어코드가변경된내용을출력 K : Call stack of main thread B : Breakpoints, 설정된브레이크포인트 R : References, 프로그램에사용된함수나문자열등을참조할때사용... : Run trace - 77 -
다음은메인화면에대해서알아보도록하자. A 부분은디스어셈블링된코드와 OP 코드들을나타낸다. 네부분으로나뉘어져있는데차례대로메모리주소, OP 코드, 디스어셈블된코드, 코멘트의내용을가지고있다. 이곳에서프로그램의흐름이나실행을제어해서분석한다. B 부분은상태바이다. A 부분에서실행되고있는각해당위치의 offset 값과변경된메모리주소, 레지스터내용들을나타낸다. C 부분은메모리의값들을헥사코드와아스키코드로보여주는부분이다. 그외다른다양한형태로값들을확인할수있다. D 부분은 CPU 레지스터들의정보를나타낸다. 레지스터값이나플래그등을직접조작할수도있다. E 부분은 Stack 의내용을보여준다. Ollydbg 를이용하여분석하기전몇가지설정을해주어야한다. - 78 -
먼저 Option Appearance 에서 udd 폴더와 plugins 폴더를지정해준다. Option Debugging Option CPU : jump 구문이있을경우어느곳으로점프하는지보여준다. - 79 -
Exception 발생시어떻게처리할것인지와범위를지정한다. 프로그램을디버거로오픈할때어느곳에서멈출것인지지정한다. - 80 -
Stud PE PE 파일의구조를분석해주는프로그램으로 http://www.cgsoftlabs.ro/studpe.html 에서다운받을수있으 며프리웨어이다. PEiD PE 파일의정보를볼수있는것은 Stud PE 와비슷하고패킹여부를확인할수있는기능도있다. http://www.peid.info/ 에서다운로드받을수있다. - 81 -
ImportREC IAT 를복구시켜주는프로그램이다. 패킹이되어있는파일을언패킹한후 IAT 를맞춰주지않으면정상 적으로실행되지않기때문에 IAT 를복구시켜주어야한다. 이때 ImportREC 을사용한다. http://vault.reversers.org/imprecdef 에서다운로드받을수있다. 하지만모든상황에서 IAT 가정상적으로복구되는것은아니다. 패커가의도적으로 IAT 를꼬이게했을경우정상적으로찾지못할수있다. 이것은임포트테이블의마지막이 0000 0000 이라는점을이용하여자동으로 IAT 를복구하는데정상적인함수들의목록중간에 0000 0000 을삽입하게되면그곳이마지막이라고 ImportREC 이인식하기때문이다. 이런경우수작업으로 IAT 의주소와사이즈를맞춰주어야한다. - 82 -
Winhex 파일을 HEX 형태로볼수있다. 바이너리파일을직접수정할수도있다. - 83 -
4-2. CrackMe 분석실습 CrackMe 실습 인증우회하기 Key 값찾기 Keyfile 만들기 등등 Student Notes 이번장에서는온라인상에공개되어있는 Crackme 예제를통해몇가지기법들에대해서익히도록하겠다. 온라인상에공개된 Crackme 프로그램들의특징을살펴보면인증을우회하거나고정된키값을찾아서입력하는방법, 또는키파일을만들어야하는방법등여러가지방법들을있다. 물론 Keygen 을만들어서입력된이름이나아이디에의해변경되는키값을알아내는방법도있는데 Keygen 에관한내용은다음장에서보도록하겠다. 여기서는먼저고정된키값을찾아내는크랙미를이용하여실습을할것이고두번째는우리가입력한 값에의해키값이변하는크랙미에대해서분석을해보도록하겠다. - 84 -
Crackme #1 이파일을실행하면다음과같이패스워드를입력하라고나오고아무런값이나입력해보도록하겠다. 이프로그램에서요구하는패스워드와일치하지않기때문에 Invalid Password 라는메시지를출력하 고프로그램이종료를하게된다. 여기서우리가확인해야할부분은정확한패스워드를입력하지않았 을때출력되는 Invalid Password 라는문구이다. 디버거를통해서프로그램을실행시켜보자. 코드창에서마우스오른쪽을클릭하여 Search for All reference text strings 를클릭하면다음과같이이 프로그램에서사용되는스트링들을확인할수있다. 앞에서보았던문구가두번째줄에보이고아래쪽에 The password is %s 라는스트링이보인다. 아마도정확한패스워드를입력할경우 The password is xxxxxxxxxx 라고콘솔에출력이될것이고그렇지않을경우 Invalid Password 라는메시지를출력하는것일거라예상할수있다. Invalid Password 라는스트링을더블클릭하면해당코드로이동하게된다. - 85 -
Invalid Password 라는메시지바로위라인에비교하는부분이있고같을경우 004010DA 로분기하는것을볼수있다. 004010DA 는다시 0040109C 로분기하기때문에아래쪽에보이는 The password is %s 부분으로이동하지는않는다. 위쪽으로조금더올라가면 JNB 에의해서분기하는코드가보이는데조건에만족하면 004010DC 로분기하여 The password is %s 라는메시지를출력하게된다. 즉, 현재보이는코드보다윗부분에서우리가입력한패스워드와프로그램이요구하는패스워드를비교하는부분이있을것이다. 그렇다면화면을조금위로올려서패스워드를입력받는부분을확인해보도록하겠다. PUSH Crackme#.00407030 은 Please enter the password: 라는출력할메시지를스택에넣어둔후바로아래에있는 CALL 에의해서화면에출력이된다. 항상 CALL 에의해서함수호출이끝나면스택을잘보아야한다. 함수내부에서어떠한연산작업후스택에데이터들이저장되기때문이다. 그리고이데이터들은다음코드에영향을미칠수있기때문이다. - 86 -
현재스택의값들을확인하고다음코드를보도록하겠다. /*40102C*/ ADD ESP,4 /*40102F*/ PUSH 10 // 스택클리어 // 스택에 10 을저장 /*401031*/ PUSH 0 // 스택에 0 을저장 /*401033*/ LEA EAX,DWORD PTR SS:[EBP-34] // EBP-34 의위치 ( 주소 ) 를 EAX 에저장 /*401036*/ PUSH EAX // EAX 의값을스택에저장 /*401037*/ CALL Crackme#.00401150 // 00401150 의코드를호출 /*40103C*/ ADD ESP,0C /*40103F*/ MOV DWORD PTR SS:[EBP-1C],0 // 스택클리어 // EBP-1C 의위치에 0 을저장 /*401046*/ MOV DWORD PTR SS:[EBP-20],0 // EBP-20 의위치에 0 을저장 /*40104D*/ MOV DWORD PTR SS:[EBP-24],0 // EBP-24 의위치에 0 을저장 위부분은우리가코드를분석하는데큰영향을미치지는않는부분들이고단지우리가입력한패스워 드와프로그램이가지고있는패스워드를비교하는연산을위해서공간을만드는역할을하는코드이다. 다음코드로진행을하면패스워드를입력할수있게된다. 임의의패스워드를입력하고다음코드를살펴보자. 00401054 에서 00401140 에위치한함수를호출한다. 이함수는사용자의입력값을받아들이는함수이 다. 그리고함수를호출하고연산을수행한결과는 EAX 에저장된다. - 87 -
Disassembly 부분에서굵은선으로표기된부분은루프문을나타내는것이다. 즉, 특정조건을만족할때 까지 00401054 부터 00401084 까지계속루프를돌게된다. 그렇다면현재위에보이는코드가어떠한역 할을하는지알아보도록하겠다. /*401059*/ MOV BYTE PTR SS:[EBP-4],AL // AL 에있는값을 EBP-4 에 1 바이트만저장 /*40105C*/ MOV ECX,DWORD PTR SS:[EBP-1C] // EBP-1C 에있는값을 ECX 에저장 /*40105F*/ MOV DL,BYTE PTR SS:[EBP-4] // EBP-4 에있는값을 DL 에저장 /*401062*/ MOV BYTE PTR SS:[EBP+ECX-34],DL // DL 에있는값을 EBP+ECX-34 에저장 /*401066*/ MOV EAX,DWORD PTR SS:[EBP-1C] // EBP-1C 에있는값을 EAX 에저장 /*401069*/ ADD EAX,1 // EAX 값을 1 증가 /*40106C*/ MOV DWORD PTR SS:[EBP-1C],EAX // EAX 에있는값을 EBP-1C 에저장 /*40106F*/ MOVSX ECX,BYTE PTR SS:[EBP-4] // EBP-4 에있는값을 ECX 에저장 /*401073*/ CMP ECX,0A // ECX와 0x0A 를비교 /*401076*/ JE SHORT Crackme#.00401086 // 같으면 00401086 으로분기 /*401078*/ MOVSX EDX,BYTE PTR SS:[EBP-4] // EBP-4 에있는값을 EDX 에저장 /*40107C*/ TEST EDX,EDX /*40107E*/ JE SHORT Crackme#.00401086 // EDX 와 EDX 를 AND 연산 // 같으면 00401086 으로분기 /*401080*/ CMP DWORD PTR SS:[EBP-1C],10 // EBP-1C 에있는값과 0x10 을비교 /*401084*/ JB SHORT Crackme#.00401054 // EBP-1C 의값이 0x10 보다적으면 00401054 로분기 - 88 -
계속해서루프를돌면서뭔가를계산하고있다. 4 번정도루프를돈후헥사값이있는부분을확인해보 도록하자. CALL 00401140 에의해우리가입력한값을한글자씩가져와서 EAX 에저장하고글자수를세고있다. 입력한글자의개수는 [EBP-1C] 에저장이되고현재까지읽어들인글자들은 [EBP+ECX-34] 에저장이된다. 그리고현재읽어들인글자는 [EBP-4] 에저장이된다. 코드에서보면 BYTE PTR SS 라는부분이자주나오는데이것이바로한글자씩 (1 바이트 ) 읽어온다는증거이다. 그리고 ECX 에는위에보는것과같이 [EBP-4] 에있는값을저장한후 0x0A 와비교하고있다. [EBP-4] 는현재읽어들인글자하나를의미하고 0x72( r ) 와 0x0A 를비교하는것이다. 0x0A 는 LF(Line Feed) 로 써사용자가입력한글자의끝을찾기위해사용된비교구문이다. 위부분은 [EBP-1C] 에있는값과 0x10 을비교하는부분인데 [EBP-1C] 에는현재까지읽어들인글자의개수를나타내므로사용자가입력한값의길이가 16 글자 (0x10) 보다작은지확인하는부분이다. 16 글자보다많은경우는 00401054 로분기하지않고루프문을빠져나오게된다. 즉, 우리가입력한글자를 16 글자까지만인식을하게된다. 루프를빠져나온후레지스터에저장된값과헥사코드정보에대한부분을확인해보도록하겠다. - 89 -
레지스터에저장된값은다음과같다. EAX 에입력한글자수 (0x0B) 가저장되어있고 ESP 에는사용자가입력한글자를가지고있는주소가 저장되어있다. 여기까지알아낸 Crackme#1 을풀기위한조건은 16 글자이하라는것과 EAX 에사용자가입력한글자 의개수를저장해두었다는것이다. 물론 EAX 에있는이값은어디론가옮겨져사용될것이다. 왜냐하 면다음루틴에서도 EAX 를써야하기때문이다. 루프문을빠져나오면다음과같은코드를볼수있다. EBP-34 의주소값을 EAX 에로드하고 EAX 에있는값을 [EBP-8] 의위치로옮긴다. 그리고 [EBP-20] 와 [EBP-24] 는각각 0 과 3 이라는값을저장한다. 아마도다음작업을위한준비단계일것이다. [EBP-34] 의주소값을확인해보니 0013FF4C 이고이주소에는사용자가입력했던값이저장되어있다. 위코드실행후다음코드는 004010AE 로분기하는코드이다. 004010AE 로이동해보도록하겠다. [EBP-20] 에있는값과 0x0D 를비교하여 [EBP-20] 의값이 0x0D 보다적지않으면 004010DC 로분기한 다. 즉, [EBP-20] 에있는값이 0x0D 보다크거나같으면분기한다는의미이다. 그런데바로앞에서 - 90 -
[EBP-20] 에 0 을저장하였기때문에분기하지않고다음코드를실행하게된다. 다음코드는 004010B4 부터시작한다. /*4010B4*/ MOV EAX,DWORD PTR SS:[EBP-20] // EBP-20 에있는값을 EAX 로저장 /*4010B7*/ SHR EAX,2 /*4010BA*/ MOV ECX,DWORD PTR SS:[EBP-8] // EAX 의값을오른쪽으로 2 바이트쉬프트 // EBP-8 에있는값을 ECX 에저장 /*4010BD*/ MOV EDX,DWORD PTR SS:[EBP-24] // EBP-24 에있는값을 EDX 에저장 /*4010C0*/ MOV EAX,DWORD PTR DS:[ECX+EAX*4] // ECX+EAX*4 에있는값을 EAX 에저장 /*4010C3*/ CMP EAX,DWORD PTR SS:[EBP+EDX*4-18] // EBP+EDX*4-18 의값과 EAX 의값비교 위코드는사용자가입력한패스워드와프로그램이가지고있는패스워드의처음 4 글자를비교하는부 분이다. 헥사코드부분을보도록하자. - 91 -
004010B4 에서부터 004010C3 까지의코드가처음실행될때 ECX 값은사용자가입력한값이저장되어있는주소를가지고있으며 EAX 는 0 을가지고있다.([EBP-20] 의값이 0 이므로 ) 그리고 EDX 는 [EBP-24] 에있는값을가지고있는데이전코드에서 3 이라는값을저장했었다. 즉, EBP+EDX*4-18 을하면 0013FF80 + 3 * 4 18 = 0x0013FF80 + 0xC 0x18 = 0x0013FF74 가된다. 즉, qwer 과 powe 를비교한다. 그리고다음코드에서같으면 004010DA 로분기하고그렇지않으면 004010C9 을실행한다. 004010C9 은 Invalid Password 라는메시지를출력하면서종료되는루틴이있는곳이다. 만약사용자가첫번째 4 글자를 powe 라고입력했다고가정하고다음코드를살펴보도록하겠다. 눈치가빠른리버서라면지금패스워드를알아보았을수도있다. 하지만좀더정확한루틴을파악하기 위해다음코드를실행할때어떻게되는지살펴보자. 첫번째 4 글자가같을경우 004010DA 로분기하 는데여기서다시 0040109C 로분기한다. /*40109C*/ MOV ECX,DWORD PTR SS:[EBP-20] // EBP-20 에있는값을 ECX 에저장 /*40109F*/ ADD ECX,4 // ECX 의값을 4 증가 /*4010A2*/ MOV DWORD PTR SS:[EBP-20],ECX // ECX 의값을 EBP-20 에저장 /*4010A5*/ MOV EDX,DWORD PTR SS:[EBP-24] // EBP-24 에있는값을 EDX 에저장 /*4010A8*/ SUB EDX,1 // EDX 에서 1 감소 /*4010AB*/ MOV DWORD PTR SS:[EBP-24],EDX // EDX 의값을 EBP-24 로저장 /*4010AE*/ CMP DWORD PTR SS:[EBP-20],0D // EBP-20 의값과 0x0D 비교 - 92 -