TEB,PEB
结构分析
PEB,全名Process eviroment block,进程环境块。
NT内核系统之中,fs寄存器指向TEB结构,TEB+0x30指向PEB结构,PEB+0x0c指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x1c处存放一些指向动态链接库的链表地址,win7下第一个指向ntdll.dll,第三个kernel32.dll
TEB结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| typedef struct _TEB { NT_TIB Tib; PVOID EnvironmentPointer; CLIENT_ID Cid; PVOID ActiveRpcHandle; PVOID ThreadLocalStoragePointer; struct _PEB *ProcessEnvironmentBlock; ULONG LastErrorValue; ULONG CountOfOwnedCriticalSections; PVOID CsrClientThread; struct _W32THREAD* Win32ThreadInfo; ULONG User32Reserved[0x1A]; ULONG UserReserved[5]; PVOID WOW32Reserved; LCID CurrentLocale; ULONG FpSoftwareStatusRegister; PVOID SystemReserved1[0x36]; LONG ExceptionCode; struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; UCHAR SpareBytes1[0x28]; GDI_TEB_BATCH GdiTebBatch; CLIENT_ID RealClientId; PVOID GdiCachedProcessHandle; ULONG GdiClientPID; ULONG GdiClientTID; PVOID GdiThreadLocalInfo; ULONG Win32ClientInfo[62]; PVOID glDispatchTable[0xE9]; ULONG glReserved1[0x1D]; PVOID glReserved2; PVOID glSectionInfo; PVOID glSection; PVOID glTable; PVOID glCurrentRC; PVOID glContext; NTSTATUS LastStatusValue; UNICODE_STRING StaticUnicodeString; WCHAR StaticUnicodeBuffer[0x105]; PVOID DeallocationStack; PVOID TlsSlots[0x40]; LIST_ENTRY TlsLinks; PVOID Vdm; PVOID ReservedForNtRpc; PVOID DbgSsReserved[0x2]; ULONG HardErrorDisabled; PVOID Instrumentation[14]; PVOID SubProcessTag; PVOID EtwTraceData; PVOID WinSockData; ULONG GdiBatchCount; BOOLEAN InDbgPrint; BOOLEAN FreeStackOnTermination; BOOLEAN HasFiberData; UCHAR IdealProcessor; ULONG GuaranteedStackBytes; PVOID ReservedForPerf; PVOID ReservedForOle; ULONG WaitingOnLoaderLock; ULONG SparePointer1; ULONG SoftPatchPtr1; ULONG SoftPatchPtr2; PVOID *TlsExpansionSlots; ULONG ImpersionationLocale; ULONG IsImpersonating; PVOID NlsCache; PVOID pShimData; ULONG HeapVirualAffinity; PVOID CurrentTransactionHandle; PTEB_ACTIVE_FRAME ActiveFrame; PVOID FlsData; UCHAR SafeThunkCall; UCHAR BooleanSpare[3]; } TEB, *PTEB;
|
PEB结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| typedef struct _PEB { UCHAR InheritedAddressSpace; UCHAR ReadImageFileExecOptions; UCHAR BeingDebugged; UCHAR Spare; PVOID Mutant; PVOID ImageBaseAddress; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PPEBLOCKROUTINE FastPebLockRoutine; PPEBLOCKROUTINE FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PVOID* KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PPEB_FREE_BLOCK FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PVOID* ReadOnlyStaticServerData; PVOID AnsiCodePageData; PVOID OemCodePageData; PVOID UnicodeCaseTableData; ULONG NumberOfProcessors; ULONG NtGlobalFlag; UCHAR Spare2[0x4]; LARGE_INTEGER CriticalSectionTimeout; ULONG HeapSegmentReserve; ULONG HeapSegmentCommit; ULONG HeapDeCommitTotalFreeThreshold; ULONG HeapDeCommitFreeBlockThreshold; ULONG NumberOfHeaps; ULONG MaximumNumberOfHeaps; PVOID** ProcessHeaps; PVOID GdiSharedHandleTable; PVOID ProcessStarterHelper; PVOID GdiDCAttributeList; PVOID LoaderLock; ULONG OSMajorVersion; ULONG OSMinorVersion; ULONG OSBuildNumber; ULONG OSPlatformId; ULONG ImageSubSystem; ULONG ImageSubSystemMajorVersion; ULONG ImageSubSystemMinorVersion; ULONG GdiHandleBuffer[0x22]; PVOID ProcessWindowStation; } PEB, *PPEB;
|
PEB_LDR_DATA结构
1 2 3 4 5 6 7 8 9
| typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA,*PPEB_LDR_DATA;
|
注意结构中的LIST_ENTRY类型。
LIST_ENTRY结构
1 2 3 4
| typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
|
1
| 这个双链表指向进程装载的模块,结构中的每个指针,指向了一个LDR_DATA_TABLE_ENTRY的结构:
|
LDR_DATA_TABLE_ENTRY结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; WORD LoadCount; WORD TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; PVOID LoadedImports; }; _ACTIVATION_CONTEXT * EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY ForwarderLinks; LIST_ENTRY ServiceTagLinks; LIST_ENTRY StaticLinks; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
|
分析
定位LIST_ENTRY之后,我们想找到LDR_DATA_TABLE_ENTRY。但是这个结构如上面的结构所示,是一个双向链表,而且链表的结构都是LIST_ENTRY。实际上在真实的系统环境中,LIST_ENTRY中填充的东西如下所示。
首先用windbg查看peb
1 2 3 4 5 6 7 8 9
| PEB at 7ffd4000 Ldr.InInitializationOrderModuleList: 00202988 . 00202cb0 Ldr.InLoadOrderModuleList: 002028e8 . 00202db8 Ldr.InMemoryOrderModuleList: 002028f0 . 00202dc0 于此同时也获得各个加载模块的基地址: 400000 3db8972c Oct 25 08:58:20 2002 C:\Users\JoRrYChEn\Desktop\test.exe 77130000 4ec49caf Nov 17 13:33:35 2011 C:\Windows\SYSTEM32\ntdll.dll 76e00000 4e21132a J1ul 16 12:27:22 2011 C:\Windows\system32\kernel32.dll 751b0000 4e21132b Jul 16 12:27:23 2011 C:\Windows\system32\KERNELBASE.dll
|
我们可以看到,ldr中的三个LIST_ENTRY,每个都有两个元素,第一个是Flink,第二个是Blink。B代表before,F代表forward,所以Blink代表着前一个链表,Flink是后一个。因为是循环链表,所以肯定是首尾相连。接下来我们先看看InLoadOrderModuleList的链表是啥样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| InLoadOrderModuleList: Flink :00202988 . Blink:00202cb0 0:000> dd 002028e8 002028e8 00202978 7720788c 00202980 77207894 002028f8 00000000 00000000 00400000 0040720b
0:000> dd 00202978 00202978 00202ca0 002028e8 00202ca8 002028f0 00202988 00202dc8 7720789c 77130000 00000000
0:000> dd 00202ca0 00202ca0 00202db8 00202978 00202dc0 00202980 00202cb0 7720789c 00202dc8 76e00000 76e51065
0:000> dd 00202db8 00202db8 7720788c 00202ca0 77207894 00202ca8 00202dc8 00202cb0 00202988 751b0000 751b7afd
0:000> dd 7720788c 7720788c 002028e8 00202db8 002028f0 00202dc0 7720789c 00202988 00202cb0 00000000 00000000
|
可以和上面的LDR_DATA_TABLE_ENTRY结构进行对比。前三个LIST_ENTRY占用8个字节,然后是dll base,所以002028e8对应的dll base就是0x400000,而偏移为0的那个对应的就是Flink,也就是0x202978,偏移为4的就是Blink,也就是最后一个链表的地址0x7720788c。其他的结构根据偏移找就可以。
其中77130000对应的是ntdll.dll的加载基地址,76e00000是kernel32.dll的加载基地址,751b0000是KERNELBASE.dll的加载基地址。其他俩链也差不多分析。
可以看到不同结构遍历的顺序是不一样的
下图是寻找LDR_DATA_TABLE_ENTRY的全部流程
以下是遍历顺序
1 2 3 4 5 6 7 8 9 10 11
| 遍历 InInitializationOrderModuleList 得到次序: NTDLL.DLL-> KERNELBASE.DLL->KERNEK32.DLL->NULL
遍历 InLoadOrderModuleList 得到次序
TEST.EXE->NTDLL.DLL-> KERNEK32.DLL->KERNELBASE.DLL->NULL
遍历 InMemoryOrderModuleList 得到次序 TEST.EXE->NTDLL.DLL-> KERNEK32.DLL->KERNELBASE.DLL->NULL
|
PEB_LDR_DATA链表的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 我们都知道,以前在xp下写shellcode,最通用获得kernel32基址代码: mov ebx, fs:[ 0x30 ] // 获得PEB mov ebx, [ ebx + 0x0C ] // 获得PEB_LDR_DATA mov ebx, [ ebx + 0x1C ] // InitializationOrderModuleList 第一项 mov ebx, [ ebx ] // InitializationOrderModuleList 第二项 mov ebx, [ ebx + 0x8 ] // 获得完整的路径地址 但是这段代码放到win7就不通用, 为什么?前三句没问题,首先是获得PEB基地址,然后在偏移0X0C处获得LDR,然后再取得LDR的偏移 0x1C处,获得InInitializationOrderModuleList加载的下一个模块基址,恰恰问题是出在第5句上,mov ebx,[ebx],此时,ebx中的值按照前面所述,应该是第二个LDR_DATA_TABLE_ENTRY,可是根据之前的观察,在win7的环境之下,InInitializationOrderModuleList第二个LDR_DATA_TABLE_ENTRY不是kernel32了,而是,KERNELBASE了(见图2),kernel32排到了第三个了。所以,若要获取win7下kernel32的基址,只需将之前的代码略加修改: mov ebx, fs:[ 0x30 ] // 获得PEB mov ebx, [ ebx + 0x0C ] // 获得PEB_LDR_DATA mov ebx, [ ebx + 0x1C ] // InitializationOrderModuleList第一项 mov ebx, [ ebx ] // InitializationOrderModuleList第二项 mov ebx, [ ebx ] // InitializationOrderModuleList第三项 mov ebx, [ ebx + 0x8 ] // 获得完整的路径地址 当然,若要想写一个通用的shellcode,就必须先要判断系统版本号再根据情况来确定是取链的第二项还是第三项。除开现有的字符串查找方法,有木有一个更为快捷的方法呢?我们试着来探讨一下:
前辈们的经验告诉我们,winXp下,InitializationOrderModuleList 加载的DLL模块顺序是固定的,通过遍历其链,kernel32.dll必然是在第二位。但是在win7条件下,这个xp下固定的“第二位”就已经换成了KERNELBASE.DLL了。那么另外两条InMemoryOrderModuleList和 InLoadOrderModuleList是不是模块加载顺序也变了呢?是不是也能像类似于查找InitializationOrderModuleList顺序链的方式找到通用的加载顺序呢? 结果不难发现,InLoadOrderModuleList在所有上述系统中,按照顺序:第一个是EXE模块本身的ImageBase,第二个是NTDLL.DLL,第三个是KERNEL32.DLL。这样,KERNEL32.DLL的顺序是不是又固定了呢? mov ebx, fs:[ 0x30 ] // 获得PEB mov ebx, [ ebx + 0x0C ] // 获得PEB_LDR_DATA mov ebx, [ ebx + 0x0C ] // InLoadOrderModuleList第一项 mov ebx, [ ebx ] // InLoadOrderModuleList第二项 mov ebx, [ ebx ] // InLoadOrderModuleList第三项 mov ebx, [ ebx + 0x18 ] // 获得完整的路径地址,此时偏移0x18 同理,InMemoryOrderModuleList也可以发现类似的规律。
|
断链隐藏
基于PEB断链在用户层和内核层分别进行实现,在用户层达到的效果主要是dll模块的隐藏,在内核层达到的效果主要是进程的隐藏。
3环PEB断链
按照上方的结构分析的步骤,我们找到_PEB_LDR_DATA里的三个LIST_ENTRY结构体。
以InLoadOrderModuleList
为例
我们知道如果要枚举模块一般都是使用CreateToolhelp32Snapshot
拍摄快照,然后找到模块列表之后进行遍历,其实api也是通过找_PEB_LDR_DATA
这个结构来获取程序有哪些模块,那么我们如果想隐藏某个dll,就可以通过修改这几个双向链表的方法来进行隐藏
步骤
首先遍历所有模块,通过dllBase找到我们想隐藏的那个模块,然后将该模块前一个模块的Flink指向该模块后一个模块,后一个模块的前模块指向前一个模块,然后再将当前的模块设置为下一个模块,实现断链操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void hide_self(void *module){ void *pPEB = nullptr; _asm { push eax mov eax,fs:[0x30] mov pPEB,eax pop eax } void *pLDR = *((void**)((unsigned char*)pPEB + 0xc)); void *pCurrent = *((void**)((unsigned char *)pLDR + 0xc)); void *pNext = pCurrent; do { void *Flink = (void *)((unsigned char*)pNext); void *Blink = (void *)((unsigned char*)pNext + 0x4); void *pBaseAddress = (void *)((unsigned char*)pNext + 0x18); if (pBaseAddress == module){ *((void **)((unsigned char *)Blink + 0x0)) = Flink; *((void **)((unsigned char *)Flink + 0x4)) = Blink; pCurrent = Flink; } pNext = *((void **)pNext); }while(pCurrent != pNext); }
|
如果想隐藏所有的模块,直接把判断的那个if删掉即可。
0环PEB断链
步骤
操作系统的层面上,一个进程本质上就是一个结构体。这个结构体就是EPROCESS
在+0x088
偏移处有一个指针ActiveProcessLinks
,指向的是 _LIST_ENTRY
。它是双向链表,所有的活动进程都连接在一起,构成了一个链表
那么链表总有一个头,全局变量PsActiveProcessHead
(八个字节)指向全局链表头。这个链表跟进程隐藏有关,只要我们把想要隐藏进程对应的EPROCESS
的链断掉,就可以达到在0环进程隐藏的目的。
我们看一下PsActiveProcessHead
前四个字节指向的是下一个EPROCESS
结构,但指向的并不是EPROCESS的首地址,而是每一个进程的 _EPROCESS + 0x88
的位置
所以当我们要查询下一个进程结构时,需要 -0x88。比如当前PsActiveProcessHead
指向的下一个地址为0x863b58b8
1
| kd> dt _EPROCESS 863b58b8-0x88
|
在0x174偏移的地方存储着进程名,我们可以看到第一个EPROCESS
结构对应的是System
进程,这里0x88偏移存放的就是下一个EPROCESS
结构的地址,但是这里注意,因为这个结构的地址是指向下一个链表的地址,所以如果要得到EPROCESS
的首结构就需要-0x88
我们通过偏移得到下一个EPROCESS
结构,可以发现为smss.exe
进程
实现
那么到这里我们的思路就清晰了,通过EPROCESS
找到我们要隐藏的进程的ActiveProcessLinks
,将双向链表的值修改,就可以将我们想要隐藏的这个进程的ActiveProcessLinks
从双向链表中抹去的效果,这里的话如果在windbg里面直接使用ed
修改的话是比较方便的,但是如果要使用代码来进行修改的话就需要首先定位到EPROCESS
在ETHREAD
的0x220
偏移得到ThreadsProcess
,指向的是_EPROCESS
这个结构体
那么就可以用汇编实现找到EPROCESS
结构
1 2 3 4 5
| __asm { mov eax, fs: [0x124] ; mov eax, [eax + 0x220]; mov pEprocess, eax; }
|
首先定义一个指针指向EPROCESS
结构,并初始化指向ActiveProcessLinks
的指针
1 2
| pCurProcess = pEprocess; curNode = (PLIST_ENTRY)((ULONG)pCurProcess + 0x88);
|
然后判断通过EPROCESS
的0x174处的ImageFileName
来判断进程名是不是我们想要隐藏的进程
1 2
| ImageFileName = (PCHAR)pCurProcess + 0x174; if (strcmp(ImageFileName, "notepad.exe") == 0)
|
如果是我们想要隐藏的进程就执行断链操作
1 2 3 4 5 6 7
| curNode = (PLIST_ENTRY)((ULONG)pCurProcess + 0x88); nextNode = curNode->Flink; preNode = curNode->Blink;
preNode->Flink = curNode->Flink;
nextNode->Blink = curNode->Blink;
|
如果不是我们想要的进程就继续往下取ActiveProcessLinks
的值
完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <ntddk.h>
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path); VOID DriverUnload(PDRIVER_OBJECT driver);
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) { PEPROCESS pEprocess, pCurProcess; PCHAR ImageFileName; __asm { mov eax, fs: [0x124] ; mov eax, [eax + 0x220]; mov pEprocess, eax; } pCurProcess = pEprocess; do { ImageFileName = (PCHAR)pCurProcess + 0x174; if (strcmp(ImageFileName, "notepad.exe") == 0) { PLIST_ENTRY preNode, curNode, nextNode; curNode = (PLIST_ENTRY)((ULONG)pCurProcess + 0x88); nextNode = curNode->Flink; preNode = curNode->Blink; preNode->Flink = curNode->Flink; nextNode->Blink = curNode->Blink;
DbgPrint("断链成功!\n"); } pCurProcess = (PEPROCESS)(*(PULONG)((ULONG)pCurProcess + 0x88) - 0x88); } while (pEprocess != pCurProcess);
driver->DriverUnload = DriverUnload; return STATUS_SUCCESS; }
VOID DriverUnload(PDRIVER_OBJECT driver) { DbgPrint("驱动卸载成功\n"); }
|