Mach-O和PE二进制文件混淆工具
项目描述
这是一个用于以各种方式混淆Mach-O和PE文件的库。这些是在MacOS和Windows上用于可执行文件和共享库的格式。(如果您需要Linux的等效版本,请查看patchelf。)
Mach-O功能
一些专为支持pynativelib建议而设计的(相当专业和复杂)Mach-O混淆工具,允许以独立的wheel文件分发本地库。具体包括:
对于pynativelib库:一个工具,它接受一个dylib和一个混淆规则,并将混淆规则应用于所有导出符号。例如,它可以将导出SSL_new的库转换为导出pynativelib_openssl__SSL_new的库。同时,它还更改库ID,例如从ssl.dylib -> pynativelib_openssl__ssl.dylib(类似于install_name_tool -id)。
此外:一个创建“占位符”库的工具,该库导入上面描述的混淆库,然后将符号以原始名称重新导出。
对于想要使用pynativelib库的代码:这是一个工具,它接受一个dylib/bundle可执行文件,一个“原始”dylib列表,以及对于每个“原始”dylib,一个新的dylib名称和一个名称混淆规则。然后(a)用来自一个不存在目录的绝对导入新dylib名称替换原始dylib的导入,(b)将其标记为“弱”导入,(c)将名称混淆规则应用于从该dylib导入的所有符号,(d)将这些符号标记为在平坦命名空间中查找。
事实证明,这是MacOS链接器/加载器提供的唯一方式,可以在不知道A和B的磁盘上的相对位置直到可执行文件启动后,将dylib/bundle A链接到dylib B,同时保留避免符号冲突的常用两级别命名空间规则。我保证,一旦我有机会把它写出来,一切都会变得有意义...
Mach-O名称混淆代码的一些已知局限性
不出所料,这种修补方式与代码签名不兼容。代码不对签名做特殊处理;它们可能会被弄乱。如果您想签名您的二进制文件,那么在签名之前先进行名称混淆。
我们目前只重写了新风格的DYLD_INFO符号表(在10.5中引入),而不是(几乎是)冗余的SYMTAB/DYSYMTAB符号表。(有趣的事实:所有Mach-O二进制文件都包含其符号表的两种完全不同的表示。新的更紧凑,以节省空间,但它们仍然保留旧的表示以保持兼容性,所以……无论如何。)据我所知,现代MacOS中唯一仍然使用SYMTAB/DYSYMTAB的是dladdr,而且我认为没有人依赖于dladdr输出做任何事情?我想最坏的情况,您可能会在调试器或分析器中看到原始符号名称?但这不会是太难修复的,如果它成为问题的话。
它不对DYLD_INFO弱绑定表或弱导出做任何特殊处理。(请注意,这些与__attribute__((weak))或__attribute__((weak_import))或ld手册页中提到的任何关于“弱”的提及都没有关系——我想它们是用来实现模糊链接的。)这可能不是一个灾难性的选择,但我不是100%确定它实际上是否正确——这是Mach-O格式中极其晦涩的部分,而Mach-O本身就是相当晦涩的。幸运的是,这个功能只由C++库使用,所以我们可以先不使用它。
在名称混淆导入时,我们将任何需要名称混淆的延迟导入转换为急切导入。这是必要的,因为延迟导入占位符将导入表的内存布局硬编码在占位符汇编的即时常数中,我不愿意尝试自动重写x86-64指令。因此,我们保持延迟导入表不变(这样所有未混淆的延迟导入都可以继续使用它),并急切绑定所有混淆的导入,这样未混淆的占位符就不会被调用。
我注意到MacOS 10.12中的dyld有新的代码,它对DYLD_INFO文件中不同位出现的顺序施加了一些令人讨厌的任意限制。这只会影响以10.12作为其最小所需版本的库,所以对于试图为通用分发构建东西的人来说,这不会在一段时间内成为问题。这也并不难修复,只是意味着我们可能不得不开始做一些无意义的冗余复制文件的某些部分,只是为了确保第二个副本可以在我们更改的文件部分之后放置,这很烦人,我还没有着手处理。
在名称混淆导入时,我们不检查重新导出,它们也是一种导入。应该修复这个问题……
PE功能
一种可以读取当前链接到 foo.dll 的 PE 文件(.exe 或 .dll),并将其重写为链接到 bar.dll 的工具(类似于 Linux 上的 patchelf --replace,或在 OS X 上的 install_name_tool -change)。这有助于避免同一库不同版本之间的命名冲突。
例如,假设你有两个由不同人分别分发的 Python 扩展 A.dll 和 B.dll,它们都包含一些链接到 libgfortran-3.dll 的 Fortran 代码,因此两个包都包含了一份 libgfortran-3.dll。由于 Windows DLL 加载的方式,如果首先加载 A.dll,那么 两个 A.dll 和 B.dll 都将使用 A 的 libgfortran-3.dll 版本,而 B 的版本将被忽略。(反之亦然,如果先导入 B。)即使我在 B 加载时确保 A 的副本不在 DLL 搜索路径上,这种情况也会发生(尽管有一些关于 SxS 集合的复杂问题,但你真的不想去那里)。
这是很糟糕的,因为没有保证 B.dll 会与 A 的 libgfortran-3.dll 版本兼容(例如,A 的副本可能对于 B 来说太老了)。欢迎来到 DLL 地狱!
我们可以通过将冲突库重命名为不同的名称来避免所有这些问题,例如 libgfortran-3-for-A.dll 和 libgfortran-3-for-B.dll。但如果我们只是重命名文件,那么一切都会出错,因为 A.dll 正在寻找 libgfortran-3.dll,而不是 libgfortran-3-for-A.dll。
这就是 machomachomangler 发挥作用的地方:它允许你修补 A.dll,使其链接到 libgfortran-3-for-A.dll。然后一切都会正常工作。太好了。
这基本上解决了与私有 SxS 集合相同的问题,但更好:它更简单(没有 XML 清单),更灵活(没有对文件系统布局的挑剔要求),并且不需要阅读糟糕的 SxS 集合文档。
示例用法
$ python3 -m machomachomangler.cmd.redll A.dll A-patched.dll libgfortran-3.dll libgfortan-3-for-A.dll
在 example/ 中有一个示例,你可以试一试。例如,在 Debian 上使用 mingw-w64 跨编译器和已安装的 wine
$ cd pe-example/ $ ./build.sh + i686-w64-mingw32-gcc -shared test_dll.c -o test_dll.dll + i686-w64-mingw32-gcc test.c -o test.exe -L. -ltest_dll + i686-w64-mingw32-strip test.exe $ wine test.exe dll_function says: test_dll $ mv test_dll.dll test_dll_renamed.dll # Apparently wine's way of signalling a missing DLL is to fail silently. $ wine test.exe || echo "failed -- test_dll.dll is missing" failed -- test_dll.dll is missing $ PYTHONPATH=.. python3 -m machomachomangler.cmd.redll test.exe test-patched.exe test_dll.dll test_dll_renamed.dll # Now it works again: $ wine test-patched.exe dll_function says: test_dll
PE dll-import-switcheroo 代码的一些已知限制
命令行工具可能不够简约。
GNU objdump 有一个错误,它无法读取我们修补的 PE 文件的导入表——它只显示导入表直到遇到修补的条目,然后停止显示任何内容。(问题是 binutils 想要导入表中的所有数据都来自单个 PE 部分。)然而,我已经尝试将修补的文件提供给 Dependency Walker、wine 和 Windows 本身,它们都能很好地处理它们——所以文件是好的,这只是 objdump 中的一个错误。只是提醒一下,如果你试图使用 objdump 来检查修补是否成功,那么它几乎肯定会告诉你一个令人困惑的谎言。
不出所料,这种修补方法与代码签名不太兼容。我们尝试至少清除任何现有的签名(这样二进制文件就变成未签名的,而不是带有无效签名的签名),但这尚未经过测试。
我们不尝试处理在PE文件正常结束之后还带有数据的文件。例如,自解压存档和安装程序通常会这样。理论上这不应该是个大问题,但我在使用mingw-w64编译一个简单的.exe文件时发现,在运行strip对二进制文件进行处理之前,该工具拒绝工作,尽管在理论上这应该可以正常工作——所以可能还有一些改进的空间。
[致自己:这看起来是GNU扩展,用于将长节段名放入PE文件中,我猜它们可能用于调试格式——这里有文档说明,搜索“Coff长节段名”。可能并不难处理得更好,例如自行去除它或者甚至修复它。]
我们不尝试更新PE头校验和,因为进行这一操作的算法(名义上)是保密的,并且我被告知对于常规用户空间代码来说,实际上没有人关心它是否正确。但我的信息可能是不准确的。(注意:看起来binutils可能知道如何计算这个校验和?我不确定。)
[更新:Stefan Kanthak通知我,这个算法是众所周知的,事实上,pefile有一个MIT许可的Python实现,所以我想可能在某个时候修复这个问题。]
一般限制
仅在Python 3.4和3.5上进行了测试。可能任何Python 3都能正常工作,而Python 2肯定不行,除非做一些修改。(有很多繁琐的字节字符串处理。)
我比较懒,所以只是将整个二进制文件加载到内存中——可能还有几个副本。实际上这并不难修复(使用内存映射等),但我想这并不重要,因为谁有多个GB的Mach-O/PE图像呢??
联系方式
许可协议
现在是周六下午,我感冒了或者类似的病,我正花我的空闲时间编写软件,使一些由世界上大型公司支持的所有权操作系统——那些能够与其他设计更优的操作系统竞争的开发者——表现得更好。我的意思是,我不是说仔细研究PE/COFF规范不是件有趣的事情!但并不是那么有趣。(而且老实说,Mach-O文档简直糟糕透顶,如果它们真的存在的话。)
为了减轻我的烦恼,此软件根据自由软件基金会发布的GNU Affero通用公共许可证进行许可,许可证版本为3或(根据您的选择)任何更新的版本。有关详细信息,请参阅LICENSE.txt。
这不应该对大多数使用产生影响,因为它只影响重新分发此软件或在代表他人运行此软件的人;您可以使用此软件来操作您的BSD许可的DLL、您的专有许可的DLL或您喜欢的任何东西,这都没问题。许可证影响的是machomachomangler本身的代码;而不是您运行的代码。
然而,如果您或您的公司对此许可证有某种过敏反应,请发送电子邮件给我,我们将商定一个合适的份额。
此外,为了以防我度过这股气冲冲,请将所有贡献许可为MIT许可证。(我绝对不会切换到任何专有许可证,但可能会切换到宽松的开源许可证。)谢谢!
行为准则
请所有贡献者遵循我们行为准则,适用于所有项目空间。
项目详情
machomachomangler-0.0.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 2acbad5bd465c5f386e5ceac8a8a1939a9789f503dd84f491e046292cf6e54a6 |
|
MD5 | 2d78acc236d6c9469787191c5dbfcee2 |
|
BLAKE2b-256 | d029d9f07b12f19d0fe64c1e88bbbebe49faf710841144c5a67dba5b98f42ab6 |
machomachomangler-0.0.1-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 4fa66b8b65959399874f05a4d93abe1e49db46f71bef66a68a93e060d9ba0cb3 |
|
MD5 | 6a9efa0be78389fb20604fe09e281f34 |
|
BLAKE2b-256 | 71dffba669db568cc25eb44a726bbce1a0a40e3a1c5b10a1a57492877b79c00b |