간혹 Application Hang 발생 시 COM Object의 Thread Model에 의한 Hang 현상이 발생하곤 합니다. 이러한 경우는 대부분 STA의 Message Loop과 Main Thread 사이에서 발생하는 Hang현상일 가능성이 높고 또한 Stack에는 어김 없이 GetToSTA 라는 Function 명이 보이게 되죠.
0:000> kvn
# ChildEBP RetAddr Args to Child
00 00129d48 7c93df5a 7c7d25db 000004a8 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 00129d4c 7c7d25db 000004a8 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
02 00129db0 7c7d2542 000004a8 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xa8 (FPO: [Non-Fpo])
03 00129dc4 769b0647 000004a8 ffffffff 001843e8 kernel32!WaitForSingleObject+0×12 (FPO: [2,0,0])
04 00129de0 76a91e50 001843e8 00195b68 00000000 ole32!GetToSTA+0×6f (FPO: [2,0,4])
05 00129e00 76a9108a 00129ec8 00129fd8 00184fa4 ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0xf6 (FPO: [1,1,4])
06 00129ee0 769bce1a 00184fa4 00129fd8 00129fc8 ole32!CRpcChannelBuffer::SendReceive2+0xc8 (FPO: [3,50,4])
07 00129f4c 769bcdb2 00184fa4 00129fd8 00129fc8 ole32!CAptRpcChnl::SendReceive+0xab (FPO: [3,19,4])
08 00129fa0 77e04db5 00184fa4 00129fd8 00129fc8 ole32!CCtxComChnl::SendReceive+0×113 (FPO: [3,13,4])
09 00129fbc 77e04ead 0018aae4 0012a004 0600015b RPCRT4!NdrProxySendReceive+0×43 (FPO: [2,0,0])
0a 0012a398 77e04e42 76976228 76979370 0012a3d0 RPCRT4!NdrClientCall2+0×1fa (FPO: [Non-Fpo])
0b 0012a3b8 77d9a83b 00000010 00000003 0012a3e0 RPCRT4!ObjectStublessClient+0×8b (FPO: [2,0,4])
0c 0012a3c8 769a5ba3 0018aae4 0012a8cc 0012ae08 RPCRT4!ObjectStubless+0xf
0d 0012a3e0 769a20c8 0018aae4 00000001 00000000 ole32!CProcessActivator::GCOCallback+0×2b (FPO: [6,0,4])
0e 0012a400 769a207f 76a9714c 0012a728 00000000 ole32!CProcessActivator::AttemptActivation+0×2c (FPO: [7,0,0])
0f 0012a438 769a5c4a 76a9714c 0012a728 00000000 ole32!CProcessActivator::ActivateByContext+0×42 (FPO: [6,2,4])
10 0012a460 769a5a11 76a9714c 0012a8cc 0012ae08 ole32!CProcessActivator::GetClassObject+0×48 (FPO: [3,1,4])
11 0012a498 769a5a2b 0012a8cc 0012ae08 0012ae08 ole32!ActivationPropertiesIn::DelegateGetClassObject+0xf3 (FPO: [2,7,0])
12 0012a4c0 769a5a11 76a97114 00000001 0012ae08 ole32!CClientContextActivator::GetClassObject+0×88 (FPO: [3,3,0])
13 0012a4f8 769a58a5 0012a8cc 0012ae08 003a0043 ole32!ActivationPropertiesIn::DelegateGetClassObject+0xf3 (FPO: [2,7,0])
GetToSTA Stack이 보이면 일단 첫번째 Parameter를 통해서 Message Loop Thread를 찾을 수 있습니다.
0:000> dd 001843e8
001843e8 76a96fa0 00184370 00000954 00000c4c
001843f8 f519ce96 35030aaa 36910894 e95a1c1c
00184408 36910894 e95a1c1c 00004801 0c4c0954
00184418 88616a96 b4e88b85 00000103 00060176
00184428 001872c8 00000000 00000000 00000000
00184438 00000001 ffffffff 00184d10 0018871c
00184448 00000008 00000000 00000000 00000000
00184458 baadf00d 00070005 001844d8 f1eef1ee
0:000> ~
. 0 Id: 954.a10 Suspend: 1 Teb: 7ffdf000 Unfrozen
1 Id: 954.2f8 Suspend: 1 Teb: 7ffde000 Unfrozen
2 Id: 954.c4c Suspend: 1 Teb: 7ffdc000 Unfrozen
3 Id: 954.e90 Suspend: 1 Teb: 7ffdd000 Unfrozen
4 Id: 954.f8c Suspend: 1 Teb: 7ffdb000 Unfrozen
5 Id: 954.7fc Suspend: 1 Teb: 7ffd9000 Unfrozen
6 Id: 954.710 Suspend: 1 Teb: 7ffd8000 Unfrozen
7 Id: 954.e00 Suspend: 1 Teb: 7ffd7000 Unfrozen
8 Id: 954.bac Suspend: 1 Teb: 7ffd6000 Unfrozen
0:000> ~2s;kvn
eax=00000000 ebx=00000000 ecx=7ffdc000 edx=00000000 esi=7c9ae178 edi=00000000
eip=7c93e514 esp=028bf7f4 ebp=028bf87c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0048 es=1518 fs=003b gs=5da8 efl=00000246
ntdll!KiFastSystemCallRet:
7c93e514 c3 ret
# ChildEBP RetAddr Args to Child
00 028bf7f0 7c93df5a 7c94b24b 00000480 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 028bf7f4 7c94b24b 00000480 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
02 028bf87c 7c931046 019ae178 7c944a53 7c9ae178 ntdll!RtlpWaitForCriticalSection+0×132 (FPO: [1,26,4])
03 028bf884 7c944a53 7c9ae178 00000000 028bf984 ntdll!RtlEnterCriticalSection+0×46 (FPO: [1,0,0])
04 028bf8c0 7c9468f0 00000000 00000000 028bf904 ntdll!LdrLockLoaderLock+0×146 (FPO: [Non-Fpo])
05 028bf934 7c9466b8 00000001 00000001 00000000 ntdll!LdrGetDllHandleEx+0×8b (FPO: [Non-Fpo])
06 028bf950 7c7de534 00000001 00000000 028bf9d4 ntdll!LdrGetDllHandle+0×18 (FPO: [4,0,0])
07 028bf9a0 7c7de64b 028bf9d4 00000000 028bfea0 kernel32!GetModuleHandleForUnicodeString+0×1d (FPO: [Non-Fpo])
08 028bfe24 7c7de4fc 00000001 00000002 028bfec8 kernel32!BasepGetModuleHandleExW+0×18e (FPO: [Non-Fpo])
09 028bfe3c 00c5ec0e 028bfec8 028bffdc 00c5ec1a kernel32!GetModuleHandleW+0×29 (FPO: [1,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
0a 028bfe90 7c93e473 028bfea0 00000074 00000074 hook1+0×1ec0e
0b 028bff10 77cf91be 77cf91f1 028bff54 00000000 ntdll!KiUserCallbackDispatcher+0×13 (FPO: [0,0,0])
0c 028bff14 77cf91f1 028bff54 00000000 00000000 USER32!NtUserGetMessage+0xc
0d 77cf91be 90909090 8b55ff8b 10558bec 0b144d8b USER32!GetMessageW+0×33 (FPO: [4,0,4])
0e 77cf91be 00000000 8b55ff8b 10558bec 0b144d8b 0×90909090
TID가 c4c 인 Thread를 보면 GetMessageW를 호출하고 있는 것을 볼 수 있습니다. 그리고 그위로 Loader Lock이 걸려 있다는 것을 확인 할 수 있습니다. Stack이 깨져 있
기 때문에 좀더 확실하게 Stack Thread를 해보죠.( ※ Stack이 깨져 보이는 이유는 정확한 Symbol이 없는 상태( FPO가 정확하지 않은 상태) 에서 Nt* 형태의 User To
Kernel Trap이 발생하는 함수를 호출했기 때문입니다. )
0:002> !teb
TEB at 7ffdc000
ExceptionList: 028bf8b0
StackBase: 028c0000
StackLimit: 028bc000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdc000
EnvironmentPointer: 00000000
ClientId: 00000954 . 00000c4c
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffda000
LastErrorValue: 126
LastStatusValue: c0000135
Count Owned Locks: 0
HardErrorMode: 0
0:002> dps 028bff14 028c0000
028bff14 77cf91be USER32!NtUserGetMessage+0xc
028bff18 77cf91f1 USER32!GetMessageW+0×33
028bff1c 028bff54
028bff20 00000000
028bff24 00000000
028bff28 00000000
028bff2c 77cf91c6 USER32!GetMessageW
028bff30 028bff70
028bff34 769c0471 ole32!CDllHost::STAWorkerLoop+0×72
028bff38 028bff54
028bff3c 00000000
028bff40 00000000
028bff44 00000000
028bff48 00000000
028bff4c 76a96c28 ole32!gATHost
028bff50 00000000
028bff54 00060176
028bff58 00000400
028bff5c 0000babe
028bff60 00195b6c
028bff64 005fd15b
028bff68 00000377
028bff6c 000002f5
028bff70 028bff8c
028bff74 769b6949 ole32!CDllHost::WorkerThread+0xc8
028bff78 00007530
028bff7c 7c7d2550 kernel32!WaitForSingleObjectEx
028bff80 00186fb0
028bff84 000005a8
028bff88 00187190
028bff8c 028bff94
028bff90 769b687c ole32!DLLHostThreadEntry+0xd
028bff94 028bffb4
028bff98 7698e3ee ole32!CRpcThread::WorkerLoop+0×1e
028bff9c 76a96c28 ole32!gATHost
028bffa0 00000000
028bffa4 00186fb0
028bffa8 76970000 ole32!_imp__RegQueryValueExA <PERF> (ole32+0×0)
028bffac 7698e456 ole32!CRpcThreadCache::RpcWorkerThreadEntry+0×1b
028bffb0 0012d22e
028bffb4 028bffec
028bffb8 7c7db729 kernel32!BaseThreadStart+0×37
028bffbc 00186fb0
028bffc0 00000000
028bffc4 0012d22e
028bffc8 00186fb0
028bffcc 7ffdc000
028bffd0 86faf600
028bffd4 028bffc0
028bffd8 8686d7c0
028bffdc ffffffff
028bffe0 7c809ad8 kernel32!_except_handler3
028bffe4 7c7db730 kernel32!`string’+0×88
028bffe8 00000000
028bffec 00000000
028bfff0 00000000
028bfff4 7698e43b ole32!CRpcThreadCache::RpcWorkerThreadEntry
028bfff8 00186fb0
028bfffc 00000000
028c0000 00000000
RawStack에 보이는것과 같이 STA Work Thread Loop 인것을 확인 할 수 있습니다. 측 STA Thread의 Message를 처리 하기 위한 Loop라는 이야기 입니다. 실제로 769c0471을 Reverse 하면 GetMessage를 호출하는 것을 볼 수 있습니다.
0:002> ub 769c0471
ole32!CDllHost::STAWorkerLoop+0×2e:
769c045d mov esi,dword ptr [ole32!_imp__GetMessageW (7697187c)]
769c0463 mov dword ptr [ole32!gTimerId (76a9974c)],eax
769c0468 push edi
769c0469 push edi
769c046a lea eax,[ebp-1Ch]
769c046d push edi
769c046e push eax
769c046f call esi
그렇다면 STA Work Thread가 선점해야하는 Critical Section을 확인해 보죠.
0:002> !cs 7c9ae178
—————————————–
Critical section = 0×7c9ae178 (ntdll!LdrpLoaderLock+0×0)
DebugInfo = 0×7c9ae1a0
LOCKED
LockCount = 0×7
OwningThread = 0×00000a10
RecursionCount = 0×2
LockSemaphore = 0×480
SpinCount = 0×00000000
STA Work Thread는 a10이 선점하고 있는 Critical Section을 기다리고 있군요. a10 Thread를 확인하면 최초의 Main Thread라는 것을 알 수 있습니다. 그렇다면 여기서 2 가지 의문만 풀면 문제를 해결 할 수 있게 됩니다.
1. GetMessageW 호출후 왜 Kernel To User Callback 에서 hook1.dll의 함수를 호출 했고 이 함수는 왜 Load Lock의 선점을 필요한 GetModuleHandleW를 호출 했는가 ?
2. Main Thread에서 기다리고 있는 Event는 어디서 해제를 해주어야하는가?
ntdll!KiUserCallbackDispatcher에서 hook1.dll의 함수를 호출했다는 것은 tacuil.dll에서 Hooking을 시도 했다는 의미로 보여 집니다. 그리고 GetMessage후에 KiUserCallbackDispatcher 가 호출되는 경우는 대부분 USER32!DispatchClientMessage( __fnDWORD를 통해서 호출 된다. )가 호출 되는 경우이지만, 이 경우는 또다른 Process에 의한 Message Hooking 모듈이 붙어 있을 가능성이 높음을 추정할 수 있습니다. 만약 Global Message Hooking 모듈이 존재 한다면 아래와 같은 스택이 구성되는 경우가 보통입니다.
Message Hooking 모듈 존재시 Stack 구성 예제
00 0007fbdc 7c93e473 0007fbec 00000080 00000080 USER32!__ClientLoadLibrary (FPO: [1,3,0])
01 0007fc68 77d1e1ad 77d1e18a 00000000 000000a0 ntdll!KiUserCallbackDispatcher+0×13 (FPO: [0,0,0])
*** ERROR: Symbol file could not be found. Defaulted to export symbols for F:\Program Files\NATEON\BIN\NateOnHook40u.dll -
02 0007fc90 1000120c 00020bd0 00000000 000000a0 USER32!NtUserCallNextHookEx+0xc
WARNING: Stack unwind information not available. Following frames may be wrong.
…
0d 0007fed8 01002a1b 0007fefc 00000000 00000000 USER32!NtUserGetMessage+0xc
0e 0007ff1c 01007511 01000000 00000000 000a2326 notepad!WinMain+0xe5 (FPO: [4,8,0])
0f 0007ffc0 7c7e7077 0043f6f2 0043f73a 7ffd8000 notepad!WinMainCRTStartup+0×174 (FPO: [Non-Fpo])
10 0007fff0 00000000 0100739d 00000000 78746341 kernel32!BaseProcessStart+0×23 (FPO: [Non-Fpo])
__ClientLoadLibray의 첫번째 Parameter
0:000> dc 0007fbec
0007fbec 00000080 00000058 00000001 b6138d60 ….X…….`…
0007fbfc 00000024 00000000 00560054 00000028 $…….T.V.(…
0007fc0c 00000000 0000001c 003a0046 0050005c ……..F.:.\.P.
0007fc1c 006f0072 00720067 006d0061 00460020 r.o.g.r.a.m. .F.
0007fc2c 006c0069 00730065 0057005c 0072006f i.l.e.s.\.W.o.r.
0007fc3c 004d006b 00730065 00650073 0067006e k.M.e.s.s.e.n.g.
0007fc4c 00720065 0064005c 0072006f 0061006d e.r.\.d.o.r.m.a.
0007fc5c 0074006e 0064002e 006c006c b6130000 n.t…d.l.l…..
__ClientLoadLibrary가 호출 되었을 가능성을 생각 할 때 tacuil.dll의 호출부의 첫번째 Data를 확인해 보도록하자.
0:002> dc 028bfea0
028bfea0 00000074 0000004c 00000001 a8e51844 t…L…….D…
028bfeb0 00000024 00000000 004a0048 028bfec8 $…….H.J…..
028bfec0 00000000 0000001c 003a0043 0057005c ……..C.:.\.W.
028bfed0 004e0049 004f0044 00530057 0073005c I.N.D.O.W.S.\.s.
028bfee0 00730079 00650074 0033006d 005c0032 y.s.t.e.m.3.2.\.
028bfef0 00770064 00680073 006e0065 00690067 d.w.s.h.e.n.g.i.
028bff00 0065006e 00300038 0044002e 004c004c n.e.8.0…D.L.L.
거의 비슷한 형태임을 알 수 있죠. __ClientLoadLibary가 호출한 어떤 함수를 Hooking하고 있음을 추정할 수 있습니다.
0:002> uf /c USER32!__ClientLoadLibrary
USER32!__ClientLoadLibrary (77d08023)
USER32!__ClientLoadLibrary+0×21 (77d08044):
call to USER32!FixupCallbackPointers (77d0b1f4)
USER32!__ClientLoadLibrary+0×2c (77d0804f):
call to kernel32!LoadLibraryExW (7c7d1af5)
USER32!__ClientLoadLibrary+0×41 (77d08064):
call to USER32!InitUserApiHook (77d07eda)
USER32!__ClientLoadLibrary+0×4b (77d0806e):
call to kernel32!FreeLibrary (7c7dac7e)
USER32!__ClientLoadLibrary+0×5d (77d08080):
call to USER32!XyCallbackReturn (77cf94a4)
0:002> uf kernel32!LoadLibraryExW
hook1+0×1ebe8:
00c5ebe8 push ebp
00c5ebe9 mov ebp,esp
00c5ebeb push ecx
00c5ebec push ebx
00c5ebed push esi
00c5ebee push edi
00c5ebef call hook1+0×11928 (00c51928)
00c5ebf4 mov dword ptr [ebp-4],eax
00c5ebf7 xor eax,eax
00c5ebf9 push ebp
00c5ebfa push offset hook1+0×1ec1a (00c5ec1a)
00c5ebff push dword ptr fs:[eax]
00c5ec02 mov dword ptr fs:[eax],esp
00c5ec05 mov eax,dword ptr [ebp+8]
00c5ec08 push eax
00c5ec09 call hook1+0×11958 (00c51958)
00c5ec0e mov esi,eax
00c5ec10 xor eax,eax
00c5ec12 pop edx
00c5ec13 pop ecx
00c5ec14 pop ecx
00c5ec15 mov dword ptr fs:[eax],edx
00c5ec18 jmp hook1+0×1ec26 (00c5ec26)
…
0:002> uf 00c51958
hook1+0×11958:
00c51958 jmp dword ptr [hook1+0x2a1e4 (00c6a1e4)]
kernel32!GetModuleHandleW:
7c7de4dd mov edi,edi
7c7de4df push ebp
7c7de4e0 mov ebp,esp
7c7de4e2 cmp dword ptr [ebp+8],0
7c7de4e6 je kernel32!GetModuleHandleW+0xb (7c802694)
kernel32!GetModuleHandleW+0×19:
7c7de4ec lea eax,[ebp+8]
7c7de4ef push eax
7c7de4f0 push dword ptr [ebp+8]
7c7de4f3 push 2
7c7de4f5 push 1
7c7de4f7 call kernel32!BasepGetModuleHandleExW (7c7de559)
7c7de4fc neg eax
7c7de4fe sbb eax,eax
7c7de500 and eax,dword ptr [ebp+8]
kernel32!GetModuleHandleW+0×30:
7c7de503 pop ebp
7c7de504 ret 4
kernel32!GetModuleHandleW+0xb:
7c802694 mov eax,dword ptr fs:[00000018h]
7c80269a mov eax,dword ptr [eax+30h]
7c80269d mov eax,dword ptr [eax+8]
7c8026a0 jmp kernel32!GetModuleHandleW+0×30 (7c7de503)
Loader Lock과 관련된 함수인 kernel32!LoadLibraryExW가 후킹되어 알 수 있고 또한 hook1+0×11958 (00c51958)을 통해서 kernel32!GetModuleHandleW가 호출 되고 있음을 추정할 수 있습니다.
그렇다면 GetModuleHandleW를 호출한 것이 문제의 원인 인가 ???
실제로 GetModuleHandleW를 호출하지 않고 바로 LoadLibraryExW를 호출하였다고 하더라도 문제는 맞찮가지죠. LoadLibraryExW 역시 Loader Lock를 사용하고 있기 때문입니다. 일단 STA Message Loop에서 Loader Lock이 걸리는 원인은 파악 된것 같습니다. 바로 Message Hooking 모듈이라는 것 때문입니다.
==> STA Message Loop 에서 Loader Lock을 사용하는 이유는 Message Hooking 모듈을 로드하기 위해서라는 것이 원인
그럼 이제 2번째 의문점을 파악해 보도록 하죠.
00 00129d48 7c93df5a 7c7d25db 000004a8 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
01 00129d4c 7c7d25db 000004a8 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
02 00129db0 7c7d2542 000004a8 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xa8 (FPO: [Non-Fpo])
03 00129dc4 769b0647 000004a8 ffffffff 001843e8 kernel32!WaitForSingleObject+0×12 (FPO: [2,0,0])
04 00129de0 76a91e50 001843e8 00195b68 00000000 ole32!GetToSTA+0×6f (FPO: [2,0,4])
05 00129e00 76a9108a 00129ec8 00129fd8 00184fa4 ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0xf6 (FPO: [1,1,4])
06 00129ee0 769bce1a 00184fa4 00129fd8 00129fc8 ole32!CRpcChannelBuffer::SendReceive2+0xc8 (FPO: [3,50,4])
07 00129f4c 769bcdb2 00184fa4 00129fd8 00129fc8 ole32!CAptRpcChnl::SendReceive+0xab (FPO: [3,19,4])
4a8 handle을 무한대기 하고 있는 것이 보입니다. STA Component 특성상 GetToSTA에서 기다리는 Event는 STA Message Loop Thread에서 Set 하게 되어 있습니다.
Thread1 Wait( GetToSTA ) ==> Thread2 Message 처리 ==> Thread2 SetEvent ==> Thread1 Release( GetToSTA )
이러한 과정을 반복하면서 STA Model은 Work Thread 동작 및 Message 처리를 모두 하도록 되어 있습니다.
0:002> uf /c ole32!CDllHost::WorkerThread
ole32!CDllHost::WorkerThread (769b6885)
ole32!CDllHost::WorkerThread+0×27 (769b68ac):
call to ole32!COleTls::TLSAllocData (7698de72)
ole32!CDllHost::WorkerThread+0×5c (769b68e1):
call to ole32!CoInitializeEx (7698ef7b)
ole32!CDllHost::WorkerThread+0×70 (769b68f5):
call to kernel32!InterlockedIncrement (7c7d9806)
ole32!CDllHost::WorkerThread+0×76 (769b68fb):
call to ole32!GetCurrentApartmentId (7698d26b)
ole32!CDllHost::WorkerThread+0×7e (769b6903):
call to kernel32!GetCurrentThreadId (7c7d97d0)
ole32!CDllHost::WorkerThread+0×89 (769b690e):
call to ole32!CDllHost::Marshal (769b6596)
ole32!CDllHost::WorkerThread+0×9a (769b691f):
call to ole32!GetCurrentApartmentToken (769b6961)
ole32!CDllHost::WorkerThread+0xaf (769b6934):
call to kernel32!SetEvent (7c7da0b7)
ole32!CDllHost::WorkerThread+0xc3 (769b6944):
call to ole32!CDllHost::STAWorkerLoop (769c042f)
ole32!CDllHost::WorkerThread+0xbc (769f6ff4):
call to ole32!CDllHost::MTAWorkerLoop (76a26ad0)
ole32!CDllHost::WorkerThread+0xd8 (769f7004):
call to ole32!wCoUninitialize (7698f1f2)
ole32!CDllHost::WorkerThread+0xe6 (769f7012):
call to kernel32!InterlockedDecrement (7c7d981a)
ole32!CDllHost::WorkerThread+0xf9 (769f7025):
call to kernel32!SetEvent (7c7da0b7)
WorkerThread에서 SetEvent을 호출 하는 것을 확인할 수 있습니다. 이제 이 Hang 현상의 답을 찾은거 같군요.
Loader Lock + STA Thread + Message Hooking 모듈 이라는 굉장히 평범하지 않는 내용이군요.
Enjoy Debugging
최근 답글