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; /* 00h */
PVOID EnvironmentPointer; /* 1Ch */
CLIENT_ID Cid; /* 20h */
PVOID ActiveRpcHandle; /* 28h */
PVOID ThreadLocalStoragePointer; /* 2Ch */
struct _PEB *ProcessEnvironmentBlock; /* 30h */
ULONG LastErrorValue; /* 34h */
ULONG CountOfOwnedCriticalSections; /* 38h */
PVOID CsrClientThread; /* 3Ch */
struct _W32THREAD* Win32ThreadInfo; /* 40h */
ULONG User32Reserved[0x1A]; /* 44h */
ULONG UserReserved[5]; /* ACh */
PVOID WOW32Reserved; /* C0h */
LCID CurrentLocale; /* C4h */
ULONG FpSoftwareStatusRegister; /* C8h */
PVOID SystemReserved1[0x36]; /* CCh */
LONG ExceptionCode; /* 1A4h */
struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; /* 1A8h */
UCHAR SpareBytes1[0x28]; /* 1ACh */
GDI_TEB_BATCH GdiTebBatch; /* 1D4h */
CLIENT_ID RealClientId; /* 6B4h */
PVOID GdiCachedProcessHandle; /* 6BCh */
ULONG GdiClientPID; /* 6C0h */
ULONG GdiClientTID; /* 6C4h */
PVOID GdiThreadLocalInfo; /* 6C8h */
ULONG Win32ClientInfo[62]; /* 6CCh */
PVOID glDispatchTable[0xE9]; /* 7C4h */
ULONG glReserved1[0x1D]; /* B68h */
PVOID glReserved2; /* BDCh */
PVOID glSectionInfo; /* BE0h */
PVOID glSection; /* BE4h */
PVOID glTable; /* BE8h */
PVOID glCurrentRC; /* BECh */
PVOID glContext; /* BF0h */
NTSTATUS LastStatusValue; /* BF4h */
UNICODE_STRING StaticUnicodeString; /* BF8h */
WCHAR StaticUnicodeBuffer[0x105]; /* C00h */
PVOID DeallocationStack; /* E0Ch */
PVOID TlsSlots[0x40]; /* E10h */
LIST_ENTRY TlsLinks; /* F10h */
PVOID Vdm; /* F18h */
PVOID ReservedForNtRpc; /* F1Ch */
PVOID DbgSsReserved[0x2]; /* F20h */
ULONG HardErrorDisabled; /* F28h */
PVOID Instrumentation[14]; /* F2Ch */
PVOID SubProcessTag; /* F64h */
PVOID EtwTraceData; /* F68h */
PVOID WinSockData; /* F6Ch */
ULONG GdiBatchCount; /* F70h */
BOOLEAN InDbgPrint; /* F74h */
BOOLEAN FreeStackOnTermination; /* F75h */
BOOLEAN HasFiberData; /* F76h */
UCHAR IdealProcessor; /* F77h */
ULONG GuaranteedStackBytes; /* F78h */
PVOID ReservedForPerf; /* F7Ch */
PVOID ReservedForOle; /* F80h */
ULONG WaitingOnLoaderLock; /* F84h */
ULONG SparePointer1; /* F88h */
ULONG SoftPatchPtr1; /* F8Ch */
ULONG SoftPatchPtr2; /* F90h */
PVOID *TlsExpansionSlots; /* F94h */
ULONG ImpersionationLocale; /* F98h */
ULONG IsImpersonating; /* F9Ch */
PVOID NlsCache; /* FA0h */
PVOID pShimData; /* FA4h */
ULONG HeapVirualAffinity; /* FA8h */
PVOID CurrentTransactionHandle; /* FACh */
PTEB_ACTIVE_FRAME ActiveFrame; /* FB0h */
PVOID FlsData; /* FB4h */
UCHAR SafeThunkCall; /* FB8h */
UCHAR BooleanSpare[3]; /* FB9h */
} 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; // 00h
UCHAR ReadImageFileExecOptions; // 01h
UCHAR BeingDebugged; // 02h
UCHAR Spare; // 03h
PVOID Mutant; // 04h
PVOID ImageBaseAddress; // 08h
PPEB_LDR_DATA Ldr; // 0Ch
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
PVOID SubSystemData; // 14h
PVOID ProcessHeap; // 18h
PVOID FastPebLock; // 1Ch
PPEBLOCKROUTINE FastPebLockRoutine; // 20h
PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
ULONG EnvironmentUpdateCount; // 28h
PVOID* KernelCallbackTable; // 2Ch
PVOID EventLogSection; // 30h
PVOID EventLog; // 34h
PPEB_FREE_BLOCK FreeList; // 38h
ULONG TlsExpansionCounter; // 3Ch
PVOID TlsBitmap; // 40h
ULONG TlsBitmapBits[0x2]; // 44h
PVOID ReadOnlySharedMemoryBase; // 4Ch
PVOID ReadOnlySharedMemoryHeap; // 50h
PVOID* ReadOnlyStaticServerData; // 54h
PVOID AnsiCodePageData; // 58h
PVOID OemCodePageData; // 5Ch
PVOID UnicodeCaseTableData; // 60h
ULONG NumberOfProcessors; // 64h
ULONG NtGlobalFlag; // 68h
UCHAR Spare2[0x4]; // 6Ch
LARGE_INTEGER CriticalSectionTimeout; // 70h
ULONG HeapSegmentReserve; // 78h
ULONG HeapSegmentCommit; // 7Ch
ULONG HeapDeCommitTotalFreeThreshold; // 80h
ULONG HeapDeCommitFreeBlockThreshold; // 84h
ULONG NumberOfHeaps; // 88h
ULONG MaximumNumberOfHeaps; // 8Ch
PVOID** ProcessHeaps; // 90h
PVOID GdiSharedHandleTable; // 94h
PVOID ProcessStarterHelper; // 98h
PVOID GdiDCAttributeList; // 9Ch
PVOID LoaderLock; // A0h
ULONG OSMajorVersion; // A4h
ULONG OSMinorVersion; // A8h
ULONG OSBuildNumber; // ACh
ULONG OSPlatformId; // B0h
ULONG ImageSubSystem; // B4h
ULONG ImageSubSystemMajorVersion; // B8h
ULONG ImageSubSystemMinorVersion; // C0h
ULONG GdiHandleBuffer[0x22]; // C4h
PVOID ProcessWindowStation; // ???
} PEB, *PPEB;

PEB_LDR_DATA结构

1
2
3
4
5
6
7
8
9
typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c 模块加载顺序
 LIST_ENTRY InMemoryOrderModuleList; // +0x14 模块在内存中的顺序
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c 模块初始化装载顺序
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

注意结构中的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;

img

分析

定位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的加载基地址。其他俩链也差不多分析。

img

可以看到不同结构遍历的顺序是不一样的

下图是寻找LDR_DATA_TABLE_ENTRY的全部流程

img

以下是遍历顺序

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为例

img

我们知道如果要枚举模块一般都是使用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

img

+0x088偏移处有一个指针ActiveProcessLinks,指向的是 _LIST_ENTRY。它是双向链表,所有的活动进程都连接在一起,构成了一个链表

那么链表总有一个头,全局变量PsActiveProcessHead(八个字节)指向全局链表头。这个链表跟进程隐藏有关,只要我们把想要隐藏进程对应的EPROCESS的链断掉,就可以达到在0环进程隐藏的目的。

我们看一下PsActiveProcessHead

img

前四个字节指向的是下一个EPROCESS结构,但指向的并不是EPROCESS的首地址,而是每一个进程的 _EPROCESS + 0x88的位置

img

所以当我们要查询下一个进程结构时,需要 -0x88。比如当前PsActiveProcessHead指向的下一个地址为0x863b58b8

1
kd> dt _EPROCESS 863b58b8-0x88

在0x174偏移的地方存储着进程名,我们可以看到第一个EPROCESS结构对应的是System进程,这里0x88偏移存放的就是下一个EPROCESS结构的地址,但是这里注意,因为这个结构的地址是指向下一个链表的地址,所以如果要得到EPROCESS的首结构就需要-0x88

img

我们通过偏移得到下一个EPROCESS结构,可以发现为smss.exe进程

img

实现

那么到这里我们的思路就清晰了,通过EPROCESS找到我们要隐藏的进程的ActiveProcessLinks,将双向链表的值修改,就可以将我们想要隐藏的这个进程的ActiveProcessLinks从双向链表中抹去的效果,这里的话如果在windbg里面直接使用ed修改的话是比较方便的,但是如果要使用代码来进行修改的话就需要首先定位到EPROCESS

ETHREAD0x220偏移得到ThreadsProcess,指向的是_EPROCESS这个结构体

img

那么就可以用汇编实现找到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来判断进程名是不是我们想要隐藏的进程

img

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");
}