这是一个具有大量深度的主题,具体取决于您想达到的疯狂程度,但我会尽量将其分解为多个块,您可以根据需要查看每个块。我将提到这些步骤背后的理论以及有关如何机械地执行这些步骤的一些细节。
通常打包机会做这些事情:
- 首先,您将获取一些可执行代码并使用可逆算法将其转换为数据块。代码可以是一个函数,也可以是一个完整的程序。一个函数很简单,整个程序需要更多的工作。
- 您将把 datablob 嵌入到打包程序可以访问的地方。您可以将其作为资源嵌入到程序中,从 Internet 下载,或使用自定义程序将有效负载手动插入到打包程序的可执行文件中。
- 在运行时,您的打包程序将找到数据块,运行反向算法并将结果复制到可执行缓冲区(通常是VirtualAlloc、VirtualProtect或这些函数调用的NTDLL 函数之一)。如果您将有效负载作为资源嵌入,您将使用标准 API 之一来检索它。如果您已手动将有效负载嵌入到打包程序的可执行文件中,您将需要有某种签名方法可供打包程序进行打包和查找。一般你会在运行时调用GetModuleHandle来获取自己的基地址,然后你就可以在内存中解析你的packer的PE文件,自己找到datablob
话虽如此,因为您只想深入了解它们的工作原理以及寻找什么我能想到的让您编写的最简单的事情可能是:
- 编写一小段汇编来做一些微不足道的事情(添加 2 个数字等)并将这些字节复制到某处
- 编写一个简单的 XOR 混淆器来获取这些字节并将它们转换为不是代码的东西
- 使用我链接的资源 API 将混淆代码作为文件嵌入到打包程序中
- 在运行时让打包程序获取资源,对字节进行异或运算,将它们复制到可执行缓冲区并调用该函数。
对于第 4 步,为了编写函数,您需要能够编写符合标准调用约定的汇编代码。MSDN这里有一篇关于它们的文章。通常,您需要执行一些操作,例如使用 VirtualAlloc 创建一个可执行缓冲区,并使用 typedefs 将该可执行缓冲区视为函数调用。Typedefs 还可以让您指定调用约定。
如果您想编写和打包执行一些更复杂功能的单个函数(还不是整个程序......),那么我建议您查看位置无关代码。互联网上有大量关于如何在开发环境中使用 PIC 的示例,但该技术也适用于此。大多数示例将使用汇编语言,但您也可以通过编写一个将函数指针作为参数的函数,用 C 语言编写 PIC 代码。您将使用 LoadLibrary、GetModuleHandle 和 GetProcAddress 找到指针的函数地址。
现在,真正有趣的是,如果您想编写一个打包程序,它可以获取整个可执行映像(PE 文件)并执行我上面提到的操作。如果是这种情况,您仍然需要进行混淆并嵌入可执行文件,但为了使加壳程序正常工作,您还必须了解足够的 PE 映像来设置一个“正常”的执行环境打包的程序。如果这是您想要做的,我会查看在内存中手动加载 DLL 的教程,因为它们必须解决许多与您的打包程序相同的问题。您可以从反射 DLL 注入的参考开始,然后从那里开始。
在非常广泛的细节中,当您编译程序时,会在称为导入地址表的内容中嵌入信息,该信息告诉操作系统您的程序需要运行哪些 DLL 以及您将使用这些 DLL 中的哪些函数。作为支持您的执行环境的操作系统的一部分,它确保在您的地址空间中加载任何必要的 DLL,然后通过程序的 PE 文件在运行时为您使用的任何 API 填充正确的函数地址。
所以基本上,从开发的角度来看,如果您想使用代码来创建套接字,您将包含 Winsock2.h 并将 Ws2_32.lib 添加到您的链接器依赖项。当您的程序启动时,操作系统将看到嵌入在您的 PE 文件中的此信息,并将 Ws2_32.dll 加载到您的进程地址空间中,进入您的 IAT 并更新占位符函数指针以指向加载的 WSASocket(对于例子)。作为打包者,您需要为目标程序执行所有这些操作,以便拥有一个“正常”的环境。解析 PE 是乏味的,但不是很困难,在 DLL 注入引用之间,应该有大量关于如何执行此操作的信息可供您使用。
一旦你完成了这些步骤,你就可以看到加壳程序所做的更疯狂的事情,比如反调试、自定义导入表、嵌入信息的高级技术等。