개발자들은 개발을 하다보면 디버깅이라는 필수적인 과정을 수행하게 됩니다.

하지만 항상 컴파일러 상에서 지원해주는 디버깅을 사용할 순 없는 것입니다.

만약 소스코드는 Windows 7 x64에서 짜고 있지만 실제로 테스트해볼 환경은

Windows XP, Windows vista, Windows 7, Windows 8 각각의 x86, x64 버전에서 전부 해야한다면


가상머신에서 테스트 할 수 밖에 없습니다. 그렇다보니 컴파일러에서 지원해주는 디버거를 바로 사용할 수 없겠지요.

이럴 경우 전 여러가지 방법을 사용합니다.


IDA와 OllyDbg를 사용해 디버깅을 하기도 하구요. 커널이나 ollydbg에서 접근할 수 없는 상황인 경우는 windbg로 분석을 하기도합니다. 하지만 이는 작은 프로그램이나 큰 프로그램의 특정 코드 부분을 자세히 살펴볼 때 유용한 방법들입니다. 즉, 큰 프로그램에서 런타임 오류가 발생하는 상황이고 그 오류의 위치를 알았을 때 사용되는 방법들입니다. 큰 프로그램에서 런타임 오류가 발생할 때 바로 ollydbg 같은 툴로 본다는 것은 멍청한 짓이지요.


그래서 windows에서 유용하게 사용되는 툴이 adplus 라는 툴입니다. 이는 WDK를 설치하면 같이 딸려오는 놈입니다.

저 툴을 사용하면 특정 프로그램에서 crash가 나거나 hang이 걸리는 경우 유저메모리 덤프를 생성시켜줍니다. 생성된 파일은 WinDbg로 BSOD 발생했을 때 생성되는 dmp 파일와 같이 분석하면 됩니다.


저도 이런것을 모르고 있다가 최근에 hang 이 걸리는 사태가 발생하여 공부하고 익혀본 것입니다. 유용하더군요.

최근에 프로그램 하나 만들어보고 있는데 타겟 프로그램을 chrome으로 잡고 테스트 하던 도중 hang이 발생했습니다. 예전 같으면 API Moniter, ollydbg 등을 사용해 이것저것 때려맞추기 식으로 디버깅을 해봤겠지만 이제 adplus를 사용하면 hang 원인도 손쉽게 찾을 수 있습니다. crash dump는 익숙하던거라 쉽게 익혔지만 hang dump는 처음이라 막막했었네요 ㅋㅋ



행 덤프 파일입니다. 이제 WinDbg로 열어보겠습니다. 어떠한 이유 때문에 chrome에서 hang이 발생됬는지 알아보겠습니다. 



처음에 딱 열리면 먼저 심볼 정보부터 로드 시켜주고..!

빨간색 보시면 hang 이 걸린 환경은 Windows XP SP3 x86 멀티코어 환경이었습니다.

0:000> !analyze -v

*******************************************************************************

*                                                                             *

*                        Exception Analysis                                   *

*                                                                             *

*******************************************************************************


*** WARNING: Unable to verify checksum for Test.dll

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for Test.dll - 

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for chrome.dll - 

*** ERROR: Symbol file could not be found.  Defaulted to export symbols for chrome.exe - 

*** ERROR: Module load completed but symbols could not be loaded for ExploitShield.dll


FAULTING_IP: 

+0

00000000 ??              ???


EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)

ExceptionAddress: 00000000

   ExceptionCode: 80000007 (Wake debugger)

  ExceptionFlags: 00000000

NumberParameters: 0


BUGCHECK_STR:  80000007


PROCESS_NAME:  chrome.exe


ERROR_CODE: (NTSTATUS) 0x80000007 - {


EXCEPTION_CODE: (HRESULT) 0x80000007 (2147483655) -                      .


NTGLOBALFLAG:  0


APPLICATION_VERIFIER_FLAGS:  0


APP:  chrome.exe


CRITICAL_SECTION:  7c9ab178 -- (!cs -s 7c9ab178)


BLOCKING_THREAD:  00000810


LOADERLOCK_BLOCKED_API:  GetModuleHandleForUnicodeString:LdrGetDllHandle:LdrGetDllHandleEx:LdrLockLoaderLock:


LOADERLOCK_OWNER_API:  _LdrpInitialize:LdrpInitializeThread:LdrpCallInitRoutine:


DERIVED_WAIT_CHAIN:  


Dl Eid Cid     WaitType

-- --- ------- --------------------------

   0   694.698 Event                  -->

   23  694.810 Sleep                  


WAIT_CHAIN_COMMAND:  ~0s;k;;~23s;k;;


DEFAULT_BUCKET_ID:  APPLICATION_HANG_HungIn_LoaderLock


PRIMARY_PROBLEM_CLASS:  APPLICATION_HANG_HungIn_LoaderLock


LAST_CONTROL_TRANSFER:  from 7c93d1fc to 7c93e4f4


FAULTING_THREAD:  00000017


STACK_TEXT:  

0638fa90 7c93d1fc 7c8023f1 00000000 0638fac4 ntdll!KiFastSystemCallRet

0638fa94 7c8023f1 00000000 0638fac4 1dc2f5df ntdll!ZwDelayExecution+0xc

0638faec 7c802455 00000001 00000000 0638fb20 kernel32!SleepEx+0x61

0638fafc 01cbc6d5 00000001 00000000 00000214 kernel32!Sleep+0xf

WARNING: Stack unwind information not available. Following frames may be wrong.

0638fb20 01c32d1e 00000000 00000214 00000014 chrome_1c30000!ovly_debug_event+0x6b887

0638fb48 01c3167f 00000001 00000214 00000214 chrome_1c30000+0x2d1e

0638fb5c 01c34d3a 00000214 00000000 00000000 chrome_1c30000+0x167f

0638fb70 01c34cda 00000001 00000214 00000000 chrome_1c30000+0x4d3a

0638fb8c 01c313eb 00000001 00000214 d3fbb088 chrome_1c30000+0x4cda

0638fbc4 01c311f0 01c30000 00000002 00000000 chrome_1c30000+0x13eb

0638fc04 01c310f7 01c30000 0638fc30 7c93118a chrome_1c30000+0x11f0

0638fc10 7c93118a 01c30000 00000002 00000000 chrome_1c30000+0x10f7

0638fc30 7c94b175 01c310d9 01c30000 00000002 ntdll!LdrpCallInitRoutine+0x14

0638fca4 7c94afee 0638fd30 0638fd30 00000029 ntdll!LdrpInitializeThread+0xc0

0638fd1c 7c93e437 0638fd30 7c930000 00000000 ntdll!_LdrpInitialize+0x219

00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7



FOLLOWUP_IP: 

chrome_1c30000+10f7

01c310f7 59              pop     ecx


SYMBOL_STACK_INDEX:  b


SYMBOL_NAME:  chrome_1c30000+10f7


FOLLOWUP_NAME:  MachineOwner


MODULE_NAME: chrome_1c30000


IMAGE_NAME:  chrome.dll


DEBUG_FLR_IMAGE_TIMESTAMP:  51e035ce


STACK_COMMAND:  ~23s ; kb


BUCKET_ID:  80000007_chrome_1c30000+10f7


FAILURE_BUCKET_ID:  APPLICATION_HANG_HungIn_LoaderLock_80000007_chrome.dll!Unknown


WATSON_STAGEONE_URL:  http://watson.microsoft.com/StageOne/chrome_exe/28_0_1500_72/51e03646/unknown/0_0_0_0/bbbbbbb4/80000007/00000000.htm?Retriage=1


WATSON_IBUCKET:  -589352337


WATSON_IBUCKETTABLE:  1


Followup: MachineOwner

---------


!analyze -v 한 결과입니다.

먼저 예외코드는 0x80000007 인데 보통 0x80000007 과 0x80000003 이 예외코드로 있으면 유저덤프를 뜬 거라고 생각하면 됩니다. 고로 저 예외코드는 별 쓸모가 없습니다.


중간에 CRITICAL_SECTION 이라는게 있는데 왠지 hang 걸린 이유가 내부적으로 크리티컬섹션 사용으로 인해 데드락이 걸린 것 같은 느낌입니다. 제 모듈은 dll 로 chrome에 injection 되서 돌아가는데 제 프로그램에선 크리티컬 섹션을 안썼습니다..

뭐지뭐지.. 일단 조금 더 자세한 분석을 위해 모든 스레드의 스택을 보겠습니다.


사실 처음에는 모든 스레드의 스택을 볼 생각을 못했었습니다. crash 덤프파일을 분석할 땐 그 crash 난 부분부터 콜스택을 따라 인자 값 등등 역추적해나가면 되니까요.. 

0:000> ~

.  0  Id: 694.698 Suspend: 1 Teb: 7ffdf000 Unfrozen

   1  Id: 694.510 Suspend: 1 Teb: 7ffde000 Unfrozen

   2  Id: 694.504 Suspend: 1 Teb: 7ffdd000 Unfrozen

   3  Id: 694.500 Suspend: 1 Teb: 7ffdc000 Unfrozen

   4  Id: 694.678 Suspend: 1 Teb: 7ffdb000 Unfrozen

   5  Id: 694.704 Suspend: 1 Teb: 7ffda000 Unfrozen

   6  Id: 694.700 Suspend: 1 Teb: 7ffd9000 Unfrozen

   7  Id: 694.6e8 Suspend: 1 Teb: 7ffd7000 Unfrozen

   8  Id: 694.6e4 Suspend: 1 Teb: 7ffd6000 Unfrozen

   9  Id: 694.524 Suspend: 1 Teb: 7ffd5000 Unfrozen

  10  Id: 694.434 Suspend: 1 Teb: 7ffd4000 Unfrozen

  11  Id: 694.790 Suspend: 1 Teb: 7ffd3000 Unfrozen

  12  Id: 694.750 Suspend: 1 Teb: 7ff9f000 Unfrozen

  13  Id: 694.6b8 Suspend: 1 Teb: 7ff9e000 Unfrozen

  14  Id: 694.720 Suspend: 1 Teb: 7ff9d000 Unfrozen

  15  Id: 694.7b0 Suspend: 1 Teb: 7ff9c000 Unfrozen

  16  Id: 694.7e0 Suspend: 1 Teb: 7ff9b000 Unfrozen

  17  Id: 694.3b0 Suspend: 1 Teb: 7ff9a000 Unfrozen

  18  Id: 694.7e4 Suspend: 1 Teb: 7ff99000 Unfrozen

  19  Id: 694.7e8 Suspend: 1 Teb: 7ff98000 Unfrozen

  20  Id: 694.7f4 Suspend: 1 Teb: 7ff97000 Unfrozen

  21  Id: 694.804 Suspend: 1 Teb: 7ff96000 Unfrozen

  22  Id: 694.808 Suspend: 1 Teb: 7ff95000 Unfrozen

  23  Id: 694.810 Suspend: 1 Teb: 7ff94000 Unfrozen


현재 프로세스에서 사용되는 스레드목록을 보니 24개의 스레드가 돌아가고 있습니다. 이제 스택을 보죠.

모든 스레드 스택을 봤을 때 전부 ms꺼 이거나 chrome 관련 모듈이면 포기할까 했지만.. 역시나 dll injection 된 모듈이 콜스택에 끼어있습니다. 여러 스레드 스택에 존재했지만 하나만 보도록 하겠습니다.

  1  Id: 694.510 Suspend: 1 Teb: 7ffde000 Unfrozen

 # ChildEBP RetAddr  

00 00c6e89c 7c93df3c ntdll!KiFastSystemCallRet

01 00c6e8a0 7c94b22b ntdll!ZwWaitForSingleObject+0xc

02 00c6e928 7c931046 ntdll!RtlpWaitForCriticalSection+0x132

03 00c6e930 7c944a33 ntdll!RtlEnterCriticalSection+0x46

04 00c6e96c 7c9468d0 ntdll!LdrLockLoaderLock+0x146

05 00c6e9e0 7c946698 ntdll!LdrGetDllHandleEx+0x8b

06 00c6e9fc 7c80e524 ntdll!LdrGetDllHandle+0x18

07 00c6ea4c 7c80e63b kernel32!GetModuleHandleForUnicodeString+0x1d

08 00c6eed0 7c80e4ec kernel32!BasepGetModuleHandleExW+0x18e

09 00c6eee8 00cbc95e kernel32!GetModuleHandleW+0x29

0a 00c6ef10 00cb95bd Test!Test+0xc87e

0b 00c6fa00 7c830a1a Test!Test+0x94dd

0c 00c6fa3c 1002dd77 kernel32!OpenProcess+0x49

0d 00c6fd34 1000665a ExploitShield+0x2dd77

0e 00c6fe8c 10006901 ExploitShield+0x665a

0f 00c6ffb4 7c80b713 ExploitShield+0x6901

10 00c6ffec 00000000 kernel32!BaseThreadStart+0x37

( Test는 원래 제 모듈의 이름이 아니지만 원래 모듈 이름은 노출되면 안되므로 Test로 바꿨습니다. )


Test는 제가 인젝션 시킨 모듈인데 콜스택을 보면 GetModuleHandle이라는 함수를 호출함으로서 내부적으로 크리티컬섹션을 사용하게 됩니다.


저 GetModuleHandle 함수를 호출 할 때 인자를 봐보겠습니다.


Test!Test+0xc87e 위치 바로 위쪽에 GetModuleHandle이 있고 

그 위에 push로 첫번째 인자를 스택에 넣고 있겠군요.


00cbc94c c745f405000000  mov     dword ptr [ebp-0Ch],5

00cbc953 6874c7cc00      push    offset Test!Test+0x1c694 (00ccc774)    ; 첫번째 인자

00cbc958 ff1534c0cc00    call    dword ptr [Test!Test+0x1bf54 (00ccc034)]    ; GetModuleHandle

00cbc95e 8945f0          mov     dword ptr [ebp-10h],eax


0:001> du 00ccc774

00ccc774  "ntdll.dll"


GetModuleHandle(L"ntdll.dll");


다른 스레드를 봐도 전부 GetModuleHandle(L"ntdll.dll"); 를 수행하던 중이었습니다.


일단, hang 이 발생된 원인은 파악된 것 같습니다.

제 모듈 내부에서 발생했고 여러 스레드에서 동시에 GetModuleHandle(L"ntdll.dll"); 를 수행하다가 크리티컬섹션이 엉켜 hang이 걸린 것입니다.


원인은 찾았으니 해결 방법은 인젝션된 dll 초기화 단계에서 한번만 ntdll.dll 의 베이스 주소를 얻고 다른 곳에선 새로 구하지 않고 그냥 그 값을 쓰는 형태로 바꿔주면 될 것 같군요. 실제로 이렇게 수정했더니 제대로 돌아갑니다.


이제 궁금증이 생겼군요.

GetModuleHandle 함수는 내부적으로 어떻게 모듈의 핸들(베이스 주소)를 얻어오기에 크리티컬섹션까지 사용하고 있는걸까요? 그리고 여러 쓰레드에서 같은 모듈의 핸들을 얻으려고 할 때 왜 hang이 걸리는 걸까요?..


위 궁금증을 해결해보기 위해 먼저 스레드 30개를 생성하고 각각의 스레드에서 GetModuleHandle(L"ntdll.dll"); 호출하는 형태의 테스트 프로그램을 만들어서 테스트 해봤지만... hang이 안걸리더군요. chrome에서 hang이 걸렸을 때는 뭔가 특수한 상황이었나 봅니다.


아무튼 GetModuleHandle은 자주 쓰면 좋진 않을 것 같습니다.


참고 URL : http://www.codemachine.com/article_rpcchain.html

Posted by Ezbeat