Windows Debugging (2) x86 stack inside 2015.03 레드스톤소프트김태형부장 http://kuaaan.tistory.com
Register 기초
3 / 60 X86 Register 특별한목적으로사용되는레지스터 EIP : 다음에실행될 Instruction의주소를 pointing EAX : 함수가리턴할때리턴값을저장. 평소에는범용으로사용됨 ESP : 현재스택의가장위 ( 마지막 push된원소 ) 를 pointing Stack에새로운원소가 PUSH될때마다 ESP 감소. POP될때마다 ESP 증가 EBP : 현재스택의가장바닥 (Base) 를 pointing 나머지레지스터. EBX, ECX, EDX, ESI, EDI 등등 ( 이런게있구나 )
4 / 8 Volatile Register.vs. Non-Volatile Register Volatile Register 다른함수를호출하고나면값이달라지는레지스터 EAX, ECX, EDX, ST0 - ST7, ES and GS Non-Volatile Register 다른함수를호출한후에도값이유지되는레지스터 EBX, EBP, ESP, EDI, ESI, CS and DS Non-Volatile Register 를다른목적으로사용하려면먼저 Stack 에원래값을백업 함수가리턴되기직전에백업받은원래값을 Non-Volatile Register 에복원 Non-Volatile Register를사용전에백업받는부분 KERNELBASE!CreateFileW+0x5e: 76d7c2f7 53 push ebx 76d7c2f8 56 push esi 76d7c2f9 8b7508 mov esi,dword ptr [ebp+8] 76d7c2fc 56 push esi 76d7c2fd 8d45d8 lea eax,[ebp-28h] 76d7c300 50 push eax 백업된 Non-Volatile Register 를복원하는부분 KERNELBASE!CreateFileW+0x48f: 76d7c728 5f pop edi KERNELBASE!CreateFileW+0x490: 76d7c729 5e pop esi 76d7c72a 5b pop ebx KERNELBASE!CreateFileW+0x492: 76d7c72b c9 leave 76d7c72c c21c00 ret 1Ch
Assembly 기초
6 / 8 Assembly 를공부해야하는이유?? 대부분은 분석 만할줄알면된다. api 안에서죽었는데 왜죽었는지모르겠을때. OS 의동작원리가궁금할때. (Windows OS이모듈이나 api 분석 ) 다른모듈을 Reversing 해야할때 ( 악성코드??) pdb가없는모듈을디버깅할때 프로그램이코드와부합하지않는동작을할때 가끔은 작성 해야할때도있다. 후킹코드작성시
7 / 8 opcode, operand opcode : assembly 의명령어 ( = instruction) operand : opcode 의연산을수행하는데필요한 Data operand 가두개인경우오른쪽 operand 가 src, 왼쪽 operand 가 dst. (disassembler 의종류에따라다를수도있다.) leave ; opcode only dec eax ; opcode + 1 operand mov dword ptr [ebp+0ch], 40000000h ; opcode + 2 operands dst operand src operand
8 / 8 dword ptr [ ] src 에사용될때는 [ ] 안에있는주소값이가리키는메모리에저장된값 을의미. (pointer indirection) dst 에사용될때는 [ ] 안에있는주소값이가리키는메모리에저장하라 는의미. dword 는포인터의사이즈를의미. (char* byte ptr[], short* word ptr[]) 디스어셈블러의종류에따라 dword ptr [] 를 [] 로표현하는경우도있으므로, [] 안의값을그대로참조할지 [] 안의주소가가리키는메모리에저장된값을참조할지여부는 dword ptr 키워드의유무가아니라 operator 의종류 에따라판단해야함. mov BYTE PTR [ebx], 2; mov WORD PTR [ebx], 2; mov DWORD PTR [ebx], 2;
9 / 8 MOV, LEA mov 우변의 값 을좌변에대입 좌변에는레지스터와메모리주소가모두가능, 우변에는레지스터 / 메모리주소 / 상수가모두가능 ( 우변 / 좌변에모두주소가올수는없음.) 우변에주소가올경우주소값대신해당주소에저장된 값 이좌변에대입됨. mov mov mov dword ptr [ebp-38h],2 ; 2라는상수값을 ebp-38h 위치에저장 eax,dword ptr [ebp+14h] ; ebp+14h에저장된 값 을 eax에저장 dword ptr [ebp-54h],esi ; esi 에들어있는값을 ebp-54h 위치에저장 lea 우변의 주소값 을좌변에대입 좌변에는레지스터만가능, 우변에는메모리주소사용가능 lea eax,[ebp-20h] ; ebp 의주소값에서 20h 뺀 주소값 을 eax 에저장
10 / 8 PUSH, POP push 주어진 operand 값을스택의꼭대기에 push 함. operand 에레지스터, 상수, 주소값이모두올수있음. operand 에주소가올경우해당주소에저장된 값 이대입됨. pop push dword ptr [ebp-8] sub esp, 4h / mov eax, dword ptr [ebp-8] / mov dword ptr [esp], eax push push push edi 0B7h dword ptr [ebp-8] 현재스택의꼭대기값을 pop 해서주어진레지스터에대입함. pop edi mov edi, dword ptr [esp] / add esp 4h pop esi pop dword ptr [ebp-8]
11 / 8 Flag 비교문 (test, cmp) 수행시결과가 Flag에임시저장됨. Conditional jump 시조건판단을위해 Flag값을참조함. test나 cmp 결과가어딘가에저장되는구나 정도로이해!! CF - carry flagset on high-order bit carry or borrow; cleared otherwise PF - parity flagset if low-order eight bits of result contain an even number of "1" bits; cleared otherwise ZF - zero flagsset if result is zero; cleared otherwise SF - sign flagset equal to high-order bit of result (0 if positive 1 if negative) OF - overflow flagset if result is too large a positive number or too small a negative number (excluding sign bit) to fit in destination operand; cleared otherwise
12 / 8 CMP, TEST cmp 두 operand를비교하기위해오른쪽 operand에서왼쪽 operand를뺀다. 결과는 ZF, CF 등에저장됨. je, jle 등의 opcode와함께사용됨 cmp jbe word ptr [ebp-28h],ax KERNELBASE!CreateFileW+0x8e (76d7c327) test 두 operand 를비교하기위해두 operand 의 AND 연산을수행. ( 결과는 ZF 등에저장 ) 분기문에서는주로 0 인지아닌지를비교하기위해사용. test je eax,eax KERNELBASE!CreateFileW+0x237 (76d7c4d0) je 와 jz 은동일한 opcode!! (jz 로해석!!)
13 / 8 조건 jump JE 와 JZ, JNE 와 JNZ 는동일한 OpCode 임 JA 는 unsigned, JG 는 signed 비교 JB 는 unsigned, JL 는 signed 비교 http://unixwiz.net/techtips/x86- jumps.html
14 / 8 if 문패턴분석 if 문의 assembly 패턴!! 1. cmp / test 2. 조건부 jmp 학습용샘플빌드시에는 Optimize 옵션을 Disable 로설정 (Default : Maximize Speed)
15 / 8 for 문패턴분석 push mov push mov jmp ebp ebp,esp ecx dword ptr [ebp-4],1 SimpleCall!GuGuDanFor+0x16 (012e1096) nindex = 1 for 문의 assembly 패턴!! 1. 최초실행시한블럭건너뜀 2. 건너뛰었던블록으로되돌아옴 (jmp) 3. 되돌아온위치에 add 4. add 다음에 cmp/test + jmp 거슬러올라오는 jmp 문은반복문의가장큰특징 SimpleCall!GuGuDanFor+0xd mov eax,dword ptr [ebp-4] add eax,1 mov dword ptr [ebp-4],eax nindex++ SimpleCall!GuGuDanFor+0x16 cmp dword ptr [ebp-4],9 jg SimpleCall!GuGuDanFor+0x3c (012e10bc) nindex <= 9 가아니면 SimpleCall!GuGuDanFor+0x1c mov ecx,dword ptr [ebp+8] imul ecx,dword ptr [ebp-4] push ecx mov edx,dword ptr [ebp-4] push edx mov eax,dword ptr [ebp+8] push eax push offset SimpleCall!GS_ExceptionPointers+0x40 (012e212c) call dword ptr [SimpleCall!_imp wprintf (012e20a0)] add esp,10h jmp SimpleCall!GuGuDanFor+0xd (012e108d) SimpleCall!GuGuDanFor+0x3c mov esp,ebp pop ebp ret
16 / 8 for 문패턴분석 add 문 cmp/test + 조건부 jmp 문 1 최초 1 회는조건 Check 를 Skip!! 2 거슬러되돌아오는흐름 ( 도착지점근처에 loop index 값 add 하는코드 ) Do Something 3 탈출조건 jmp
17 / 8 while 문패턴분석 SimpleCall!GuGuDanWhile push ebp mov ebp,esp push ecx mov dword ptr [ebp-4],1 SimpleCall!GuGuDanWhile+0xb cmp dword ptr [ebp-4],9 jg SimpleCall!GuGuDanWhile+0x3a (012e103a) while 문의 assembly 패턴!! 1. 반복문시작부분으로되돌아옴 (jmp) 2. 되돌아온위치근처에탈출문 SimpleCall!GuGuDanWhile+0x11 mov eax,dword ptr [ebp+8] imul eax,dword ptr [ebp-4] push eax mov ecx,dword ptr [ebp-4] push ecx mov edx,dword ptr [ebp+8] push edx push offset SimpleCall!GS_ExceptionPointers+0x8 (012e20f4) call dword ptr [SimpleCall!_imp wprintf (012e20a0)] add esp,10h mov eax,dword ptr [ebp-4] add eax,1 mov dword ptr [ebp-4],eax jmp SimpleCall!GuGuDanWhile+0xb (012e100b) SimpleCall!GuGuDanWhile+0x3a mov esp,ebp pop ebp ret
18 / 8 do~while 문패턴분석 do~while 문의 assembly 패턴!! 1. 반복문시작부분으로되돌아옴 ( 조건부 jmp) 2. 탈출문은없을수도 분석목적이라면굳이반복문의종류를구분할필요는없음. SimpleCall!GuGuDanDoWhile push ebp mov ebp,esp push ecx mov dword ptr [ebp-4],1 SimpleCall!GuGuDanDoWhile+0xb mov eax,dword ptr [ebp+8] imul eax,dword ptr [ebp-4] push eax mov ecx,dword ptr [ebp-4] push ecx mov edx,dword ptr [ebp+8] push edx push offset SimpleCall!GS_ExceptionPointers+0x24 (012e2110) call dword ptr [SimpleCall!_imp wprintf (012e20a0)] add esp,10h mov eax,dword ptr [ebp-4] add eax,1 mov dword ptr [ebp-4],eax cmp jle dword ptr [ebp-4],9 SimpleCall!GuGuDanDoWhile+0xb (012e104b) SimpleCall!GuGuDanDoWhile+0x38 mov esp,ebp pop ebp ret
19 / 8 구조체사용코드패턴분석 구조체의멤버에접근하는예제 0:000> uf simplecall!getsum push ebp mov ebp,esp mov eax,dword ptr [ebp+8] mov eax,dword ptr [eax+4] pop ebp ret 구조체의첫번째멤버에접근하는예제 0:000> uf SimpleCall!GetSum push ebp mov ebp,esp mov eax,dword ptr [ebp+8] mov eax,dword ptr [eax] pop ebp ret 구조체사용시의 assembly 패턴!! 1. 레지스터에구조체의주소를저장 2. 저장된구조체주소에서접근할멤버의 Offset 만큼이동
calling convention
21 / 8 calling convention? 함수가호출되는규약 호출할때파라메터를전달하는방법, 함수리턴후파라메터를정리하는방법이 calling convention 에의해결정됨.
22 / 8 cdecl 파라메터전달 : 오른쪽파라메터 > 왼쪽파라메터순서로 stack에 push 파라메터정리 : 호출한측 (caller) 에서!! CRT 함수들의호출규약
23 / 8 stdcall 파라메터전달 : 오른쪽파라메터 > 왼쪽파라메터순서로 stack에 push 파라메터정리 : 호출된측 (callee) 에서!! Windows api들의호출규약 CALLBACK, APIENTRY 등으로 #define되어사용되기도함.
24 / 8 thiscall 기본적으로 stdcall과동일 Class member 함수호출시사용되는호출규약 this 포인터를 ecx 레지스터를통해전달
25 / 8 C++ 클래스가상함수호출 클래스인스턴스의시작부분에가상함수테이블의포인터가저장됨 함수호출시런타임에가상함수테이블에서함수주소를구하여 call pkid Vtable_Ptr Hello() 의주소 m_int1 Fine() 의주소 m_int2
stack 기초
27 / 8 Stack 에서알수있는정보들 CallStack (ret + 이전 ebp) Local Variables Stack Non-volatile Retister Exception Frame Security Cookie Parameters Detect Anomaly
28 / 8 Stack 은거꾸로자란다!! stack 은항상높은주소 > 낮은주소방향으로자란다. (Intel cpu 기준 ) 새로운변수가할당될때 함수가호출되어새로운스택프레임이생성될때 하지만스택에서사용되는변수들은낮은주소 > 높은주소방향으로 0:000> k ChildEBP RetAddr 0025e6b4 76d7c5f7 ntdll!ntcreatefile 0025e758 76d7268f KERNELBASE!CreateFileW+0x35e 0025e92c 76d729ec KERNELBASE!BasepLoadLibraryAsDataFileInternal+0x288 0025e94c 76d72c3b KERNELBASE!BasepLoadLibraryAsDataFile+0x19 0025e988 756aeef1 KERNELBASE!LoadLibraryExW+0x18a 스택이자라는방향
29 / 8 함수가호출되는과정에서스택의변화? High Address [ 샘플코드 : SimpleCall.exe]
30 / 8 함수가호출되는과정 (1) parameter push int _tmain(int argc, _TCHAR* argv[]) { INT nproduct = 0; INT nfrom = 3; INT nto = 9; sub esp,0ch mov dword ptr [ebp-8],0 // nproduct mov dword ptr [ebp-0ch],3 // nfrom mov dword ptr [ebp-4],9 // nto High Address nproduct = GuGuDan(nFrom, nto, NULL); push 0 // NULL mov eax,dword ptr [ebp-4] // nto push eax mov ecx,dword ptr [ebp-0ch] // nfrom push call ecx SimpleCall!GuGuDan (013d1000) 함수의 prolog 이후에 push 가나오면 call 을위한준비단계!!
31 / 8 함수가호출되는과정 (2) 함수호출 (call) call instruction 은다음의두 instruction 으로풀어쓸수있다. push eip ( return address) jmp <function address> High Address nproduct = GuGuDan(nFrom, nto, NULL); push 0 // NULL mov eax,dword ptr [ebp-4] // nto push eax mov ecx,dword ptr [ebp-0ch] // nfrom push call ecx SimpleCall!GuGuDan (013d1000)
32 / 8 함수가호출되는과정 (3) 새로운 stack frame 생성 새로운함수는 ( 대부분 ) 다음과같이시작한다 공식!! mov edi, edi push ebp mov ebp, esp <push ebp 후 > 빌드시 /hotpatch 옵션추가하면 mov edi, edi 가포함됨 (5bytes align 을위한 padding!!) <mov ebp, esp 후 >
33 / 8 함수가호출되는과정 (4) 로컬변수생성, non-vol register 백업 로컬변수를선언하기위해 esp를필요한메모리사이즈만큼감소시킴 Non-volatile register를사용하기위해 stack에백업 (if required) INT _cdecl GuGuDan(IN INT ndanfrom, IN INT ndanto, OPTIONAL OUT INT * ptotalproduct) { INT ndanindex = 0; INT nindex = 0; INT nproduct = 0; push ebp mov ebp,esp sub esp,0ch mov dword ptr [ebp-0ch],0 mov dword ptr [ebp-8],0 mov dword ptr [ebp-4],0 로컬변수가 INT 3 개이므로 esp 를 4 * 3 = 12 바이트만큼감소시킴.
34 / 8 함수가호출되는과정 (5) Do Something 이미지출처 : https://polyrouse.wordpress.com
35 / 8 함수가호출되는과정 (6) Stack 정리 & non-vol register 복원 리턴값을 eax 에저장 (if any) 로컬변수용으로할당한메모리를회수하기위해 esp 를다시증가시킴. 백업받은 non-volatile register 복원 (if any) 함수 prologue 에서백업한 ebp 레지스터복원 mov mov pop ret eax,dword ptr [ebp-4] esp,ebp ebp mov esp, ebp 직후의 stack pop ebp 직후의 stack
36 / 8 함수가호출되는과정 (6) 함수리턴 (ret) ret instruction 은다음과같이풀어쓸수있다. pop ecx jmp ecx ret instruction 이실행되면현재 stack 의최상단에들어있는주소값으로 jmp 한다. 따라서, ret 이실행되는시점에서 stack 의최상단에는 Return Address 가들어있어야함. 상기샘플에서 ecx 는임의의 register 를표현함. mov mov pop ret eax,dword ptr [ebp-4] esp,ebp ebp
37 / 8 함수가호출되는과정 (6) parameter 정리 _cdecl 호출이므로 call 한측에서 parameter 가 push 된 stack 을정리. (_stdcall 이면??) int _tmain(int argc, _TCHAR* argv[]) { INT nproduct = 0; INT nfrom = 3; INT nto = 9; nproduct = GuGuDan(nFrom, nto, NULL); call add SimpleCall!GuGuDan (013d1000) esp,0ch
38 / 8 To see is to believe!!
39 / 8 [ 공식 ] 로컬변수 / parameter 를참조하는방법 ebp+c : 2 nd parameter ebp+8 : 1 st parameter ebp+4 : Return Address ebp : 이전함수의 ebp. ebp-xx : local variable 요약하자면.. ebp + xx : parameter ebp + 4 : RET ebp : 이전함수의 ebp ebp xx : local variable
40 / 8 ebp 는 callstack 이라는 linked list 의포인터!! 각 ebp 가가리키는위치에는이전함수의 ebp 가저장 각 ebp+4 위치에는이전함수로돌아갈 Return Address 가저장됨 ebp 가가리키는곳의값을계속따라가면이전함수의 return address chain 을구성해낼수있음.
41 / 8 Windbg 로 Stack 디벼보기 (1) 0:000>!teb TEB at 7efdd000 ExceptionList: 0030fc74 StackBase: 00310000 StackLimit: 0030d000 SubSystemTib: 00000000 FiberData: 00001e00 ArbitraryUserPointer: 00000000 Self: 7efdd000 EnvironmentPointer: 00000000 ClientId: 00001ac0. 00000ad0 RpcHandle: 00000000 Tls Storage: 7efdd02c PEB Address: 7efde000 LastErrorValue: 0 LastStatusValue: 0 Count Owned Locks: 0 HardErrorMode: 0 0:000> r eax=0000000a ebx=00000000 ecx=00000000 edx=0015df98 esi=00000001 edi=00353370 eip=00351078 esp=0030fc14 ebp=0030fc20 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 SimpleCall!GuGuDan+0x78: 00351078 ebc3 jmp SimpleCall!GuGuDan+0x3d (0035103d) 0:000> dps esp 00310000 0030fc14 00000003 0030fc18 00000001 0030fc1c 00000000 0030fc20 0030fc40 0030fc24 003510ca SimpleCall!wmain+0x2a 0030fc28 00000003 0030fc2c 00000009 0030fc30 00000000 < 이하생략 >
42 / 8 Windbg 로 Stack 디벼보기 (2) teb 가확인되지않을때는 충분히넓은범위의값을다찍어본다. 0:000> r ebp ebp=0030fc20 0:000> dps esp esp+1000 0030fc14 00000003 0030fc18 00000001 0030fc1c 00000000 0030fc20 0030fc40 0030fc24 003510ca SimpleCall!wmain+0x2a 0030fc28 00000003 0030fc2c 00000009 0030fc30 00000000 0030fc34 00000003 0030fc38 00000000 0030fc3c 00000009 0030fc40 0030fc84 0030fc44 00351264 SimpleCall! tmaincrtstartup+0x122 0030fc48 00000001 0030fc4c 00201258 0030fc50 00203d50 0030fc80 00000000 0030fc84 0030fc90 0030fc88 7723338a kernel32!basethreadinitthunk+0xe 0030fc8c 7efde000 0030fc90 0030fcd0 0030fc94 77c19f72 ntdll! RtlUserThreadStart+0x70 0030fc98 7efde000 0:000> kbn # ChildEBP RetAddr Args to Child 00 0030fc20 003510ca 00000003 00000009 00000000 SimpleCall!GuGuDan+0x78 01 0030fc40 00351264 00000001 00201258 00203d50 SimpleCall!wmain+0x2a 02 0030fc84 7723338a 7efde000 0030fcd0 77c19f72 SimpleCall! tmaincrtstartup+0x122 03 0030fc90 77c19f72 7efde000 71e4aa41 00000000 kernel32!basethreadinitthunk+0xe 04 0030fcd0 77c19f45 00351385 7efde000 00000000 ntdll! RtlUserThreadStart+0x70 05 0030fce8 00000000 00351385 7efde000 00000000 ntdll!_rtluserthreadstart+0x1b child 의 ebp child 의 RET wmain 의스택 (wmain = tmaincrtstartup 의 child) tmaincrtstartup 의스택 Child 의의미?? child 에전달된아규먼트 child 의이름 ( 심볼 )
43 / 8 로컬변수실종사건!! 로컬변수가항상스택에선언되는것은아니며, 최적화시 Register가 잠시 사용되는경우가많음최적화과정에서함수인라인화, 파편화등다양한변화가발생함. (Default : Maximize Speed) 0:000> uf simplecall!wmain push ebp mov ebp,esp sub esp,0ch mov dword ptr [ebp-8],0 mov dword ptr [ebp-0ch],3 mov dword ptr [ebp-4],9 push 0 mov eax,dword ptr [ebp-4] push eax mov ecx,dword ptr [ebp-0ch] push ecx call SimpleCall!GuGuDan (00351000) add esp,0ch mov dword ptr [ebp-8],eax mov edx,dword ptr [ebp-8] push edx push offset SimpleCall!GS_ExceptionPointers+0x28 (00352114) call dword ptr [SimpleCall!_imp wprintf (003520a0)] add esp,8 xor eax,eax mov esp,ebp pop ebp ret 0:000> dv // wmain에서로컬변수프린트 argc = 0n1 argv = 0x00201258 nfrom = 0n3 nproduct = 0n0 nto = 0n9 최적화 : Maximize Speed!! 0:000> uf simplecall!wmain call SimpleCall!GuGuDan (000b1000) // 파라메터가없다??? push eax push offset SimpleCall!`string' (000b2114) call dword ptr [SimpleCall!_imp wprintf (000b20a0)] add esp,8 xor eax,eax ret 0:000> dv // wmain 에서로컬변수프린트 로컬변수가어디갔지?? argc = 0n594520 argv = 0x00093d50 0:000> uf simplecall!gugudan SimpleCall!GuGuDan push ebp mov ebp,esp push ecx push ebx push esi push edi mov dword ptr [ebp-4],0 mov edi,3 // 앗이것은 하.. 하드코딩!!?? SimpleCall!GuGuDan+0x13 mov esi,1 mov ebx,edi lea ebx,[ebx] < 이하생략 >
44 / 8 육안으로 callstack 을재구성해보자!! (1) dps 결과에서 ebp, ret 찾기 0030fc20 0030fc40 0030fc24 003510ca SimpleCall!wmain+0x2a 2 추정 RET 값이들어있는스택위치 (0030fc24) 에서 -4 바이트한주소 (0030fc20) 를 ebp 라고, 그주소에저장된값 (0030fc40) 을이전함수의 ebp 라고가정 1 RET 로보이는값 (003510ca) 을찾는다. ( 모듈 / 함수영역의심볼에매치되는값 ) 3 찾아낸 ebp 값 (0030fc40) 을추적하여그이전 ebp 를찾아냄. (0030fc84) 0030fc40 0030fc84 4 이전 ebp 로추정되는값 (0030fc84) 이진짜 ebp 인지검증 +4 바이트한위치의값 (00351264) 가 RET 처럼보이는지? 그위치에저장된값 (0030fc84) 이현재위치의근처값인가?? 0030fc44 00351264 SimpleCall! tmaincrtstartup+0x122 5 추정되는 ebp 값이가리키는곳에저장된값 (0030fc84) 에대해앞서수행했던검증 (1~4) 을반복한다. 6 확인되는 RET 값들을연결하여 callstack 을만들어낸다.
45 / 8 육안으로 callstack 을재구성해보자!! (2) 확인된 ret 값들을연결해서 callstack 을만들어낸다. // 수동으로재구성한 callstack 0045f840 00d410ca SimpleCall!wmain+0x2a 0045f860 00d41264 SimpleCall! tmaincrtstartup+0x122 0045f8a4 7723338a kernel32!basethreadinitthunk+0x12 0045f8b0 77c19f72 ntdll!rtlinitializeexceptionchain+0x63 0045f8f0 77c19f45 ntdll!rtlinitializeexceptionchain+0x36 // windbg 가자동으로만들어주는 callstack 과비교해보자!! 0:000> k ChildEBP RetAddr 0045f83c 00d410ca SimpleCall!GuGuDan+0x4c 0045f85c 00d41264 SimpleCall!wmain+0x2a 0045f8a0 7723338a SimpleCall! tmaincrtstartup+0x122 WARNING: Stack unwind information not available. Following frames may be wrong. 0045f8ac 77c19f72 kernel32!basethreadinitthunk+0x12 0045f8ec 77c19f45 ntdll!rtlinitializeexceptionchain+0x63 0045f904 00000000 ntdll!rtlinitializeexceptionchain+0x36
46 / 8 육안으로 callstack 을재구성해보자!! (3) 주의 : 저렇게 (097ffda8) 생긴애는 ebp 가아님. ( 저건예외프레임 ) See also : http://www.microsoft.com/msj/0197/exception/exception.aspx 0:052> dps esp esp+100 097ffda8 097ffe24 097ffdac 61790ee0 tcpsvc!_except_handler4 097ffdb0 57055c30 097ffdb4 fffffffe EXCEPTION_REGISTRATION 구조체 typedef struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION* prev; DWORD handler; } EXCEPTION_REGISTRATION, *PEXCEPTION_REGISTRATION; 나 ebp! 난 exception frame!
[ 실습 1] Assembly 분석
48 / 8 [ 실습 1] 도전!! GuGuDan() 을 Decompile 해보자!! (1) 0:000> uf simplecall!gugudan SimpleCall!GuGuDan 8 01211000 55 push ebp 8 01211001 8bec mov ebp,esp 8 01211003 83ec0c sub esp,0ch 9 01211006 c745f400000000 mov dword ptr [ebp-0ch],0 10 0121100d c745f800000000 mov dword ptr [ebp-8],0 11 01211014 c745fc00000000 mov dword ptr [ebp-4],0 13 0121101b 8b4508 mov eax,dword ptr [ebp+8] 13 0121101e 8945f4 mov dword ptr [ebp-0ch],eax 13 01211021 eb09 jmp SimpleCall!GuGuDan+0x2c (0121102c) SimpleCall!GuGuDan+0x23 13 01211023 8b4df4 mov ecx,dword ptr [ebp-0ch] 13 01211026 83c101 add ecx,1 13 01211029 894df4 mov dword ptr [ebp-0ch],ecx Assembly C Source [ 요령 ] Step by Stap!! 1. windbg에서 uf 커맨드로 assembly code 확인 2. ebp-xx, ebp+xx 를변수명으로 Replace!! 3. Label과 goto 문을활용해 초벌번역 4. 화살표를그려서코드블럭간의흐름을파악 거슬러올라가는화살표에주목!!! 5. 초벌번역된 C 소스를보기좋게 Decoration
49 / 8 Api 호출에서힌트를얻자!! 프로토타입이알려진 WinApi 호출부분을분석하면로컬변수의의미, 구조체등에대한정보를얻을수있음. KERNELBASE!GetFileAttributesW: mov edi,edi push ebp mov ebp,esp sub esp,48h push esi xor esi,esi push esi push esi [ebp-8] = PUNICODE_STRING NtName lea push push call eax,[ebp-8] eax dword ptr [ebp+8] dword ptr [KERNELBASE!_imp RtlDosPathNameToNtPathName_U_WithStatus] KERNELBASE!GetFileAttributesW+0x41: mov edi,dword ptr [ebp-4] mov dword ptr [ebp-18h],eax lea eax,[ebp-48h] push eax lea eax,[ebp-20h] push eax mov dword ptr [ebp-20h],18h mov dword ptr [ebp-1ch],esi mov dword ptr [ebp-14h],40h mov dword ptr [ebp-10h],esi mov dword ptr [ebp-0ch],esi call dword ptr [KERNELBASE!_imp NtQueryAttributesFile] [ebp-48h] = FileInformation [ebp-20h] = ObjectAttributes [ebp-20h] = ObjectAttributes->Length [ebp-1ch] = ObjectAttributes->RootDirectory [ebp-18h] = ObjectAttributes->ObjectName [ebp-14h] = ObjectAttributes->Attributes
50 / 8 [ 실습 1] 도전!! GuGuDan() 을 Decompile 해보자!! (2) SimpleCall!GuGuDan push ebp mov ebp,esp sub esp,0ch mov dword ptr [ebp-0ch],0 mov dword ptr [ebp-8],0 mov dword ptr [ebp-4],0 mov eax,dword ptr [ebp+8] mov dword ptr [ebp-0ch],eax jmp SimpleCall!GuGuDan+0x2c (0121102c) SimpleCall!GuGuDan+0x23 mov ecx,dword ptr [ebp-0ch] add ecx,1 mov dword ptr [ebp-0ch],ecx SimpleCall!GuGuDan push ebp mov ebp,esp sub esp,0ch mov dword ptr [locvar_a],0 mov dword ptr [locvar_b],0 mov dword ptr [locvar_c],0 mov eax,dword ptr [arg_a] mov dword ptr [locvar_a],eax jmp SimpleCall!GuGuDan+0x2c (0121102c) SimpleCall!GuGuDan+0x23 mov ecx,dword ptr [locvar_a] add ecx,1 mov dword ptr [locvar_a],ecx 1 windbg 에서 uf 커맨드로 assembly code 확인 GuGuDan(arg_A, arg_b, arg_c) { locvar_a = arg_a; goto GuGuDan+0x2c; GuGuDan+0x23: locvar_a++; GuGuDan+0x2c: if (locvar_a > arg_b) { goto GuGuDan+0x8a; } 2 ebp-xx, ebp+xx 를변수명으로 Replace!! GuGuDan(arg_A, arg_b, arg_c) { locvar_a = arg_a; for (locvar_a = arg_a; locvar_a <= arg_b; locvar_a++) { for (locvar_b = 1; locvar_b <= 9; locvar_b++) { wprintf(l %d * %d = %d\n, locvar_a, locvar_b, locvar_a * locvar_b); locvar_c = locvar_a * locvar_b * locvar_c; } GuGuDan+0x34: locvar_b = 1; goto GuGuDan+0x46; 3 Label 과 goto 문을활용해 초벌번역 } wprintf( \n ); 4 초벌번역된 C 소스를보기좋게 Decoration
51 / 8 하지만 최적화가적용된다면? 하지만최적화가적용되면어떨까? 최 적 화!
52 / 8 Profile-Guided Optimizations 실제실행결과를분석하여코드를재배치 Cache Hit 율을높이는효과. 코드블록의배치가뒤죽박죽 거슬러올라오는 jmp 문은순환문? 과연그럴까?? OS 모듈에기본적용 cmp jne dword ptr [bresult],1 SampleCode+20h cmp jne dword ptr [bresult],1 SampleCode+20h push call add jmp L"TRUE!!\n" wprintf esp,4 SampleCode+2Dh push call add L"FALSE!!\n" wprintf esp,4 push call add L"FALSE!!\n" wprintf esp,4 push call add L"After if block!!\n wprintf esp,4 push call add L"After if block!!\n wprintf esp,4 최적화없음 프로파일기반최적화 push call add jmp L"TRUE!!\n" wprintf esp,4 SampleCode+2Dh
53 / 8 궁극의디컴파일러 IDA-Pro!!
[ 실습 2] Stack 분석응용 (1)
55 / 8 Stack 분석결과를메모리덤프분석에활용하자!! Stack 을수동분석해야하는경우?? 최적화로인해 stack 이자동분석되지않는경우 모듈이 unload 되어 crash 가발생하면서 stack 이분석되지않는경우 Buffer Overflow 가발생하여 Return Address 가변조된경우 pdb 가없어서 callstack 이제대로분석되지않는경우 자동분석된결과가의심스러운경우 1) 콜스택의시작함수가일반적인스레드시작 / 초기화함수가아닌경우 2) 함수의호출순서가상식적으로이해되지않는경우 3) 콜스택의중간부분이비어있는경우 ( 부정확할수있다는경고메시지출력 ) 4) 콜스택이끊겨있는경우 Func1 Func2???? Call Call Call Call X 이전 frame 추적 이전 frame 추적실패!!??
56 / 8 삽푸고있는 Windbg 에게힌트를주자!! WinDbg 는현재 Context 를기준으로 Callstack 을만들어냄. WinDbg 가자동분석에실패했을때수동으로 Context 를지정하면더나은 Callstack 을만들어낼수 ( 도 ) 있음!! 한번에전체 callstack 을만들어낼수없는경우도많음. 경우에따라서는 2~3 번나누어만들어낸 stack 을이어붙여서해석하라. [step 1] dps 결과를분석하여 ebp 값을추정 [step 2] 찾아낸 ebp 값을힌트를지정하여 callstack 재구성. 만족스러운 callstack 이나올때까지 [step1] ~ [step2] 를반복 [step 3] 확인된 ebp 값으로 esp, eip 값을추정 [step 4].frame 명령에 ebp, esp, eip 값을현재 Context 로지정. (.frame 에힌트로제공 ) [step 5] stack 프레임을이동, 로컬변수를확인하는등디버깅계속진행
57 / 8 [ 실습 2] [dump2] 데드락원인분석 ( 심화 ) 전형적인데드락덤프 (part 1 교육자료참조 )!locks 결과에따르면 36 번스레드는 Loarder Lock 을소유하고있지만, callstack 상으로는모듈 Load/Unload 혹은 Thread 시작 / 중지등 Loader Lock 을소유한정황이없다. 자동분석된 callstck 의일부가깨져보이며, 분석된 callstack 이스레드시작 / 초기화관련함수에서시작하지않는다. 자동분석결과를믿을수없음!!! 미션 : LoaderLock 이잠기게된이유를확인하라!! Special thanks to 김지훈 at http://www.slideshare.net/devgrapher/windbg-28727619# 0:036>!locks CritSec ntdll!ldrploaderlock+0 at 77c77340 WaiterWoken No LockCount 13 RecursionCount 1 OwningThread 13b4 EntryCount 0 ContentionCount 9d *** Locked 0:036> k ChildEBP RetAddr 07f5d76c 77be6a64 ntdll!kifastsystemcallret 07f5d770 77bd2278 ntdll!ntwaitforsingleobject+0xc 07f5d7d4 77bd215c ntdll!rtlpwaitoncriticalsection+0x13e 07f5d7fc 777bce18 ntdll!rtlentercriticalsection+0x150 07f5d848 777bd130 user32!bitmapfromdib+0x1f 07f5d8ac 777bd6db user32!convertdibbitmap+0x12b 07f5db74 777bccbf user32!convertdibicon+0x112 07f5dbc8 777bcdef user32!objectfromdibresource+0xc8 07f5de20 777bf15d user32!loadicocur+0x197 07f5de40 0aec14f2 user32!loadiconw+0x1b WARNING: Stack unwind information not available. Following frames may be wrong. 07f5de6c 77bfa15b ShellStreams!DllUnregisterServer+0x22c72 07f5de80 ffffffff ntdll!rtlinitializecriticalsection+0x12 07f5deb8 77655452 0xffffffff 07f5dec8 00000000 kernel32!deactivateactctx+0x31
58 / 8 [ 실습 2][step 1~2] ebp 값을확인 callstack 테스트 ebp 추정 : 앞서했던대로하면됨 0:036> ~~[13b4]s < > ntdll!kifastsystemcallret: 77be70f4 c3 ret 0:036> dps esp esp+1000 07f5d770 77be6a64 ntdll!ntwaitforsingleobject+0xc 07f5d774 77bd2278 ntdll!rtlpwaitoncriticalsection+0x13e 07f5d778 000020cc 07f5d77c 00000000 07f5d780 00000000 < 중간생략 > 07f5de7c 07f5df08 07f5de80 0aee148b ShellStreams!DllUnregisterServer+0x42c0b 07f5de84 ffffffff < 중간생략 > 07f5def4 0ae90000 ShellStreams 07f5def8 6fff0e30 msvcr80!operator new+0x1d 07f5defc 00000068 07f5df00 00000004 07f5df04 cd8dafae 07f5df08 07f5df20 07f5df0c 0aee1534 ShellStreams!DllUnregisterServer+0x42cb4 07f5df10 0000000b 07f5df14 0ae92acb ShellStreams+0x2acb 07f5df18 cd8dae12 07f5df1c 14377dd8 07f5df20 07f5df84 07f5df24 0aedd11b ShellStreams!DllUnregisterServer+0x3e89b 07f5df28 00000000 Trial and Error 0:036> k = 07f5df08 ChildEBP RetAddr 07f5d76c 77be6a64 ntdll!kifastsystemcallret 07f5d770 77bd2278 ntdll!ntwaitforsingleobject+0xc 07f5df08 0aee1534 ntdll!rtlpwaitoncriticalsection+0x13e WARNING: Stack unwind information not available. Following frames may be wrong. 07f5df20 0aedd11b ShellStreams!DllUnregisterServer+0x42cb4 07f5df94 0aedc96e ShellStreams!DllUnregisterServer+0x3e89b 07f5e0b0 77c0052e ShellStreams!DllUnregisterServer+0x3e0ee 07f5e20c 77bbe114 ntdll!ldrploaddll+0x4d1 07f5f968 77bd0846 ntdll!_load_config_used+0x6c 07f5fab8 77bbe114 ntdll!tppworkerthread+0x572 07f5fb14 77c037be ntdll!_load_config_used+0x6c 07f5fb20 00000000 ntdll!_rtluserthreadstart+0x1b 0:036> ub 77c0052e // 임의의 RET 에대해 callstack 검증!! ntdll!ldrploaddll+0x49a: 77c00506 0f8410f7ffff je ntdll!ldrploaddll+0x557 (77bffc1c) 77c0050c 803d7070c77700 cmp byte ptr [ntdll!ldrpldrdatabaseissetup (77c77070)],0 77c00513 0f8403f7ffff je ntdll!ldrploaddll+0x557 (77bffc1c) 77c00519 393d0c70c777 cmp dword ptr [ntdll!g_shimsenabled (77c7700c)],edi 77c0051f 0f8524fafbff jne ntdll!ldrploaddll+0x4b5 (77bbff49) 77c00525 895dfc mov dword ptr [ebp-4],ebx 77c00528 57 push edi 77c00529 e8f61e0000 call ntdll!ldrpruninitializeroutines (77c02424) // RET 직전인스트럭션이다음프레임에대한 call인지확인!!
59 / 8 [ 실습 2][step 3] 선택된 ebp 를기반으로 esp, eip 유추 현재의스택프레임이선택된 ebp 값이었을때 esp, eip 에어떤값이들어있을지를상상해보라!! 해당함수의스택 0:036> dps esp esp+1000 07f5e784 76086494 ole32!gcomprocessactivator 07f5e21c 07f5e250 07f5e220 77c02322 ntdll!ldrloaddll+0x92 07f5e224 07f5e27c 07f5e248 07520750 07f5e24c 110c87ac 07f5e250 07f5e28c 07f5e254 75c98c19 KERNELBASE!LoadLibraryExW+0x1d3 07f5e258 110c87ac 2. 해당함수의 Return Address 를 eip 로선정 3. 해당함수스택내임의의주소값을 esp 로정함. 1. 이게선정된 ebp 라면
60 / 8 [ 실습 2][step 4] 선택된 ebp,esp,eip 를현재 Context 에 Set 선택한 ebp, esp, eip 값을.frame 에힌트로제공 0:036>.frame /c = 07f5e250 07f5e24c 77c02322... ntdll!ldrloaddll+0x92: 77c02322 8bf0 mov esi,eax 0:036> r Last set context: eax=0d2d735c ebx=07f5ea70 ecx=00000003 edx=00000000 esi=778192e0 edi=00000000 eip=77c02322 esp=07f5e24c ebp=07f5e250 iopl=0 ntdll!ldrloaddll+0x92: 77c02322 8bf0 mov esi,eax
61 / 8 [ 실습 2][step 5] set 된 Context 에서디버깅진행 stack frame 을이동하거나로컬변수를확인하는등등 0:036> kn # ChildEBP RetAddr 00 07f5e250 75c98c19 ntdll!ldrloaddll+0x92 01 07f5e28c 75f69d43 KERNELBASE!LoadLibraryExW+0x1d3 02 07f5e2a8 75f69cc7 ole32!loadlibrarywithlogging+0x16 03 07f5e2cc 75f69bb6 ole32!cclasscache::cdllpathentry::loaddll+0xa9 04 07f5e2fc 75f690be ole32!cclasscache::cdllpathentry::create_rl+0x37 05 07f5e548 75f68f93 ole32!cclasscache::cclassentry::createdllclassentry_rl+0xd4 06 07f5e590 75f68e99 ole32!cclasscache::getclassobjectactivator+0x224 07 07f5e5c8 75f68c57 ole32!cclasscache::getclassobject+0x30 08 07f5e644 75f83170 ole32!cservercontextactivator::createinstance+0x110 09 07f5e684 75f68dca ole32!activationpropertiesin::delegatecreateinstance+0x108 0a 07f5e6d8 75f68d3f ole32!capartmentactivator::createinstance+0x112 0b 07f5e6f8 75f68ac2 ole32!cprocessactivator::ccicallback+0x6d 0c 07f5e718 75f68a73 ole32!cprocessactivator::attemptactivation+0x2c 0d 07f5e754 75f68e2d ole32!cprocessactivator::activatebycontext+0x4f 0e 07f5e77c 75f83170 ole32!cprocessactivator::createinstance+0x49 0f 07f5e7bc 75f82ef4 ole32!activationpropertiesin::delegatecreateinstance+0x108... 27 07f5f968 77bd0846 ntdll!rtlptpworkcallback+0x11d 28 07f5fac8 7765ed6c ntdll!tppworkerthread+0x572 29 07f5fad4 77c037eb kernel32!basethreadinitthunk+0xe 2a 07f5fb14 77c037be ntdll! RtlUserThreadStart+0x70 2b 07f5fb2c 00000000 ntdll!_rtluserthreadstart+0x1b 0:036>.frame /c f.. ole32!activationpropertiesin::delegatecreateinstance+0x108: 75f83170 8b4dfc mov ecx,dword ptr [ebp-4] ss:0023:07f5e7b8=13dcfe0d 0:036> dv this = 0x07f5ea70 punkouter = 0x00000000 ppactpropsout = 0x07f5f17c preplaceclassinfo = 0xffffffff clsid = struct _GUID {07f5f0e0-0000-0000-70ea-f5071ceaf507} pclassinfo = 0x00000001
62 / 8 [ 실습 2] 결론 추정해낸 ebp 값을이용해콜스택을구성 현재 DLL Load가진행중임을확인!! ShellStreams.dll의 DllMain에서동기화를수행하는 api를호출한것이 DeadLock의원인 ( 참고 : http://www.jiniya.net/tt/788) 0:036> kbn = 07f5e21c # ChildEBP RetAddr Args to Child 00 07f5d76c 77be6a64 77bd2278 000020cc 00000000 ntdll!kifastsystemcallret 01 07f5d770 77bd2278 000020cc 00000000 00000000 ntdll!ntwaitforsingleobject+0xc 02 07f5e21c 77c02322 07f5e27c 07f5e248 00000000 ntdll!rtlpwaitoncriticalsection+0x13e 03 07f5e250 75c98c19 110c87ac 07f5e294 07f5e27c ntdll!ldrloaddll+0x92 04 07f5e28c 75f69d43 00000000 00000000 110c87ac KERNELBASE!LoadLibraryExW+0x1d3 05 07f5e2a8 75f69cc7 00000000 07f5e324 00000008 ole32!loadlibrarywithlogging+0x16 06 07f5e2cc 75f69bb6 07f5e324 07f5e2f0 07f5e2f4 ole32!cclasscache::cdllpathentry::loaddll+0xa9 07 07f5e2fc 75f690be 07f5e324 07f5e60c 07f5e31c ole32!cclasscache::cdllpathentry::create_rl+0x37 08 07f5e548 75f68f93 00000001 07f5e60c 07f5e578 ole32!cclasscache::cclassentry::createdllclassentry_rl+0xd4 09 07f5e590 75f68e99 00000001 002f607c 07f5e5bc ole32!cclasscache::getclassobjectactivator+0x224 0a 07f5e5c8 75f68c57 07f5e60c 00000000 07f5ec14 ole32!cclasscache::getclassobject+0x30 < 이하생략 > 0:036> du 07f5e324 07f5e324 "C:\Program Files\Common Files\Ap" 07f5e364 "ple\internet Services\ShellStrea" 07f5e3a4 "ms.dll.frame /c = 07f5e21c 07f5e24c 77c02322
[ 실습 3] Stack 분석응용 (2)
64 / 8 [ 실습 3] [dump9] - 비정상종료원인분석 Stack 의내용을육안으로분석해야할때도있음!! 로컬변수, 아규먼트등스택의내용을분석하여장애발생시의상황을추정해보자. CAccept::ManagerThread 에진입한이후에도대체무슨일이?? 0:052> k ChildEBP RetAddr 097ffdb8 616b1c1e tcpsvc!clock::`vftable // 자동분석결과가이상하다??? 097ffdd4 616b2ac7 tcpsvc!caccept::managerthread+0x3e 097ffdfc 617910a9 tcpsvc!xthreads::handler+0x97 097ffe34 61791133 tcpsvc!_callthreadstartex+0x1b 097ffe40 7613336a tcpsvc!_threadstartex+0x64 WARNING: Stack unwind information not available. Following frames may be wrong. 097ffe4c 773f9f72 kernel32!basethreadinitthunk+0x12 097ffe8c 773f9f45 ntdll!rtlinitializeexceptionchain+0x63 097ffea4 00000000 ntdll!rtlinitializeexceptionchain+0x36
65 / 8 [ 실습 3] [dump9] - 비정상종료원인분석 stack 내용을분석하여잃어버린함수의흐름을쫓아가보자. 0:052> dps esp esp+100 097ffd64 616c39f3 tcpsvc!ctcpservice::acceptcallback+0x1b3 097ffd68 00000001 097ffd6c 617c3050 tcpsvc!`string'+0xcfc 097ffd70 616c39dd tcpsvc!ctcpservice::acceptcallback+0x19d 097ffd74 3f054ad0 097ffd78 00000000 097ffd7c 00000000 097ffd80 03ee4420 097ffd84 00000000 097ffd88 01000000 xserver! ImageBase 097ffd8c 00f52c10 097ffd90 61823d90 netserver!cnetserver::connectcallback 097ffd94 03f48578 097ffd98 00000000 097ffd9c 02992c98 097ffda0 00000011 097ffda4 097ff928 097ffda8 097ffe24 097ffdac 61790ee0 tcpsvc!_except_handler4 097ffdb0 57055c30 097ffdb4 fffffffe 097ffdb8 097ffdd4 097ffdbc 616b1c1e tcpsvc!caccept::managerthread+0x3e 수수께끼의답은이안에있다!!
66 / 8 stack 의좀더자세한지도 함수의중간에서할당된로컬변수도실제로는함수시작시일괄할당됨. 로컬변수는선언된순서대로 stack에할당되지않음. 실제로스택엔로컬변수, return address, arguments 외에도많은것이
Debugging Tips 67 / 64 To Be Continued (1) Debugging 개론 (2) x86 stack inside (3) heap inside (4) x64 stack inside