출처 :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는 유저 모드 스레드당 하나씩 존재합니다.

 주의! 커널 모드(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