출처 :http://vbdream.tistory.com/entry/Windows-TEBThread-Environment-Block-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0
[Windows] PEB(Process Environment Block)/TEB(Thread Environment Block) 살펴보기
윈도우에는 PEB와 TEB라 불리는 특수 구조체가 프로세스별로 존재합니다. PEB는 유저 모드 프로세스당 하나, TEB는 유저 모드 스레드당 하나씩 존재합니다.
PEB는 다음 논제로 남겨두기로 하고, TEB 부터 살펴보겠습니다.
Windows XP SP3를 기준으로 TEB의 구조체는 아래와 같이 표현됩니다.
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
+0x0c8 FpSoftwareStatusRegister : Uint4B
+0x0cc SystemReserved1 : [54] Ptr32 Void
+0x1a4 ExceptionCode : Int4B
+0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
+0x1bc SpareBytes1 : [24] UChar
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : Ptr32 Void
+0x6c0 GdiClientPID : Uint4B
+0x6c4 GdiClientTID : Uint4B
+0x6c8 GdiThreadLocalInfo : Ptr32 Void
+0x6cc Win32ClientInfo : [62] Uint4B
+0x7c4 glDispatchTable : [233] Ptr32 Void
+0xb68 glReserved1 : [29] Uint4B
+0xbdc glReserved2 : Ptr32 Void
+0xbe0 glSectionInfo : Ptr32 Void
+0xbe4 glSection : Ptr32 Void
+0xbe8 glTable : Ptr32 Void
+0xbec glCurrentRC : Ptr32 Void
+0xbf0 glContext : Ptr32 Void
+0xbf4 LastStatusValue : Uint4B
+0xbf8 StaticUnicodeString : _UNICODE_STRING
+0xc00 StaticUnicodeBuffer : [261] Uint2B
+0xe0c DeallocationStack : Ptr32 Void
+0xe10 TlsSlots : [64] Ptr32 Void
+0xf10 TlsLinks : _LIST_ENTRY
+0xf18 Vdm : Ptr32 Void
+0xf1c ReservedForNtRpc : Ptr32 Void
+0xf20 DbgSsReserved : [2] Ptr32 Void
+0xf28 HardErrorsAreDisabled : Uint4B
+0xf2c Instrumentation : [16] Ptr32 Void
+0xf6c WinSockData : Ptr32 Void
+0xf70 GdiBatchCount : Uint4B
+0xf74 InDbgPrint : UChar
+0xf75 FreeStackOnTermination : UChar
+0xf76 HasFiberData : UChar
+0xf77 IdealProcessor : UChar
+0xf78 Spare3 : Uint4B
+0xf7c ReservedForPerf : Ptr32 Void
+0xf80 ReservedForOle : Ptr32 Void
+0xf84 WaitingOnLoaderLock : Uint4B
+0xf88 Wx86Thread : _Wx86ThreadState
+0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
+0xf98 ImpersonationLocale : Uint4B
+0xf9c IsImpersonating : Uint4B
+0xfa0 NlsCache : Ptr32 Void
+0xfa4 pShimData : Ptr32 Void
+0xfa8 HeapVirtualAffinity : Uint4B
+0xfac CurrentTransactionHandle : Ptr32 Void
+0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME
+0xfb4 SafeThunkCall : UChar
+0xfb5 BooleanSpare : [3] UChar
TEB(Thread Environment Block)에서 몇가지 재미난 필드를 살펴봅시다.
+0x000 NtTib : _NT_TIB
SEH(Structured Exception Handler) 체인 중 최상위 엔트리를 말합니다.
+0x01c EnvironmentPointer : Ptr32 Void
CreateProcess()의 Environment 필드에 의해 지시된 포인터 값입니다.
+0x020 ClientId : _CLIENT_ID
프로세스 아이디와 스레드 아이디를 가지고 있는 필드(CLIENT_ID structure)입니다.
+0x02c ThreadLocalStoragePointer : Ptr32 Void
TlsAlloc/TlsGetValue/TlsSetValue 등의 Win32 API에 의해 참조되는 값으로, Thread Local Storage(TLS) pointer를 저장합니다.
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
PEB(Process Environment Block) 포인터를 저장합니다.
+0x040 Win32ThreadInfo : Ptr32 Void
스레드 정보를 담고 있는 커널 모드 포인터입니다.
그렇다면 Application에서 TEB를 어떻게 사용하고 참조하는 것일까요?
그 해답은 API를 디어셈블하면 얻을 수 있을겁니다. GetCurrentProcessId()를 Disassemble해보았습니다.
7C8099B0 64 A1 18 00 00 00 MOV EAX,DWORD PTR FS:[18]
7C8099B6 8B 40 20 MOV EAX,DWORD PTR DS:[EAX+20]
7C8099B9 C3 RETN
생각 외로 코드가 별로 길지 않죠? :p
ClientId: CLIENT_ID 필드 오프셋이 0x20 이였던가요? 왠지 'EAX+20' 이 눈에 띄는군요~
그러면 EAX에는 TEB pointer가 있다는 것인데...
그 전에는 MOV EAX, DWORD PTR FS:[18h] 로 FS segment block을 참조하고 있는것을 볼 수 있습니다.
저 코드를 임의로 실행시켜보니 EAX 값은 0x7FFDF000 이였습니다. (물론 Thread 마다 주소는 다를겁니다. :p)
Thread 마다 주소가 다를 것 같으니, FS segment에 대한 비밀을 파헤쳐 보아야 될 것 같은데요... 아쉽게도 유저 모드(Ring3)에서는 세그먼트 정보를 볼 수 없습니다. ( 유저 모드에서 얻을 수 있는 것은 단지 Segment Index 뿐이죠. )
Debugger에서 Segment Index를 확인해보니 'FS=003B' 였습니다. (이 값은 버전, 환경에 따라 다를 수 있습니다.)
003B 는 2진수로 '0011 1011' 이고, 하위 3비트는 RPL(Request Privilege Level), 그리고 그 다음 1비트는 TI(LDT 사용 여부)를 의미합니다. 실제 세그먼트 인덱스는 (하위 4비트를 버린 값)+8 의 값을 가집니다.
다음은 WinDbg에서 dg명령어를 이용하여 세그먼트 정보를 출력한 결과입니다.:
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0000 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
0008 00000000 ffffffff Code RE Ac 0 Bg Pg P Nl 00000c9b
0010 00000000 ffffffff Data RW Ac 0 Bg Pg P Nl 00000c93
0018 00000000 ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb
0020 00000000 ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3
0028 bab50d70 000020ab TSS32 Busy 0 Nb By P Nl 0000008b
0030 bab50000 00001fff Data RW Ac 0 Bg Pg P Nl 00000c93
0038 7ffdd000 00000fff Data RW Ac 3 Bg By P Nl 000004f3
0040 00000400 0000ffff Data RW 3 Nb By P Nl 000000f2
0048 00000000 00000000 <Reserved> 0 Nb By Np Nl 00000000
7FFDD000 이군요. 아무래도 FS:[] 자체가 TEB인것 같습니다.
그렇다면 FS:[18h] 는 무엇이였던걸까요? 오프셋 0x18은 NT_TIB 안입니다. 그렇다면 NT_TIB 구조체를 조사해보면...
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB
결국 FS:[0] 자기 자신을 가리키는 것이였습니다. Microsoft는 왜 Self 필드를 참조하는 것일까요? :p
필자 생각으로는, 아마 미래를 위해서 그리 구현해놓은 것 같습니다. TEB가 아닌 다른 구조체가 FS:[]로 바뀌어도, 0x18 오프셋만은 TEB의 포인터를 가리키면 후방 호환성이 유지되니까요.
(뭐, 그 덕분에 해커가 Self 필드를 수정하면 모든 Windows API에서 TEB에 접근할 때 다른 메모리를 참조할 수 있게 만들 수 있는 틈이 생긴 셈이지만요 :p)
결론적으로, GetCurrentProcessId() API는 CLIENT_ID를 참조하는 것을 볼 수 있습니다.
마지막으로, CLIENT_ID 구조체는 다음과 같습니다. 읽어주셔서 감사합니다.
LPVOID UniqueProcess; // Process ID (Offset: 0x0)
LPVOID UniqueThread; // Thread ID (Offset: 0x4)
} CLIENT_ID, *PCLIENT_ID;
Windows NT 이상 버전의 OS에서는 ntdll.dll에 NtCurrentTeb()을 export하고 있고, 그 함수는 인자가 없고, 반환값은 32비트 정수의 TEB 주소값입니다.
그 함수를 이용해서 TEB의 주소를 구해주고 메모리 API(RtlMoveMemory 등)를 이용해서 읽거나 써주면, FS:[]와 마찬가지의 효과로 TEB에 접근할 수 있습니다.