博注:偶很少弄Windows的东西,偶尔因为RE了ntoskrnl.exe->ntoskrnl.c,发现里面很多__readfsdword(32),__readfsdword(292)之类的调用,因此Google了一下,下面是记录,不敢说原创,故都列出了原文地址。
1.What is the “FS”/“GS” register intended for?
http://stackoverflow.com/questions/10810203/what-is-the-fs-gs-register-intended-for
There is what they were intended for, and what they are used for by Windows and Linux.
The original intention behind the segment registers was to allow a program to access many different (large) segments of memory that were intended to be independent and part of a persistent virtual store. The idea was taken from the 1966 Multics operating system, that treated files as simply addressable memory segments. No BS "Open file, write record, close file", just "Store this value into that segment" with dirty page flushing.
Our current 2010 operating systems are a giant step backwards, which is why they are called "Eunuchs". You can only address your process space's single segment, giving a so-called "flat (IMHO dull) address space". The segment registers on the x86-32 machine can still be used for real segment registers, but nobody has bothered (Andy Grove, former Intel president, had a rather famous public fit last century when he figured out after all those Intel engineers spent energy and his money to implement this feature, that nobody was going to use it. Go, Andy!)
AMD in going to 64 bits decided they didn't care if they eliminated Multics as choice and so disabled the general capability of segment registers in 64 bit mode. There was still a need for threads to access thread local store, and an thread needed a pointer ... somewhere in the immediately accessible thread state (e.g, in the registers) ... to thread local store. Since Windows and Linux both used FS for this purpose in the 32 bit version, AMD decided to let the 64 bit segment registers (GS and FS) be used essentially only for this purpose (I think you can make them point anywhere in your process space; dunno if the application code can load them or not). Intel in their panic to not lose market share to AMD on 64 bits, and Andy being retired, decided to just copy AMD's scheme.
It would have been architecturally prettier IMHO to make each thread's memory map have an absolute virtual address (e.g, 0-FFF say) that was its thread local storage (no [segment] register pointer needed!); I did this in an 8 bit OS back in the 1970s and it was extremely handy, like having another big stack of registers to work in.
So, the segment registers are now kind of like your appendix. They serve a vestigial purpose. To our collective loss.
Those that don't know history aren't doomed to repeat it; they're doomed to doing something dumber.
2. x86 memory segmentation on Wikipedia
http://en.wikipedia.org/wiki/X86_memory_segmentation
The x86-64 architecture does not use segmentation in long mode (64-bit mode). Four of the segment registers: CS, SS, DS, and ES are forced to 0, and the limit to 264. The segment registers FS and GS can still have a nonzero base address. This allows operating systems to use these segments for special purposes.
For instance, Microsoft Windows on x86-64 uses the GS segment to point to the Thread Environment Block, a small data structure for each thread, which contains information about exception handling, thread-local variables, and other per-thread state. Similarly, the Linux kernel uses the GS segment to store per-CPU data.
3. Getting the current thread ID without a syscall?
#ifdef WITH_INTRINSICS
# ifdef MS_WINDOWS
# include <intrin.h>
# if defined(MS_WIN64)
# pragma intrinsic(__readgsdword)
# define _Py_get_current_process_id() (__readgsdword(0x40))
# define _Py_get_current_thread_id() (__readgsdword(0x48))
# elif defined(MS_WIN32)
# pragma intrinsic(__readfsdword)
# define _Py_get_current_process_id() (__readfsdword(0x20))
# define _Py_get_current_thread_id() (__readfsdword(0x24))
# endif
#endif
4. What does the ntoskrnl RE reveals?
{
return (PKTHREAD)__readfsdword(292);
}
{
return *(_DWORD *)(__readfsdword(292) + 128);
}
5. KPCR,KPRCB,ETHREAD,KTHREAD,EPROCESS,KPROCESS,TEB,PEB
KPCR(Kernel's Processor Control Region,内核进程控制区域)是一个不会随WINDOWS版本变动而改变的固定结构体,在它的末尾[偏移0x120]指向KPRCB结构。
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
...// 省略
+0x120 PrcbData : _KPRCB
KPRCB同样是一个不会随WINDOWS版本变动而改变的固定结构体。它包含有指向当前KTHREAD的指针,偏移值0x004。其实也就是知道了当前的ETHREAD基地址。[因为ETHREAD的第一项便是KTHREAD,ETHREAD在后面讨论,现在讨论进程相关] [通过 KeGetCurrentPrcb() 函数即可得到PKPRCB,具体参见WRK]
展开KTHREAD,其中的_KAPC_STATE结构中包含当前KPROCESS的地址
nt!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
...
+0x034 ApcState : _KAPC_STATE
+0x034 ApcState : struct _KAPC_STATE, 5 elements, 0x18 bytes
+0x000 ApcListHead : [2] struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x010 Process : Ptr32 to struct _KPROCESS, 29 elements, 0x6c bytes
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
而EPROCESS的第一项正是KPROCESS。联想我们熟悉的断EPROCESS链表隐藏进程的手法。通过PsGetCurrentProcess得到的其实是当前KPROCESS的地址,而KPROCESS就是EPROCESS结构体的第一项,这样就得到了当前的EPROCESS。然后遍历整个链表。。。
---->>大致流程:PsGetCurrentProcess()函数---->_PsGetCurrentProcess()宏----->KeGetCurrentThread()函数
---->>具体细节:
#define _PsGetCurrentProcess() (CONTAINING_RECORD(((KeGetCurrentThread())->ApcState.Process),EPROCESS,Pcb))
// 很明显,KeGetCurrentThread()得到KTHREAD结构体,KTHREAD偏移0x034处的
// ApcState中process即为EPROCESS的第一项KPROCESS的地址。CONTAINING_RECORD宏
// 将此地址减去它在EPROCESS中的偏移值,得到当前EPROCESS的实际地址
FORCEINLINE
struct _KTHREAD *
NTAPI KeGetCurrentThread (VOID)
{
#if (_MSC_FULL_VER >= 13012035)
return (struct _KTHREAD *) (ULONG_PTR) __readfsdword (FIELD_OFFSET (KPCR, PrcbData.CurrentThread));
#else
__asm { mov eax, fs:[0] KPCR.PrcbData.CurrentThread }
#endif
}
// fs在用户模式下指向TEB结构,在内核模式下指向KPCR
关于KPROCESS。里面保存了一些有用的信息,我们来简单的瞅下。
nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B // 进程的页目录PDT [涉及内存管理知识]
+0x020 LdtDescriptor : _KGDTENTRY // GDT的入口
+0x028 Int21Descriptor : _KIDTENTRY // IDT的入口
+0x030 IopmOffset : Uint2B
+0x032 Iopl : UChar
+0x033 Unused : UChar
+0x034 ActiveProcessors : Uint4B
+0x038 KernelTime : Uint4B
+0x03c UserTime : Uint4B
+0x040 ReadyListHead : _LIST_ENTRY
+0x048 SwapListEntry : _SINGLE_LIST_ENTRY
+0x04c VdmTrapcHandler : Ptr32 Void
+0x050 ThreadListHead : _LIST_ENTRY // 指向KTHREAD链
+0x058 ProcessLock : Uint4B
+0x05c Affinity : Uint4B
+0x060 StackCount : Uint2B
+0x062 BasePriority : Char
+0x063 ThreadQuantum : Char
+0x064 AutoAlignment : UChar
+0x065 State : UChar
+0x066 ThreadSeed : UChar
+0x067 DisableBoost : UChar
+0x068 PowerState : UChar
+0x069 DisableQuantum : UChar
+0x06a IdealNode : UChar
+0x06b Flags : _KEXECUTE_OPTIONS
+0x06b ExecuteOptions : UChar
PEB是很有用的东西,写shellcode、定位EPROCESS等都可以用到它。PEB在EPROCESS偏移0x1b0处
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
...
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
...
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE
...
+0x1b0 Peb : Ptr32 _PEB
...
---->>获得PEB的地址是非常简单的。可以通过EPROCESS的偏移,也可以用硬编码实现[不同进程的PEB高位都是一样的]
xor esi, esi ; FS寄存器 -> TEB结构,TEB+0x30 -> PEB结构
mov esi, fs:[esi + 30H] ; 而PEB中包含有_PEB_LDR_DATA。通过一系列的
mov eax, esi ; 偏移可以定位到Kernel32.dll基地址。。。
ret ; 呵呵,参看gz1X大虾的文章:WIN下获取kernel基址的shellcode探讨
当然可以直接用Windbg来查看当前的PEB的结构 lkd>dt _peb
lkd> !peb
PEB at 7ffdc000 //高位都是7ffd
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: No
ImageBaseAddress: 01000000
Ldr 00191e90
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 00191f28 . 00193330
Ldr.InLoadOrderModuleList: 00191ec0 . 00193320
Ldr.InMemoryOrderModuleList: 00191ec8 . 00193328
...// 省略