在 macOS 下构造 App,实现对另一 App 的代码注入

前言

在 macOS 的逆向中,除了直接修改二进制文件外,针对使用 Objective-C 语言的原生 App,编写动态链接库来实现将代码逻辑动态注入到 App,也是一种逆向方式。将动态链接库注入到 App,一般通过两种方式,一种是修改 Mach-O 文件的 load command,而另外一种就是在运行 App 的二进制文件时,添加 DYLD_INSERT_LIBRARIES 环境变量,让 dyld 来加载我们的动态库。

利用 DYLD_INSERT_LIBRARIES 这个特性,我们可以自己构造一个全新的 App 来完成代码注入。当运行这个注入 App 之后,就会把我们的动态库,注入到指定的 App 中。目前常见的 macOS 的逆向的项目中,大多是提供一个 shell 脚本,这个脚本通过 ‘insert_dylib’ 或者类似的工具,修改原 App 的可执行文件来实现代码注入的。而通过构造动态注入 App 的方式,可以避免对原 App 的修改,对原 App 没有任何影响。如果用户通过注入 App 来打开,则可以运行我们修改后的代码,而如果打开的是原 App, 则是 App 本身的代码,而且只要原 App 升级后没有影响到我们 hook 的函数,用户可以正常对 App 进行升级。

App 文件结构

macOS 系统(包括 iOS 系统)的 App 文件,其实是一个文件夹,里面按照指定的格式,放置了 App 所需要的所有文件,比如可执行文件、图片资源、动态库等等。打开 Finder,在 Applications 文件夹中随便找一个后缀是 .app 的文件点击鼠标右键,选择 Show Package Contents,就能看到其中的内容了。在这些文件中, Contents/MacOS 文件夹里,放置的就是 App 的可执行文件, Contents/Frameworks 中,放置了 App 自带的一些动态库,而我们就从这两个地方着手,构建我们的注入 App。

生成注入 App

对一个 App 来说,可执行文件不一定非要是使用源代码编译链接后生成的,一个拥有执行权限的 shell 脚本,也是可以的。接下来,以注入 QQ 这个 App 为例来说明如何生成一个注入 App。

动态链接库

新建一个 macOS 下的动态库工程,最终生成名为 libQQInject.dylib 的动态链接库。为了演示,这个库的作用仅仅是在 App 打开后,在控制台输出一个 log,证明动态库已经运行起来。代码如下:

__attribute__((constructor)) void myentry() {
    NSLog(@"QQ is Injected successfully!!!");
}

创建 App

新建一个文件夹,命名为 QQInject.app,并创建一个子文件夹,名字叫 Contents

拷贝动态库

QQInject.app/Contents 下创建一个名为 Frameworks 的文件夹,将动态链接库 libQQInject.dylib 拷贝到这个文件夹中。

编写启动脚本

QQInject.app/Contents 下新建文件夹 MacOS,然后在这个文件夹下新建一个 shell 脚本文件,名字为 QQInject

注意:这个步骤中,脚本文件的名字必须与 App 名字保持一致。

以下是 shell 脚本内容:

#!/bin/sh
CurrentAppPath=$(cd $(dirname $0) && cd .. && pwd)
DYLD_INSERT_LIBRARIES=${CurrentAppPath}/Frameworks/libQQInject.dylib /Applications/QQ.app/Contents/MacOS/QQ

注意: 这里默认了 QQ 的 App 安装在了 /Applications 路径下

使用以下命令为脚本增加可执行权限

chmod +x QQInject.app/Contents/MacOS/QQInject

优化

至此,注入 App 已经可以运行了,试着双击以下这个新生成的 App,然后你会发现 QQ 已经运行起来,同时在系统的 console 中,可以找到一条日志:

QQ is Injected successfully!!!

注入成功,但是同时你会发现一些问题。接下来继续做优化

系统 Dock 图标

首先发现的一个问题就是,App 运行后,系统 Dock 图标不是 QQ 的图标,而是一个默认的应用图标。解决这个问题办法就是在 QQInject.app/Contents 下新建一个 Info.plist 文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>LSUIElement</key>
	<true/>
</dict>
</plist>

再次运行 QQInject.app 后,系统 Dock 上已经是 QQ 的图标了。

脚本化

上面的整个过程都是固定的流程,可以编写一个 shell 脚本来完成从动态库 build 到 注入 App 生成的整个过程。

最后

本文只是提供一个代码注入更加方便使用的思路,为了把逆向的结果更方便的使用。其中很多地方的逻辑并不是很严谨,比如被注入 App 不一定就安装在 /Applications 路径下,这就交给大家来自己写一个逻辑来判断啦~~

参考资料

4 个赞

感谢!改后的标题确实更好了。 @snakeninny

1 个赞

很多(两年前)有个叫Parasite的项目从内核态劫持并Patch二进制文件来实现同样的功能。私心认为那会不会是更好的方案。这个思路也挺好,不过个人的话不是很喜欢这样带来的overhead

有Parasite这个项目的地址吗还

1 个赞

按你这个教程 双击运行QQInject.app 提示:您不能打开应用程序“QQInject”,因为它可能已损坏或不完整。

单独双击 QQInjectshell脚本 能将qq运行起来 但是在日志中并没有看见注入的日志 哪里不对吗?