安全路透社
当前位置:安全路透社 > 网络转载 > 正文

用TEB结构实现ShellCode的通用性

*本文原创作者:pwdme,属于FreeBuf原创奖励计划,禁止转载。

前言

为了动态定位函数地址,jneo和scz提出了可以使用TEB结构体在程序中定位动态链接库的地址进而获取函数地址的方法,本文进一步探究这种方法的原理,并挖掘更多的细节。

寻找模块装载地址

exe文件载入内存时,在NT内核系统中fs:[0]指向了TEB结构,TEB(Thread Environment Block)位于用户地址空间,进程中的每个线程都有自己的一个TEB,用windbg随便加载一个文件,用dt命令查看TEB结构体:

0:001> dt nt!_TEB

ntdll!_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

我们重点关注的是位于偏移+0x030处指向PEB(Process Environment Block)结构的指针,查看一下这个结构体:

0:001> dt nt!_PEB

ntdll!_PEB

   +0x000 InheritedAddressSpace : UChar

   +0x001 ReadImageFileExecOptions : UChar

   +0x002 BeingDebugged    : UChar

   +0x003 SpareBool        : UChar

   +0x004 Mutant           : Ptr32 Void

   +0x008 ImageBaseAddress : Ptr32 Void

   +0x00c Ldr              : Ptr32 _PEB_LDR_DATA

   +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS

   +0x014 SubSystemData    : Ptr32 Void

   +0x018 ProcessHeap      : Ptr32 Void

   +0x01c FastPebLock      : Ptr32 _RTL_CRITICAL_SECTION

   +0x020 FastPebLockRoutine : Ptr32 Void

   +0x024 FastPebUnlockRoutine : Ptr32 Void

   +0x028 EnvironmentUpdateCount : Uint4B

   +0x02c KernelCallbackTable : Ptr32 Void

   +0x030 SystemReserved   : [1] Uint4B

   +0x034 AtlThunkSListPtr32 : Uint4B

   +0x038 FreeList         : Ptr32 _PEB_FREE_BLOCK

   +0x03c TlsExpansionCounter : Uint4B

   +0x040 TlsBitmap        : Ptr32 Void

   +0x044 TlsBitmapBits    : [2] Uint4B

   +0x04c ReadOnlySharedMemoryBase : Ptr32 Void

   +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void

   +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void

   +0x058 AnsiCodePageData : Ptr32 Void

   +0x05c OemCodePageData  : Ptr32 Void

   +0x060 UnicodeCaseTableData : Ptr32 Void

   +0x064 NumberOfProcessors : Uint4B

   +0x068 NtGlobalFlag     : Uint4B

   +0x070 CriticalSectionTimeout : _LARGE_INTEGER

   +0x078 HeapSegmentReserve : Uint4B

   +0x07c HeapSegmentCommit : Uint4B

   +0x080 HeapDeCommitTotalFreeThreshold : Uint4B

   +0x084 HeapDeCommitFreeBlockThreshold : Uint4B

   +0x088 NumberOfHeaps    : Uint4B

   +0x08c MaximumNumberOfHeaps : Uint4B

   +0x090 ProcessHeaps     : Ptr32 Ptr32 Void

   +0x094 GdiSharedHandleTable : Ptr32 Void

   +0x098 ProcessStarterHelper : Ptr32 Void

   +0x09c GdiDCAttributeList : Uint4B

   +0x0a0 LoaderLock       : Ptr32 Void

   +0x0a4 OSMajorVersion   : Uint4B

   +0x0a8 OSMinorVersion   : Uint4B

   +0x0ac OSBuildNumber    : Uint2B

   +0x0ae OSCSDVersion     : Uint2B

   +0x0b0 OSPlatformId     : Uint4B

   +0x0b4 ImageSubsystem   : Uint4B

   +0x0b8 ImageSubsystemMajorVersion : Uint4B

   +0x0bc ImageSubsystemMinorVersion : Uint4B

   +0x0c0 ImageProcessAffinityMask : Uint4B

   +0x0c4 GdiHandleBuffer  : [34] Uint4B

   +0x14c PostProcessInitRoutine : Ptr32     void 

   +0x150 TlsExpansionBitmap : Ptr32 Void

   +0x154 TlsExpansionBitmapBits : [32] Uint4B

   +0x1d4 SessionId        : Uint4B

   +0x1d8 AppCompatFlags   : _ULARGE_INTEGER

   +0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER

   +0x1e8 pShimData        : Ptr32 Void

   +0x1ec AppCompatInfo    : Ptr32 Void

   +0x1f0 CSDVersion       : _UNICODE_STRING

   +0x1f8 ActivationContextData : Ptr32 Void

   +0x1fc ProcessAssemblyStorageMap : Ptr32 Void

   +0x200 SystemDefaultActivationContextData : Ptr32 Void

   +0x204 SystemAssemblyStorageMap : Ptr32 Void

   +0x208 MinimumStackCommit : Uint4B

这里重点关注的是偏移+0x00c处指向_PEB_LDR_DATA结构的指针,查看一下这个结构体:

0:001> dt nt!_PEB_LDR_DATA

ntdll!_PEB_LDR_DATA

   +0x000 Length           : Uint4B

   +0x004 Initialized      : UChar

   +0x008 SsHandle         : Ptr32 Void

   +0x00c InLoadOrderModuleList : _LIST_ENTRY

   +0x014 InMemoryOrderModuleList : _LIST_ENTRY

   +0x01c InInitializationOrderModuleList : _LIST_ENTRY

   +0x024 EntryInProgress  : Ptr32 Void

这里需要注意的是三个结构体:

+0x00c InLoadOrderModuleList : _LIST_ENTRY

+0x014 InMemoryOrderModuleList : _LIST_ENTRY

+0x01c InInitializationOrderModuleList : _LIST_ENTRY

这三个都是_LIST_ENTRY的结构体,都是进程当前已加载模块的链表,只是按照不同的方式排序,前两个的第一项都是当前进程第二项才是ntdll.dll,第三个第一项是ntdll.dll,这个结构体成员如下:

0:001> dt nt!_LIST_ENTRY

ntdll!_LIST_ENTRY

   +0x000 Flink            : Ptr32 _LIST_ENTRY

   +0x004 Blink            : Ptr32 _LIST_ENTRY

_LIST_ENTRY结构体是双向链表的头结点结构,两个指针分别指向了链表的第一个结点和最后一个节点,这里的双向链表的每一个结点都是一个指向LDR_DATA_TABLE_ENTRY结构体的指针,看一下这个结构的描述:

0:001> dt nt!_LDR_DATA_TABLE_ENTRY

ntdll!_LDR_DATA_TABLE_ENTRY

   +0x000 InLoadOrderLinks : _LIST_ENTRY

   +0x008 InMemoryOrderLinks : _LIST_ENTRY

   +0x010 InInitializationOrderLinks : _LIST_ENTRY

   +0x018 DllBase          : Ptr32 Void

   +0x01c EntryPoint       : Ptr32 Void

   +0x020 SizeOfImage      : Uint4B

   +0x024 FullDllName      : _UNICODE_STRING

   +0x02c BaseDllName      : _UNICODE_STRING

   +0x034 Flags            : Uint4B

   +0x038 LoadCount        : Uint2B

   +0x03a TlsIndex         : Uint2B

   +0x03c HashLinks        : _LIST_ENTRY

   +0x03c SectionPointer   : Ptr32 Void

   +0x040 CheckSum         : Uint4B

   +0x044 TimeDateStamp    : Uint4B

   +0x044 LoadedImports    : Ptr32 Void

   +0x048 EntryPointActivationContext : Ptr32 Void

   +0x04c PatchInformation : Ptr32 Void

前三个仍然是_LIST_ENTRY结构体,可以看到+0x018处正是dll的加载地址,+0x024处是名称字符串的地址,这里还要注意三个_LIST_ENTRY是分别相连的,也就是一一对应,所以如果用_PEB_LDR_DATA0x140x1c处的结构体的话,找到的_LDR_DATA_TABLE_ENTRY结构体也会分别在偏移0x80x10处,并不是从0开始,还要注意的是_UNICODE_STRING是个结构体,大小为8 bytes:

typedef struct _UNICODE_STRING

{

     WORD Length;

     WORD MaximumLength;

     DWORD * Buffer;

} UNICODE_STRING, *PUNICODE_STRING;

所以名称字符串的偏移实际上是0x28

在xp中调试

windbg中随意附加一个进程,来看一下怎么样一步步找到kernel32.dll的加载基址。

首先找TEB:

0:001> !teb

TEB at 7ffde000

然后在0x30偏移处找到PEB地址:

0:001> dd 7ffde000+0x30 L1

7ffde030  7ffdc000

0:001> !peb

PEB at 7ffdc000

PEB偏移0x1c处找到_PEB_LDR_DATA的地址:

0:001> dd 7ffdc000+0xc L1

7ffdc00c  00251e90

shellcode中一般使用InInitializationOrderLinks,因为第二项就是kernel32.dll,可以少一次遍历链表,也就是找到偏移0x1c处的_LIST_ENTRY结构体:

0:001> dd 00251e90+0x1c L2

00251eac  00251f28 00252e50

之前说过三个_LIST_ENTRY分别连接,由于我们用的是第三个链表,所以字符串地址就是0x28-0x10=0x18,之后计算偏移时都要在原始偏移减去0x10,在第一个结构体对应偏移找到字符串地址:

0:001> dd 00251f28 L2

00251f28  00251fd0 00251eac   // InInitializationOrderLinks

0:001> dd 00251f28+0x18 L1

00251f40  7c99d028

对应地址处的字符串:

0:001> du 7c99d028

7c99d028  "C:\WINDOWS\system32\ntdll.dll"

链表第二个结点位于0x00251fd0,可以找到对应字符串,发现就是kernel32.dll:

0:001> dd 00251fd0+0x18 L1

00251fe8  00251f70

0:001> du 00251f70

00251f70  "C:\WINDOWS\system32\kernel32.dll"

找到基址:

0:001> dd 00251fd0+0x8 L1

00251fd8  7c800000

0:001> lm a 7c800000

start    end        module name

7c800000 7c91e000   kernel32   (deferred

汇编实现:

上面是手动查找,转换成汇编语言如下:

MOV EAX, FS:[30] // EAX = PEB基址 

MOV EAX, [EAX+C] //EAX = PEB_LDR_DATA结构指针 

MOV ESI, [EAX+1C] // ESI = 第一个_LDR_DATA_TABLE_ENTRY

LODS DWORD PTR DS:[ESI] // EAX = 第二个_LDR_DATA_TABLE_ENTRY

MOV EBX, [EAX+8] // EBX = kernel32.dll基地址

可以加载一个进程然后用空格键汇编,依次粘贴以上5行代码,然后单步执行调试:

00402451 >   64:A1 30000000 MOV EAX,DWORD PTR FS:[30]

00402457     8B40 0C        MOV EAX,DWORD PTR DS:[EAX+C]

0040245A     8B70 0C        MOV ESI,DWORD PTR DS:[EAX+1C]

0040245D     AD             LODS DWORD PTR DS:[ESI]

0040245E     8B58 08        MOV EBX,DWORD PTR DS:[EAX+8]

执行完查看寄存器:

EBX 7C800000 kernel32.7C800000

完整的kernel32.dllLDR_DATA_TABLE_ENTRY结构体:

00242010| 002420D0

00242014| 00241F48

00242018| 002420D8

0024201C| 00241F50

00242020| 002420E0

00242024| 00241F58

00242028| 7C800000  kernel32.7C800000

0024202C| 7C80B63E  kernel32.<ModuleEntryPoint>

00242030| 0011E000

00242034| 00420040

00242038| 00241FB0  UNICODE "C:\WINDOWS\system32\kernel32.dll"

0024203C| 001A0018

00242040| 00241FD8  UNICODE "kernel32.dll"

00242044| 80084004

00242048| 0000FFFF

0024204C| 7C99B2B0  ASCII "L $"

00242050| 7C99B2B0  ASCII "L $"

00242054| 4802BDC6

00242058| 00000000

0024205C| 00000000

可以对照之前的结构体定义看看。

多系统通用的实现

上述方法在xp上十分有效,但到了win7或是有些版本上可能就不行,原因在于kernel32.dll并不在链表的第二个位置,看看win8上的结果:

00431C50  00431B20

00431C54  004319C8

00431C58  00431B28

00431C5C  004319D0

00431C60  004319D8  <= 指向kernel32.dll的结构体

00431C64  004314F8

00431C68  74D00000  KERNELBA.74D00000

00431C6C  74D0E9D1  KERNELBA.<ModuleEntryPoint>

00431C70  000D1000

00431C74  00460044

00431C78  00431BF0  UNICODE "C:\Windows\system32\KERNELBASE.dll"

00431C7C  001E001C

00431C80  00431C18  UNICODE "KERNELBASE.dll"

00431C84  00082ACC

00431C88  0000FFFF

00431C8C  77084408  ntdll.77084408

00431C90  77084408  ntdll.77084408

00431C94  52158F91

00431C98  00000000

00431C9C  00000000

这时链表上第二个结点是KERNELBASE.dll,而kernel32.dll在第三个结点处:

004319C8  00431C50

004319CC  004314E8

004319D0  00431C58

004319D4  004314F0

004319D8  00431B30

004319DC  00431C60

004319E0  750B0000  KERNEL32.750B0000

004319E4  750B642A  KERNEL32.<ModuleEntryPoint>

004319E8  000F9000

004319EC  00420040

004319F0  00431968  UNICODE "C:\Windows\system32\KERNEL32.DLL"

004319F4  001A0018

004319F8  00431990  UNICODE "KERNEL32.DLL"

004319FC  000C2ACC

00431A00  0000FFFF

00431A04  77084478  ntdll.77084478

00431A08  77084478  ntdll.77084478

00431A0C  52158E47

00431A10  00000000

00431A14  00000000

而在InMemoryOrderModuleList这个链表上,kernel32.dll的位置在多个系统上都是不变的,仍处于第三个位置,所以通用的代码为:

MOV EAX, FS:[30] // EAX = PEB基址 

MOV EAX, [EAX+C] //EAX = PEB_LDR_DATA结构指针 

MOV ESI, [EAX+14] // ESI = 第一个_LDR_DATA_TABLE_ENTRY

MOV ESI, [ESI] // ESI = 第二个_LDR_DATA_TABLE_ENTRY

MOV ESI, [ESI] // ESI = 第三个_LDR_DATA_TABLE_ENTRY

MOV EBX, [ESI+10] // EBX = kernel32.dll基地址

win8上测试通过,最后一句成功获取到了kernel32.dll的基地址:

1D0012A8 >   64:8B1D 300000>MOV EBX,DWORD PTR FS:[30]

1D0012AF     8B5B 0C        MOV EBX,DWORD PTR DS:[EBX+C]

1D0012B2     8B5B 14        MOV EBX,DWORD PTR DS:[EBX+14]

1D0012B5     8B1B           MOV EBX,DWORD PTR DS:[EBX]

1D0012B7     8B1B           MOV EBX,DWORD PTR DS:[EBX]

1D0012B9     8B5B 10        MOV EBX,DWORD PTR DS:[EBX+10] ;KERNEL32.750B0000

总结

有了kernel32.dll的基地址,我们就可以根据PE文件的结构找到GetProcAddress的地址,然后可以随意得到需要的函数地址,为之后的shellcode编写提供了便利,这种通用性代码的发明使得shellcode能够在不同的系统版本上稳定的工作。

*本文原创作者:pwdme,属于FreeBuf原创奖励计划,禁止转载。

未经允许不得转载:安全路透社 » 用TEB结构实现ShellCode的通用性

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册