대부분의 메모리덤프는 WinDbg로 열어서 pdb를 맞추어주고서 !analyze -v 를 해주면 콜스택이 거의 정확하게 분석됩니다만, 가끔은 콜스택이 제대로 분석되지 않는 덤프도 있습니다.

예를 들어 콜스택이 다음과 같다면 정상적이지 않다고 볼 수 있습니다.

1. 심볼(PDB)을 제대로 맞추었는데도 함수명과 같은 정보가 제대로 보이지 않는 콜스택 엔트리가 있다.
2. 상식적으로 이해할 수 없는 함수 호출이 보인다.
3. 콜 스택의 마지막에 kernel32!BastThreadStart 와 같은 스레드 시작 함수가 보이지 않는다.


WinDbg가 콜스택을 제대로 분석하지 못한다면, 스택을 수동으로 분석해야 하는데... 이건 너무 빡시죠. ㅎㅎ
수동분석까지는 아니더라도... WinDbg에게 약간의 힌트(!)를 제공하여 제대로 콜스택을 분석하게 할 수 있습니다. (이 방법은 x86에서만 가능합니다.)

일단... 이 방법을 이해하기 위해서는 함수 호출시에 스택이 어떤 식으로 사용되는지를 이해해야 합니다. 이부분을 잘 모르시는 분은 아래의 포스트를 먼저 읽으세요. ^^
http://kuaaan.tistory.com/115 

WinDbg에서는 콜스택을 분석하는 명령으로 "k"라는 명령을 이용할 수 있습니다.
k 명령의 도움말을 보면 아래와 같이 나와 있네요.

빨간 네모친 부분을 보시면... BasePtr (가능하다면 StackPtr, InstructionPtr까지)을 힌트로 줄 수 있다고 되어 있네요.
여기서 BastPtr은 EBP, StackPtr은 ESP, InstructionPtr은 Eip를 의미합니다.
말하자면, EBP 값을 수동으로 찾아내서 WinDbg에 알려주면 콜스택을 찾지 못해 헤매고 있는 WinDbg에게 힌트를 주어 제대로 CallStack을 찾도록 할수 있다는 의미입니다.

전체적인 과정을 살펴보면 다음과 같습니다.

1. 심볼(PDB)를 맞춘다. (이때, 자신의 모듈에 대한 PDB도 반드시 지정되어야 합니다.)
2. 문제가 발생한 시점에서 해당 스레드의 스택 Base(스택의 맨 밑바닥)와 스택 포인터(ESP)를 확인한다.
3. dps 명령을 이용해 스택 Base에서부터 ESP까지의 해당 스레드의 스택을 출력한다.
4. 출력된 스택 내용을 분석해 EBP값을 추정한다.
5. EBP 값을 k  명령에 인자로 주어 정상적인 콜스택이 출력되는지 확인한다. 만족할만한 콜스택이 확인되지 않을 경우, 3번으로 되돌아가 콜스택이 제대로 분석될때까지 Trial and Error를 반복한다.


위의 과정에서 핵심은 4번 과정의 스택에서 EBP로 보여지는 값을 추정해서 찾아내는 부분인데요... 다음과 같은 요령이 있습니다.

1. dps로 출력된 콜스택 내용 중 Return Address일 가능성이 있는 값을 찾는다. 보통 스택의 내용 옆에 찍히는 심볼 정보로 확인합니다.
예를 들어, 아래와 같은 정보가 있다면 "0040d3bb" 이 Return Address일 가능성이 있는 값입니다.

146f4dfc  003f8f18
146f4e00  003f8e68
146f4e04  146f4e90
146f4e08  0040d3bb MyTestSvc!CMiniDump::WriteCrashDump+0x228
146f4e0c  ffffffff
146f4e10  00000338
146f4e14  00000454

2. 확인된 Return Address 값이 들어있는 스택 주소 (위의 예에서는 "146f4e08"이 되겠네요)에서 4바이트 뺀 위치에 들어있는 값이 EBP의 후보값입니다. 위의 예에서는 "146f4e04"이 되겠네요.

3. 확인된 EBP 후보값에 들어있는 데이터 값 ("146f4e90")을 확인합니다. 이 값이 스레드 Base보다 작고, ESP보다 큰 값이라면 2에서 확인된 EBP 후보값이 EBP일 가능성이 높습니다.

4. WinDbg에 다음과 같은 명령을 입력하여 콜스택을 찍어봅니다.

0:010> kvn L=146f4e04


5. 출력된 콜스택이 만족스러워 질때까지 위의 EBP Gussing 작업을 계속합니다.


일단 방법은 모두 설명되었으니, 샘플 메모리덤프를 하나 골라서 분석을 해보도록 하겠습니다. (스택 내용을 그대로 출력했기 때문에 내용이 좀 길어집니다... 스크롤의 압박 주의! ^^) 

========================================================================================================


일단... 문제의 메모리덤프에 대한 !analyze -v 결과는 다음과 같습니다.
음... 콜스택이 뭔가 많이 이상하군요. (!analyze 결과를 보기 위해 "더보기"를 클릭하세요 ^^ )


k 명령으로 콜스택을 다시 찍어보면 다음과 같이 나옵니다.

0:010> kvn

 # ChildEBP RetTestdr  Args to Child              

00 146f45bc 7c93d18a 7c7d94e5 146f4618 000f0005 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

01 146f45c0 7c7d94e5 146f4618 000f0005 000004c0 ntdll!NtCreateSection+0xc (FPO: [7,0,0])

02 146f45cc 00000000 00000000 146f461c 7c7db9d7 kernel32!CreateFileMappingW+0x10b (FPO: [6,12,0])


네.. !analyze -v의 내용과는 많이 다르지만, 뭔가 이상하긴 하네요. ^^

위에서 설명한 절차대로 한번 진행해보겠습니다.

Step  1. 심볼(PDB)를 맞춘다. (이때, 자신의 모듈에 대한 PDB도 반드시 지정되어야 합니다.)

.symfix+ C:\Symbols       // WebSymbol을 다운받을 디렉토리 지정
.sympath+ C:\MyPdb      // 내 모듈의 Pdb가 있는 위치를 지정한다.
.reload 


Step  2. 문제가 발생한 시점에서 해당 스레드의 스택 Base(스택의 맨 밑바닥)와 스택 포인터(ESP)를 확인한다.

0:010> !teb

TEB at 7ff95000

    ExceptionList:        146f4e84

    StackBase:            14700000

    StackLimit:           146f1000

    SubSystemTib:         00000000

    FiberData:            00001e00

    ArbitraryUserPointer: 00000000

    Self:                 7ff95000

    EnvironmentPointer:   00000000

    ClientId:             00000338 . 00000e30

    RpcHandle:            00000000

    Tls Storage:          00000000

    PEB Testdress:          7ffd8000

    LastErrorValue:       87

    LastStatusValue:      c000000d

    Count Owned Locks:    0

    HardErrorMode:        0
 

0:010> r

eax=00ab0000 ebx=00209030 ecx=00000007 edx=7c93e514 esi=00209008 edi=00209060

eip=7c93e514 esp=146f45bc ebp=146f45cc iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

ntdll!KiFastSystemCallRet:

7c93e514 c3              ret 


이 스레드의 스택은 14700000 에서 시작되었고, 문제가 발생한 시점에서 146f45bc까지 사용되었음을 알 수 있습니다. (스택은 거꾸로 자랍니다.)

Step  3. dps 명령을 이용해 스택 Base에서부터 ESP까지의 해당 스레드의 스택을 출력한다.

0:010> dps esp 14700000


혹은 만약 미니덤프이거나 해서 위의 !teb 명령이 실패한다면, 다음과 같이 스택 사이즈를 Guessing해볼 수 있습니다. 이땐, 적절한 값을 주어서 "?????" 인 값들이 출력될 때까지 주면 됩니다.

0:010> dps esp esp+3000


실제로 예시의 메모리덤프에서 출력해보면 다음과 같은 결과가 나옵니다. (스크롤 압박!! ... 스택 덤프 내용을 보려면 "더보기"를 클릭하세요.)

Step 4. 출력된 스택 내용을 분석해 EBP값을 추정한다.
일단 위의 출력된 내용을 육안으로 분석해도 많은 정보를 얻을 수 있습니다. 모든 Return Address와 로컬변수, 그리고 함수 파라메터들이 저 속에 들어있기 때문이죠.  하지만, 정확한 콜스택을 얻기 위해 위에 출력된 값 중 EBP 값일 것으로 추정되는 값을 육안으로 찾아봅니다. (EBP 값은 여러개... 함수 Call이 일어난 갯수만큼 발견될 수 있지만 그중 하나만 찾아내면 됩니다.)

146ffa18  00101510

146ffa1c  146ffa4c

146ffa20  146ffa50

146ffa24  00e667eb MyTestCore!CMyEngineCtrl::FileRunStart+0xb5

146ffa28  01c424b0

146ffa2c  01bec070

146ffa30  00101510

146ffa34  146ffa4c


저는 "146ffa20" 값이 쓸만해 보이는군요. ^^

이 값을 이용해 콜스택을 출력해보겠습니다.

0:010> kv L=146ffa20

ChildEBP RetTestdr  Args to Child              

146f45bc 7c93d18a 7c7d94e5 146f4618 000f0005 ntdll!KiFastSystemCallRet (FPO: [0,0,0])

146f45c0 7c7d94e5 146f4618 000f0005 000004c0 ntdll!NtCreateSection+0xc (FPO: [7,0,0])

146ffa20 00e667eb 01c424b0 01bec070 00101510 kernel32!CreateFileMappingW+0x10b (FPO: [6,12,0])

146ffa50 00e6c397 01c424b0 01bec070 00000008 MyTestCore!CMyEngineCtrl::FileRunStart+0xb5 (FPO: [Non-Fpo]) (CONV: thiscall)

146ffd38 00e6d385 146ffd68 146ffdb0 01c424b0 MyTestCore!CTestCoreMgr::RunTargetHash+0x62 (FPO: [Non-Fpo]) (CONV: cdecl)

146fff54 00e646ae 01be7df4 146fff78 00000000 MyTestCore!CTestCoreMgr::IdentifyThreatFile+0x25e (FPO: [Non-Fpo]) (CONV: cdecl)

146fff70 00e82967 00000000 66649b71 00000000 MyTestCore!CMyDriverCtrl::IdentifyThreatFileProc+0x83 (FPO: [1,0,0]) (CONV: stdcall)

146fffa8 00e82a0f 00000000 146fffec 7c7db729 MyTestCore!_callthreadstartex+0x1b (FPO: [Non-Fpo]) (CONV: cdecl)

146fffb4 7c7db729 01be8010 00000000 00000000 MyTestCore!_threadstartex+0x82 (FPO: [Non-Fpo]) (CONV: stdcall)

146fffec 00000000 00e8298d 01be8010 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])


네~~ 꽤 그럴듯한 콜스택이 만들어졋습니다. ^^
일단 콜스택 상의 함수 호출들이 논리적으로 흐름에 맞고, BaseThreadStart 함수까지 이어져 있으니 이 콜스택이 제대로 분석되었다고 볼수 있겠습니다. ^^





 
Posted by kuaaan
,


사랑합니다. 편안히 잠드소서