(44)MessageBoxA 监视器(过写拷贝,不使用 shellcode 注入)
一、研發背景
這是中級上保護模式和驅動開發章節的綜合練習。程序可以監視系統API調用,和三期的那個函數調用監視器不同,三期的只能HOOK本進程的API,而這個項目可以監視所有進程。
項目源碼:https://github.com/Kwansy98/MsgBoxAWatcher/tree/main
編譯環境是 VS2010 + WDK7600,運行環境是32位XP。
每個人做這個作業時都有不同的寫法,本文僅供參考,有什么疑問或建議,歡迎留言或私信,也可以加q群 1046088090 討論。
二、項目展示
程序輸出非常簡單,只要有進程調用了 MessageBoxA,監控程序就會打印。
這里并沒有把字符串也打印出來,原因有二:
一是要切換進程CR3讀字符串,再發給監控程序,工作量大(主要原因);
二是擴展性的考慮,我一開始是想監控所有user32.dll的導出函數的,所以直接傳參數的值(不經轉化)是最方便的。
三、涉及的技術
以下是老師在課堂上提到的,可能用到的主要的技術:
//1、自己寫代碼加載,卸載驅動程序
//2、段頁的知識;繞寫拷貝
//3、寫HOOK
//4、shellcode
老師說要用shellcode,是因為正常的做法是向某個系統dll注入shellcode,讓MessageBoxA執行時跳過去的,這是典型的Inline hook操作。
我的項目中沒有用這種方法,因此也就沒有涉及shellcode,以下是我在實際開發中使用的技術:
- 驅動編程
- 0環-3環通信常規方式(設備讀寫)
- 修改PTE過寫拷貝
- 調用門提權(我寫了一套API實現以系統權限調用用戶程序里的裸函數,支持傳參)
- 中斷門HOOK(這個名字是我自己想的)
- 設備擴展內存
- 雙端鏈表模擬隊列
四、3環監控程序詳解
項目源碼:https://github.com/Kwansy98/MsgBoxAWatcher/tree/main
3環程序是一個控制臺程序,執行流程是非常清晰簡單的,只做了幾件事。
加載驅動
調用封裝函數 LoadDriver ,加載驅動,通過驅動,我們可以操作高2G的內存。
LoadDriver(DRIVER_NAME, DRIVER_PATH)這個函數沒什么好說的,里面就是調用Windows API 加載驅動。
過寫拷貝
BypassApiWriteCopy();寫拷貝我在之前的博客里介紹過:https://blog.csdn.net/Kwansy/article/details/109232856
過寫拷貝最簡單的辦法就是將線性地址的 R/W 位改成1,我就是這么做的,我把這步放在3環做了。為了操作頁表,我的代碼必須有系統權限,我的做法是用調用門提權,然后調用裸函數。
為了提高代碼復用,我寫了一套API,可以很方便地以0環權限調用應用程序里的裸函數。
這部分我覺得是我在這個項目里寫的最漂亮的代碼了= =
// 以0環權限調用某個裸函數,支持傳參 BOOL CallInRing0(PVOID pFuncion, PDWORD pParam, DWORD nParam);有了這個函數,我們就可以很方便地過寫拷貝了。
// 過寫拷貝 void BypassApiWriteCopy() {// MessageBoxA 掛物理頁,不這樣操作,MessageBoxA的PTE可能是無效的__asm{mov eax, dword ptr ds:[MessageBoxA];mov eax,[eax];}// MessageBoxA過寫拷貝 DWORD pParam[3];pParam[0] = (DWORD)MessageBoxA;pParam[1] = (DWORD)GetPDE(pParam[0]);pParam[2] = (DWORD)GetPTE(pParam[0]);CallInRing0(BypassApiWriteCopyNaked, pParam,3); }HOOK MessageBoxA
HOOK這步我也放在3環完成。分為兩步:
當調用 MessageBoxA,就會觸發 0x20 中斷,跳轉到0環提供的監控函數里執行。這部分我留到待會介紹驅動的時候說。
循環打印調用記錄
HOOK 完成后,主函數進入一個死循環,不斷地從驅動里讀取 MessageBoxA 調用記錄,并打印到控制臺。當用戶輸入Q鍵,退出循環。
收尾工作
取消 HOOK,卸載驅動,打印提示程序結束的信息。
// 取消HOOK ((PUSHORT)MessageBoxA)[0] = 0xff8b; UnLoadDriver(DRIVER_NAME); printf("敲任意按鍵退出程序.\n");五、驅動程序詳解
項目源碼:https://github.com/Kwansy98/MsgBoxAWatcher/tree/main
驅動是被動響應3環監控程序的,主要功能如下:
- 為3環構造提權調用門,返回調用門描述符
- 為3環構造提權中斷門,返回中斷號
- HOOK API后,通過提權中斷門跳轉到監控函數
- 返回API調用記錄給3環
重點說一下監控函數,監控函數有兩個,一個是觸發中斷跳轉進去的裸函數,裸函數內部調用了另一個函數(裸函數里寫C代碼不方便)
// User32.dll 導出函數的鉤子函數 // 調用方式:修改API函數頭2字節,使API函數觸發中斷,通過提權中斷門調用本函數 void __declspec(naked) User32ApiSpyNaked() {__asm{pushad; // esp - 0x20pushfd; // esp - 0x04mov eax,[esp + 0x24]; mov ecx,[esp + 0x24 + 0x0C];push eax; // EIP3push ecx; // ESP3call User32ApiSpy;popfd;popad;iretd;} }// 此處需要完成的工作:讀取3環EIP,判斷API來源,讀取3環ESP,獲取參數,傳給3環控制程序 void __stdcall User32ApiSpy(UINT32 ESP3, UINT32 EIP3) {UINT32 ApiAddress;// EIP3-0x02是API的地址// ESP3是3環的ESP,可以用來讀參數__asm push fs;ApiAddress = EIP3 - 2;//DbgPrint("ESP3: %08x, API: %08x\n", ESP3, ApiAddress);// 判斷API地址if (ApiAddress == 0x77d507ea){PAPICALLRECORD pApiCallRecord = NULL;// 添加調用記錄到隊列,監視進程通過IRP消息讀取隊列pApiCallRecord = (PAPICALLRECORD)ExAllocatePool(PagedPool,sizeof(APICALLRECORD));pApiCallRecord->nParam = 4;pApiCallRecord->pApiAddress = ApiAddress;pApiCallRecord->Param[0] = ((PUINT32)ESP3)[1];pApiCallRecord->Param[1] = ((PUINT32)ESP3)[2];pApiCallRecord->Param[2] = ((PUINT32)ESP3)[3];pApiCallRecord->Param[3] = ((PUINT32)ESP3)[4];PushApiCallQueue(&g_ApiCallRecordQueue, (PAPICALLRECORD)pApiCallRecord);}__asm pop fs; }可以看到,監控函數的工作就是記錄API的地址(判斷是哪個API,MessageBoxA還是別的什么函數),以及記錄參數,這些信息都會存儲到一個隊列里,然后3環監控程序請求數據的時候,就會從隊列里彈出數據返回給3環監控程序。
為了存儲調用記錄,我定義了一個結構體和一套隊列操作的API,詳見代碼,此處不細說。
// API調用記錄 typedef struct _APICALLRECORD {LIST_ENTRY ApiCallRecordList; // 鏈表UINT32 pApiAddress; // API函數地址UINT32 nParam; // 參數個數UINT32 Param[32]; // 參數列表 } APICALLRECORD, *PAPICALLRECORD;void InitApiCallQueue(IN PAPICALLRECORD QueueHead); void PushApiCallQueue(IN PAPICALLRECORD QueueHead, IN PAPICALLRECORD pApiCallRecord); void PopApiCallQueue(IN PAPICALLRECORD QueueHead, OUT PAPICALLRECORD * pApiCallRecord); UINT32 GetCountApiCallQueue(IN PAPICALLRECORD QueueHead); void FreeApiCallQueue(IN PAPICALLRECORD QueueHead);六、開發中遇到的坑點和難點
坑點
1、DebugPrint與push fs
在裸函數內調用 DebugPrint,需要保存FS
2、驅動中使用全局變量和static變量
全局變量貌似可以用,但是static就建議不要用了,調用驅動的函數時,全局變量和static變量的地址會變,還會重新初始化,這方面細節我也不是很清楚。建議使用設備擴展內存代替。
3、MessageBoxA 線性地址 PTE 無效的處理方法
過寫拷貝前,先讀一下 MessageBoxA 里面的數據,否則 PTE 是無效的。
難點
1、3環提權調用裸函數框架
這部分主要是傳參不好寫,我用了一個DWORD數組表示參數,用一個DWORD表示參數個數,然后循環push參數。詳細請看這個函數:
2、遍歷GDT/IDT
你也可以不遍歷,比如說 0x8003f048 和 0x8003f500 這兩個就是無效的GDT和 IDT ,可以直接用,但是不保證每次都好使。遍歷的代碼在這兩個函數里:
3、驅動中實現隊列
這個隊列是用來存API調用記錄的,方法比較多,我這里用雙向鏈表模擬隊列實現,詳細請看驅動部分的代碼。
4、設備擴展內存
// 創建設備 status = IoCreateDevice(pDriver,DeviceExtendSize,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&pDeviceObj);創建設備時,第二個參數是設備擴展內存的大小,你可以在這申請一塊非分頁內存,只要設備還在,就都可以用,可以用它代替全局變量。
七、完整代碼
雖然已經上傳了github,但是還是在這里也留一個備份吧。
驅動
#include <ntifs.h> #include <wdm.h>#define DEVICE_NAME L"\\Device\\MsgBoxAWatcherDriverDev" #define DRIVER_LINK L"\\??\\MsgBoxAWatcherDriverLnk"// 申請了4KB設備擴展內存,用于替代全局變量 // 0-3字節:調用門描述符地址(GDT) // 4-7字節:中斷門描述符地址(IDT) #define DeviceExtendSize 0x1000// 3環發 IRP_MJ_DEVICE_CONTROL 的操作編號 #define OPER_CALL_GATE_R0 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define OPER_HOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS) #define OPER_GET_APICALLRECORD CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_BUFFERED,FILE_ANY_ACCESS)// 結構聲明 typedef struct _LDR_DATA_TABLE_ENTRY {LIST_ENTRY InLoadOrderLinks;LIST_ENTRY InMemoryOrderLinks;LIST_ENTRY InInitializationOrderLinks;PVOID DllBase;PVOID EntryPoint;UINT32 SizeOfImage;UNICODE_STRING FullDllName;UNICODE_STRING BaseDllName;UINT32 Flags;USHORT LoadCount;USHORT TlsIndex;LIST_ENTRY HashLinks;PVOID SectionPointer;UINT32 CheckSum;UINT32 TimeDateStamp;PVOID LoadedImports;PVOID EntryPointActivationContext;PVOID PatchInformation; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;// API調用記錄 typedef struct _APICALLRECORD {LIST_ENTRY ApiCallRecordList; // 鏈表UINT32 pApiAddress; // API函數地址UINT32 nParam; // 參數個數UINT32 Param[32]; // 參數列表 } APICALLRECORD, *PAPICALLRECORD;// 全局變量 PDEVICE_OBJECT g_pDevObj = NULL; // 自定義設備,用于和3環通信 APICALLRECORD g_ApiCallRecordQueue = { 0 }; // API調用記錄隊列,不要直接操作該鏈表,使用程序提供的API// 函數聲明 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath); VOID DriverUnload(PDRIVER_OBJECT pDriver); NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp); NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp); NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp); UINT32 *GetPDE(UINT32 addr); UINT32 *GetPTE(UINT32 addr); USHORT SetCallGate(UINT32 pFunction, UINT32 nParam); USHORT SetIntGate(UINT32 pFuncion); void User32ApiSpyNaked(); void __stdcall User32ApiSpy(UINT32 ESP3, UINT32 EIP3); void InitApiCallQueue(IN PAPICALLRECORD QueueHead); void PushApiCallQueue(IN PAPICALLRECORD QueueHead, IN PAPICALLRECORD pApiCallRecord); void PopApiCallQueue(IN PAPICALLRECORD QueueHead, OUT PAPICALLRECORD * pApiCallRecord); UINT32 GetCountApiCallQueue(IN PAPICALLRECORD QueueHead); void FreeApiCallQueue(IN PAPICALLRECORD QueueHead);// 入口函數 NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath){NTSTATUS status;ULONG uIndex = 0;PDEVICE_OBJECT pDeviceObj = NULL; // 設備對象指針UNICODE_STRING DeviceName; // 設備名,0環用UNICODE_STRING SymbolicLinkName; // 符號鏈接名,3環用// 初始化調用記錄隊列InitApiCallQueue(&g_ApiCallRecordQueue);// 創建設備名稱RtlInitUnicodeString(&DeviceName,DEVICE_NAME);// 創建設備 status = IoCreateDevice(pDriver,DeviceExtendSize,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&pDeviceObj);if (status != STATUS_SUCCESS){IoDeleteDevice(pDeviceObj);DbgPrint("創建設備失敗.\n");return status;}// 全局變量依賴于設備擴展內存// 初始化全局設備指針g_pDevObj = pDeviceObj; // 初始化設備擴展數據memset(pDeviceObj->DeviceExtension,0,DeviceExtendSize);//DbgPrint("創建設備成功.\n");// 設置交互數據的方式pDeviceObj->Flags |= DO_BUFFERED_IO;// 創建符號鏈接RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);// 設置分發函數pDriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceControlProc;// 設置卸載函數pDriver->DriverUnload = DriverUnload;return STATUS_SUCCESS; }// 卸載驅動 VOID DriverUnload(PDRIVER_OBJECT pDriver) {UNICODE_STRING SymbolicLinkName;// 刪除GDT表項中的調用門memset((PVOID)((PUINT32)(pDriver->DeviceObject->DeviceExtension))[0],0,8);// 刪除IDT表中的中斷門memset((PVOID)(((PUINT32)(pDriver->DeviceObject->DeviceExtension))[1]),0,8);// 釋放隊列內存//DbgPrint("隊列長度:%d\n", GetCountApiCallQueue(&g_ApiCallRecordQueue));FreeApiCallQueue(&g_ApiCallRecordQueue);//DbgPrint("隊列長度:%d\n", GetCountApiCallQueue(&g_ApiCallRecordQueue));// 刪除符號鏈接,刪除設備RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);IoDeleteSymbolicLink(&SymbolicLinkName);IoDeleteDevice(pDriver->DeviceObject);DbgPrint("驅動卸載成功\n"); }// 不設置這個函數,則Ring3調用CreateFile會返回1 // IRP_MJ_CREATE 處理函數 NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) {//DbgPrint("應用層連接設備.\n");// 返回狀態如果不設置,Ring3返回值是失敗pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT);return STATUS_SUCCESS; }// IRP_MJ_CLOSE 處理函數 NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) {//DbgPrint("應用層斷開連接設備.\n");// 返回狀態如果不設置,Ring3返回值是失敗pIrp->IoStatus.Status = STATUS_SUCCESS;pIrp->IoStatus.Information = 0;IoCompleteRequest(pIrp, IO_NO_INCREMENT);return STATUS_SUCCESS; }// IRP_MJ_DEVICE_CONTROL 處理函數 NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp) {NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;PIO_STACK_LOCATION pIrpStack;ULONG uIoControlCode;PVOID pIoBuffer;ULONG uInLength;ULONG uOutLength;// 獲取IRP數據pIrpStack = IoGetCurrentIrpStackLocation(pIrp);// 獲取控制碼uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;// 獲取緩沖區地址(輸入輸出是同一個)pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;// Ring3 發送數據的長度uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;// Ring0 發送數據的長度uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;switch (uIoControlCode){case OPER_CALL_GATE_R0:{UINT32 pFunction; // 3環函數指針UINT32 nParam; // 參數個數// 給3環傳進來的函數指針設置一個調用門pFunction = ((PUINT32)pIoBuffer)[0];nParam = ((PUINT32)pIoBuffer)[1];// 設置狀態,返回數據((PUSHORT)pIoBuffer)[0] = SetCallGate(pFunction, nParam); // 返回調用門選擇子pIrp->IoStatus.Information = 2; // 返回給3環的數據量status = STATUS_SUCCESS;break;}case OPER_HOOK:{// 返回給3環的中斷號,3環根據中斷號HOOK APIUSHORT IntGateNum;// 構造提權中斷門IntGateNum = SetIntGate((UINT32)User32ApiSpyNaked);// 返回中斷號*(PUSHORT)pIoBuffer = IntGateNum;// 設置狀態,返回數據pIrp->IoStatus.Information = 2; // 返回給3環的數據量status = STATUS_SUCCESS;break;}case OPER_GET_APICALLRECORD:{PAPICALLRECORD record = NULL;PopApiCallQueue(&g_ApiCallRecordQueue, &record);if (record == NULL){// 設置狀態,返回數據pIrp->IoStatus.Information = 0; // 返回給3環的數據量status = STATUS_SUCCESS;}else{memcpy(pIoBuffer, record, sizeof(APICALLRECORD));// 設置狀態,返回數據pIrp->IoStatus.Information = sizeof(APICALLRECORD); // 返回給3環的數據量status = STATUS_SUCCESS;}break;}}// 返回狀態如果不設置,Ring3返回值是失敗pIrp->IoStatus.Status = status;IoCompleteRequest(pIrp, IO_NO_INCREMENT);return STATUS_SUCCESS; }// 構造提權中斷門,返回中斷號 USHORT SetIntGate(UINT32 pFuncion) { UCHAR IDT[6]; // IDT寄存器UINT32 IdtAddr,IdtLen;UINT32 IntGateHi = 0,IntGateLo = 0; // 中斷門描述符UINT32 *pPreIntGateAddr = (UINT32*)g_pDevObj->DeviceExtension + 1;UINT32 i;// 構造中斷門描述符IntGateLo = ((pFuncion & 0x0000FFFF) | 0x00080000);IntGateHi = ((pFuncion & 0xFFFF0000) | 0x0000EE00);// 遍歷IDT,找無效項__asm{sidt fword ptr IDT;}IdtAddr = *(PULONG)(IDT+2);IdtLen = *(PUSHORT)IDT;// 遍歷IDT,找一個P=0的(跳過第一項)if ((*pPreIntGateAddr) == 0){ for (i = 8; i < IdtLen; i+=8){if ((((PUINT32)(IdtAddr + i))[1] & 0x00008000) == 0){// P=0,此處GDT表項無效,可以使用((PUINT32)(IdtAddr + i))[0] = IntGateLo;((PUINT32)(IdtAddr + i))[1] = IntGateHi;(*pPreIntGateAddr) = IdtAddr + i; break;}}}else{((PUINT32)(*pPreIntGateAddr))[0] = IntGateLo;((PUINT32)(*pPreIntGateAddr))[1] = IntGateHi;}//DbgPrint("*pPreIntGateAddr: %p.\n", *pPreIntGateAddr);//DbgPrint("INT %02X\n", (USHORT)((*pPreIntGateAddr - IdtAddr) / 8));if (*pPreIntGateAddr == 0) return 0;return (USHORT)((*pPreIntGateAddr - IdtAddr) / 8); }// 構造提權調用門,返回調用門選擇子 USHORT SetCallGate(UINT32 pFunction, UINT32 nParam) { UINT32 CallGateHi = 0,CallGateLo = 0; // 調用門描述符UCHAR GDT[6]; // GDT寄存器UINT32 GdtAddr,GdtLen;UINT32 i;UINT32 *pPreCallGateAddr = (UINT32*)g_pDevObj->DeviceExtension;// 構造調用門CallGateHi = (pFunction & 0xFFFF0000);CallGateHi |= 0x0000EC00;CallGateHi |= nParam;CallGateLo = (pFunction & 0x0000FFFF);CallGateLo |= 0x00080000;// 獲取GDT基址和大小__asm{sgdt fword ptr GDT;}GdtAddr = *(PULONG)(GDT+2);GdtLen = *(PUSHORT)GDT;// 遍歷GDT,找一個P=0的(跳過第一項)if ((*pPreCallGateAddr) == 0){ for (i = 8; i < GdtLen; i+=8){//DbgPrint("%p\n",(PUINT32)(GdtAddr + i));if ((((PUINT32)(GdtAddr + i))[1] & 0x00008000) == 0){// P=0,此處GDT表項無效,可以使用((PUINT32)(GdtAddr + i))[0] = CallGateLo;((PUINT32)(GdtAddr + i))[1] = CallGateHi;(*pPreCallGateAddr) = GdtAddr + i;break;}}}else{((PUINT32)(*pPreCallGateAddr))[0] = CallGateLo;((PUINT32)(*pPreCallGateAddr))[1] = CallGateHi;}if (*pPreCallGateAddr == 0) return 0;return (USHORT)((*pPreCallGateAddr) - GdtAddr); }// 獲取PDE UINT32 *GetPDE(UINT32 addr) {return (UINT32 *)(0xc0600000 + ((addr >> 18) & 0x3ff8)); }// 獲取PTE UINT32 *GetPTE(UINT32 addr) {return (UINT32 *)(0xc0000000 + ((addr >> 9) & 0x7ffff8)); }// User32.dll 導出函數的鉤子函數 // 調用方式:修改API函數頭2字節,使API函數觸發中斷,通過提權中斷門調用本函數 void __declspec(naked) User32ApiSpyNaked() {__asm{pushad; // esp - 0x20pushfd; // esp - 0x04mov eax,[esp + 0x24]; mov ecx,[esp + 0x24 + 0x0C];push eax; // EIP3push ecx; // ESP3call User32ApiSpy;popfd;popad;iretd;} }// 此處需要完成的工作:讀取3環EIP,判斷API來源,讀取3環ESP,獲取參數,傳給3環控制程序 void __stdcall User32ApiSpy(UINT32 ESP3, UINT32 EIP3) {UINT32 ApiAddress;// EIP3-0x02是API的地址// ESP3是3環的ESP,可以用來讀參數__asm push fs;ApiAddress = EIP3 - 2;//DbgPrint("ESP3: %08x, API: %08x\n", ESP3, ApiAddress);// 判斷API地址if (ApiAddress == 0x77d507ea){PAPICALLRECORD pApiCallRecord = NULL;// 添加調用記錄到隊列,監視進程通過IRP消息讀取隊列pApiCallRecord = (PAPICALLRECORD)ExAllocatePool(PagedPool,sizeof(APICALLRECORD));pApiCallRecord->nParam = 4;pApiCallRecord->pApiAddress = ApiAddress;pApiCallRecord->Param[0] = ((PUINT32)ESP3)[1];pApiCallRecord->Param[1] = ((PUINT32)ESP3)[2];pApiCallRecord->Param[2] = ((PUINT32)ESP3)[3];pApiCallRecord->Param[3] = ((PUINT32)ESP3)[4];PushApiCallQueue(&g_ApiCallRecordQueue, (PAPICALLRECORD)pApiCallRecord);}__asm pop fs; }// 初始化隊列 void InitApiCallQueue(IN PAPICALLRECORD QueueHead) {QueueHead->ApiCallRecordList.Flink = QueueHead->ApiCallRecordList.Blink = (PLIST_ENTRY)QueueHead; }// 插入一條調用記錄到隊尾 void PushApiCallQueue(IN PAPICALLRECORD QueueHead, IN PAPICALLRECORD pApiCallRecord) {// 原隊尾的下一個節點指向新隊尾QueueHead->ApiCallRecordList.Blink->Flink = (PLIST_ENTRY)pApiCallRecord;// 新隊尾的上一個節點指向原隊尾pApiCallRecord->ApiCallRecordList.Blink = QueueHead->ApiCallRecordList.Blink;// 新隊尾的下一個節點指向隊列頭pApiCallRecord->ApiCallRecordList.Flink = (PLIST_ENTRY)QueueHead;// 隊列頭的上一個節點指向新隊尾QueueHead->ApiCallRecordList.Blink = (PLIST_ENTRY)pApiCallRecord; }// 從隊首彈出一條調用記錄 void PopApiCallQueue(IN PAPICALLRECORD QueueHead, OUT PAPICALLRECORD * pApiCallRecord) {// 記錄要彈出的節點*pApiCallRecord = (PAPICALLRECORD)(QueueHead->ApiCallRecordList.Flink);// 如果隊列為空,返回NULLif (*pApiCallRecord == &g_ApiCallRecordQueue){*pApiCallRecord = NULL;}// 第二個節點的上一個節點指向隊首QueueHead->ApiCallRecordList.Flink->Flink->Blink = (PLIST_ENTRY)QueueHead;// 隊首的下一個節點指向第二個節點QueueHead->ApiCallRecordList.Flink = QueueHead->ApiCallRecordList.Flink->Flink; }// 計算隊列長度 UINT32 GetCountApiCallQueue(IN PAPICALLRECORD QueueHead) {UINT32 cnt = 0;PLIST_ENTRY pList = QueueHead->ApiCallRecordList.Flink;while (pList != (PLIST_ENTRY)QueueHead){pList = pList->Flink;cnt++;}return cnt; }// 釋放隊列內存 void FreeApiCallQueue(IN PAPICALLRECORD QueueHead) {PAPICALLRECORD pApiCallRecord;while(QueueHead->ApiCallRecordList.Flink != (PLIST_ENTRY)QueueHead){ PopApiCallQueue(QueueHead, &pApiCallRecord);ExFreePool(pApiCallRecord);} }監控程序
// MsgBoxAWatcher_Ring3.cpp : 定義控制臺應用程序的入口點。 // //1、自己寫代碼加載,卸載驅動程序 //2、段頁的知識;繞寫拷貝 //3、寫HOOK //4、shellcode#include "stdafx.h" #include <Windows.h>#define DRIVER_NAME L"MsgBoxAWatcher_Ring0" #define DRIVER_PATH L"MsgBoxAWatcher_Ring0.sys" #define DRIVER_LINK L"\\\\.\\MsgBoxAWatcherDriverLnk"#define OPER_CALL_GATE_R0 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define OPER_HOOK CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS) #define OPER_GET_APICALLRECORD CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_BUFFERED,FILE_ANY_ACCESS)// API調用記錄父類 typedef struct _APICALLRECORD {LIST_ENTRY ApiCallRecordList; // 鏈表UINT32 pApiAddress; // API函數地址UINT32 nParam; // 參數個數UINT32 Param[32]; // 參數列表 } APICALLRECORD, *PAPICALLRECORD;BOOL LoadDriver(PCWSTR lpszDriverName, PCWSTR lpszDriverPath); void UnLoadDriver(PCWSTR lpszDriverName); DWORD *GetPDE(DWORD addr); DWORD *GetPTE(DWORD addr); USHORT CreateCallGate(DWORD pBaseAddress, DWORD nParam); BOOL CallInRing0(PVOID pFuncion, PDWORD pParam, DWORD nParam); void BypassApiWriteCopyNaked(); void BypassApiWriteCopy(); BOOL HookUser32Api(); void UpdateApiCallRecord();int _tmain(int argc, _TCHAR* argv[]) { // 加載驅動if (!LoadDriver(DRIVER_NAME, DRIVER_PATH)){printf("驅動服務加載失敗.\n");getchar();return 1;}else{printf("驅動服務加載成功.\n");}// 過寫拷貝BypassApiWriteCopy();// HOOK MessageBoxAif (HookUser32Api()){printf("HOOK MessageBoxA 成功,現在可以在其他程序里調用 MessageBoxA.\n"); }else{printf("HOOK MessageBoxA 失敗.\n");}// 讀取調用記錄UpdateApiCallRecord();// 取消HOOK((PUSHORT)MessageBoxA)[0] = 0xff8b;UnLoadDriver(DRIVER_NAME);printf("敲任意按鍵退出程序.\n");getchar();return 0; }// 加載驅動 BOOL LoadDriver(PCWSTR lpszDriverName, PCWSTR lpszDriverPath) {// 獲取驅動完整路徑WCHAR szDriverFullPath[MAX_PATH] = { 0 };GetFullPathNameW(lpszDriverPath,MAX_PATH,szDriverFullPath,NULL);//printf("%s\n", szDriverFullPath);// 打開服務控制管理器SC_HANDLE hServiceMgr = NULL; // SCM管理器句柄 hServiceMgr = OpenSCManagerW(NULL,NULL,SC_MANAGER_ALL_ACCESS);if (NULL == hServiceMgr){printf("OpenSCManagerW 失敗, %d\n", GetLastError());return FALSE;}//printf("打開服務控制管理器成功.\n");// 創建驅動服務SC_HANDLE hServiceDDK = NULL; // NT驅動程序服務句柄hServiceDDK = CreateServiceW(hServiceMgr,lpszDriverName,lpszDriverName,SERVICE_ALL_ACCESS,SERVICE_KERNEL_DRIVER,SERVICE_DEMAND_START,SERVICE_ERROR_IGNORE,szDriverFullPath,NULL,NULL,NULL,NULL,NULL);if (NULL == hServiceDDK){DWORD dwErr = GetLastError();if (dwErr != ERROR_IO_PENDING && dwErr != ERROR_SERVICE_EXISTS){printf("創建驅動服務失敗, %d\n", dwErr);return FALSE;}}//printf("創建驅動服務成功.\n");// 驅動服務已經創建,打開服務hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);if (!StartService(hServiceDDK, NULL, NULL)){DWORD dwErr = GetLastError();if (dwErr != ERROR_SERVICE_ALREADY_RUNNING){printf("運行驅動服務失敗, %d\n", dwErr);return FALSE;}}//printf("運行驅動服務成功.\n");if (hServiceDDK){CloseServiceHandle(hServiceDDK);}if (hServiceMgr){CloseServiceHandle(hServiceMgr);}return TRUE; }// 卸載驅動 void UnLoadDriver(PCWSTR lpszDriverName) {SC_HANDLE hServiceMgr = OpenSCManagerW(0,0,SC_MANAGER_ALL_ACCESS);SC_HANDLE hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);SERVICE_STATUS SvrStatus;ControlService(hServiceDDK,SERVICE_CONTROL_STOP,&SvrStatus);DeleteService(hServiceDDK);if (hServiceDDK){CloseServiceHandle(hServiceDDK);}if (hServiceMgr){CloseServiceHandle(hServiceMgr);} }// 獲取PDE DWORD *GetPDE(DWORD addr) {return (DWORD *)(0xc0600000 + ((addr >> 18) & 0x3ff8)); }// 獲取PTE DWORD *GetPTE(DWORD addr) {return (DWORD *)(0xc0000000 + ((addr >> 9) & 0x7ffff8)); }// 構建調用門(提權、有參) USHORT CreateCallGate(DWORD pBaseAddress, DWORD nParam) {HANDLE hDevice = CreateFileW(DRIVER_LINK,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);if (hDevice == INVALID_HANDLE_VALUE){return 0;}USHORT CallGateDescriptor; // 調用門選擇子DWORD dwRetBytes; // 返回的字節數DWORD InBuffer[2];InBuffer[0] = pBaseAddress;InBuffer[1] = nParam;DeviceIoControl(hDevice,OPER_CALL_GATE_R0,InBuffer,8,&CallGateDescriptor,sizeof(USHORT),&dwRetBytes,NULL);if (dwRetBytes != 2 || CallGateDescriptor == 0){printf("構造調用門失敗.\n");return 0;}CloseHandle(hDevice);return CallGateDescriptor; }// 以0環權限調用某個裸函數,支持傳參 BOOL CallInRing0(PVOID pFuncion, PDWORD pParam, DWORD nParam) {// 命令驅動構建調用門USHORT CallGateDescriptor = CreateCallGate((DWORD)pFuncion,nParam);if (CallGateDescriptor == 0){return FALSE;}// 構造調用門描述符USHORT buff[3] = {0};buff[2] = CallGateDescriptor;// 參數壓棧if (nParam && pParam){for (DWORD i = 0; i < nParam; i++){__asm{mov eax,pParam;push [eax];}pParam++;}} // 調用門調用__asm call fword ptr [buff]; // 長調用,使用調用門提權return TRUE; }// API函數過寫拷貝,其實就是將函數線性地址的PDE,PTE改成可寫 // 參數0:要過寫拷貝的函數地址 // 參數1:PDE線性地址 // 參數2:PTE線性地址 void __declspec(naked) BypassApiWriteCopyNaked() {__asm{pushad;pushfd;}__asm{ // R/W = 1, U/S = 1mov eax,[esp+0x24+0x8+0x0]; // 參數2,PTE的地址or dword ptr [eax],0x00000006;mov eax,[esp+0x24+0x8+0x4]; // 參數1,PDE的地址or dword ptr [eax],0x00000006;mov eax,[esp+0x24+0x8+0x8]; // 參數0,要過寫拷貝的函數地址invlpg [eax]; // 清除TLB緩存}__asm{popfd;popad;retf 0xC;} }// 過寫拷貝 void BypassApiWriteCopy() {// MessageBoxA 掛物理頁,不這樣操作,MessageBoxA的PTE可能是無效的__asm{mov eax, dword ptr ds:[MessageBoxA];mov eax,[eax];}// MessageBoxA過寫拷貝 DWORD pParam[3];pParam[0] = (DWORD)MessageBoxA;pParam[1] = (DWORD)GetPDE(pParam[0]);pParam[2] = (DWORD)GetPTE(pParam[0]);CallInRing0(BypassApiWriteCopyNaked, pParam,3); }// HOOK MessageBoxA // 理論上可以 HOOK User32.dll 里的任意函數 BOOL HookUser32Api() {HANDLE hDevice = CreateFileW(DRIVER_LINK,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);if (hDevice == INVALID_HANDLE_VALUE){printf("打開設備失敗.\n");return FALSE;}USHORT IntGateNum; // 中斷號DWORD dwRetBytes; // 返回的字節數DeviceIoControl(hDevice,OPER_HOOK,NULL,0,&IntGateNum,sizeof(USHORT),&dwRetBytes,NULL);if (dwRetBytes != 2 || IntGateNum == 0){printf("構造中斷門失敗.\n");return FALSE;}CloseHandle(hDevice);// HOOK MessageBoxAUSHORT IntInstructions = (IntGateNum << 8);IntInstructions |= (USHORT)0x00CD; *(PUSHORT)MessageBoxA = IntInstructions;return TRUE; }// 從驅動獲取調用記錄 void UpdateApiCallRecord() {HANDLE hDevice = CreateFileW(DRIVER_LINK,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);if (hDevice == INVALID_HANDLE_VALUE){printf("打開設備失敗.\n");return;}APICALLRECORD ApiCallRecord;DWORD dwRetBytes; // 返回的字節數while (!GetAsyncKeyState('Q')){Sleep(50);DeviceIoControl(hDevice,OPER_GET_APICALLRECORD,NULL,0,&ApiCallRecord,sizeof(ApiCallRecord),&dwRetBytes,NULL);if (dwRetBytes == 0) {//printf("無API調用記錄.\n");continue;}if (ApiCallRecord.pApiAddress == (DWORD)MessageBoxA){printf("MessageBoxA(%x, %x, %x, %x);\n", \ApiCallRecord.Param[0],ApiCallRecord.Param[1],ApiCallRecord.Param[2],ApiCallRecord.Param[3]);}}CloseHandle(hDevice); }總結
以上是生活随笔為你收集整理的(44)MessageBoxA 监视器(过写拷贝,不使用 shellcode 注入)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 共享内存+Shellcode实现跨进程调
- 下一篇: (46)分析 INT 0x2E 和 sy