现在的位置: 首页 > 综合 > 正文

The use of FS/GS registers

2013年05月24日 ⁄ 综合 ⁄ 共 7965字 ⁄ 字号 评论关闭
博注:偶很少弄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? 

http://web.archiveorange.com/archive/v/nwbg9rB8BAtPXVDARyvU

I have an unusual requirement: I need to get the current thread ID in as few instructions as possible.  On Windows, I managed to come up with this glorious hack:

#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
#endif

That exploits the fact that Windows uses the FS/GS registers to store thread/process metadata.

4. What does the ntoskrnl RE reveals?

PKTHREAD __stdcall KeGetCurrentThread()
{
  return (PKTHREAD)__readfsdword(292);
}

int __cdecl PsGetCurrentProcess()
{
  return *(_DWORD *)(__readfsdword(292) + 128);
}

5. KPCR,KPRCB,ETHREAD,KTHREAD,EPROCESS,KPROCESS,TEB,PEB 


http://hi.baidu.com/sudami/item/d1a5b235bb60bb342f20c459

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

    ...// 省略

抱歉!评论已关闭.