TEB 구조체

Reversing 2012. 4. 24. 10:11
 윈도우에는 PEB와 TEB라 불리는 특수 구조체가 프로세스별로 존재합니다. PEB는 유저 모드 프로세스당 하나, TEB는 유저 모드 스레드당 하나씩 존재합니다.

주의! 커널 모드(Kernel Mode)에서는 PEB/TEB가 존재하지 않습니다.

PEB는 다음 논제로 남겨두기로 하고, TEB 부터 살펴보겠습니다.

Windows XP SP3를 기준으로 TEB의 구조체는 아래와 같이 표현됩니다.

nt!_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해보았습니다.

kernel32!GetCurrentProcessId:
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명령어를 이용하여 세그먼트 정보를 출력한 결과입니다.:

P Si Gr Pr Lo
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 구조체를 조사해보면...

nt!_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 구조체는 다음과 같습니다. 읽어주셔서 감사합니다.

typedef struct {
LPVOID UniqueProcess; // Process ID (Offset: 0x0)
LPVOID UniqueThread; // Thread ID (Offset: 0x4)
} CLIENT_ID, *PCLIENT_ID;

ps: Visual Basic이나 Delphi같이 Assembly Language에 접근하기 힘든 언어는, TEB의 주소를 구해야 할 필요가 있습니다.

Windows NT 이상 버전의 OS에서는 ntdll.dll에 NtCurrentTeb()을 export하고 있고, 그 함수는 인자가 없고, 반환값은 32비트 정수의 TEB 주소값입니다.

그 함수를 이용해서 TEB의 주소를 구해주고 메모리 API(RtlMoveMemory 등)를 이용해서 읽거나 써주면, FS:[]와 마찬가지의 효과로 TEB에 접근할 수 있습니다.


AND

첫째, OS가 호출할 함수는 절대적으로 __stdcall을 쓴다. 

왜냐하면 OS는 __stdcall의 형식으로 호출하기 때문이다. 

둘째, DLL에 들어가는 함수를 만들때 왠만하면 __stdcall을 쓴다. 

왜냐하면 다른 프로그램에서도 그 함수를 호출할 수 있기때문이다. 

마지막 셋째, WinMain은 반드시.. _stdcall이다. 


일단간단하게 이렇게 알고


스택청소하는 순서... 뭐 이런건 차후에...

AND

요즘 몇가지 안티리버싱을 해보고있는데, 그중한가지.


PEB구조에서 NtGlobalFlags 값이 70h이면 디버깅이라고 판단한다.


찾는 방법은 인터넷에 많이 있는데,


검출추틴을 만들었는데, 의문점은 일단 아무프로세스하나 실행시키고 WinDBG에서 attach a process 로 붙이면 NtGlobalFlags로 걸리지가 않는다는점.


아무프로그램을  WinDBG에서 open executable ... 이걸로 실행해서  열면 검출이되고....


이거뭐야! 내가 잘못짠거야? ㅠ


언제 값이 변경되는지 알아봐야겠다. 

'Reversing' 카테고리의 다른 글

[ANTI-Reversing] ANTI_DLL Injection  (0) 2012.04.24
[ANTI Reversing] BegingDebuged 설명  (0) 2012.04.24
PEB구조체 시작 주소 MS Windows 버전별  (0) 2012.04.24
PEB 구조체  (0) 2012.04.24
TEB 구조체  (0) 2012.04.24
AND