QiuChenly 发表于 2023-1-27 22:06

AirBuddy2 2.6.3 的dylib注入方案 (2)

本帖最后由 QiuChenly 于 2023-1-27 22:18 编辑

# AirBuddy2 2.6.3 的dylib注入方案(2)
## 上篇概要
妙控键盘2电源开关坏了,不知道怎么修,就不写了。
有兴趣的直接翻我帖子上一篇看。

就这个小开关,全网找不到卖的。有知道怎么修的朋友给个提示。
现在关机没用,拨回去按两下又通电了,好烦。放包里严重误按。

## 准备工具
1. AppCode(需要预装XCode).
2. 编译好的insert_dylib二进制执行文件.
3. IDA 7.0 Mac.
4. VSCode + Frida.
5. 关闭Mac上的SIP以注入进程内存.
6. 人.

IDA在论坛资源区下载,AppCode不会用可以直接用XCode。
Frida不会用可以百度,TypeScript看不懂可以百度。

insert_dylib源代码下载地址:
(https://github.com/Tyilo/insert_dylib)

注入我用的是rd_route劫持库.
h文件和c文件下载(后面用到):
https://github.com/rodionovd/rd_route

注意:
楼主没写过macOS App,也不会写。以下发表的所有Hook理解也是根据百度搜索到的资料理解的,如果有理解错误的地方请指正。

关于评论区有朋友问别人拿我的成果卖钱/伪装第一作者/不注明来源偷盗转载的看法:
```
偷盗转载卖钱狗nm似了捏。
我希望这是极少部分人,如果所有人都在盗取别人的研究结果,而没有人愿意分享,那么这对整个行业是一个沉重的打击。
众所周知,原创凉的快,火的都是抄袭者。但是没有原创,那么也就没有人能学到知识。即使对我没有任何好处,我也愿意做一个分享的人。

但是很可惜,上面那些屁话糊弄糊弄刚毕业的还行,毕业三年看遍世间百态,皆为追名逐利之徒,把别人自由分享的东西据为己有变现,偷盗转载提升个人名气的宵小之徒比比皆是。毕竟狗改不了吃屎。

以上种种人间丑态,任世事纷扰,我自且听风吟,一笑置之。
```
关于xxxx的看法省流:
```
偷盗转载卖钱狗nm4了。
```

## 直入主题
### 第一步 寻找特征
这次我们不破坏原始文件二进制,使用dylib注入Framework文件的方式劫持内存实施运行时代码注入达到修改目标函数的目的。
其缺点是必须要关闭SIP,否则无法通过App文件的完整性检查。
不破坏App的原始签名可以绕过一些权限问题,如后面释出的CleanMyMac X 4.12.3破解后无限弹窗问题。

打开软件寻找目标。

Your copy of AirBuddy is not activated.
这行字我们去搜一下。
我们直接搜索整个app文件里面:


好 我们已经找到结果,下面就是根据字符串的引用key去ida搜索了。

此时多个文件我们怎么检查呢?

注意看,这些文件是一个framework和一个xpc文件中存在的。xpc就不多说了,就没有意义。我们直接重点关注AirCore.framework文件。


可以看到字符串引用key就是“LICENSE_STATE_NOT_ACTIVATED”。

我们直接在IDA中打开此文件搜索:



我们查到了索引,那么这里很可能就是调用方了。跳过去看下:

继续跳!
来到函数6B4C0.


继续查找:

有两个调用方,那么我们看看第一个:

事情此时变得有趣了起来:这个一看就很不对劲的函数没有任何调用方!
下面那个函数也是如此,那么他是如何被调用的呢?

由于这不是在主进程中,所以我们大胆假设:这是一个导出函数!

我们到Exports里面复制一部分的文字搜索,果然。

那么问题来了,哪里引用了呢?

我们直接搜主程序试试:
(主程序: /Applications/AirBuddy.app/Contents/MacOS/AirBuddy)

果然不出所料。

我们继续跟进去看看他是如何被调用的:

到这里我们继续在函数名上按下X查找引用:


又跳到了一个符号表:


继续跳:

终于逮住了。

我们继续追查:

原来如此。我们F5看下伪代码:

他是来自一个叫100050480的函数调用:


在这个函数的入口中,a1所在的r13寄存器对内存+153的偏移处进行判断,如果不等于1那么执行这里的A1F78函数(101行),否则执行刚刚找到的NotActivated。

而且注意最上面一个return sub_100017DF0(v19,v20),也就是说如果这个内存偏移地址的值不等于1就会返回这个函数的返回值而不执行下面的NotActivated。也就是界面上红色的X。

那么我们可以算是找到爆破点了,但是我们不打算用暴力修改的方式,我们今天试一下内存注入。

打开最爱的VSCode,打开frida,开始测试注入结果!

### 第二步 注入内存修改内存指针所处的值
python文件新建,写入如下:


然后我们要准备一个_index.js,否则就没戏唱了。
那么有玩frida比较多的朋友可能会问,为什么不用frida -f xxx启动这个app进程 还要python这么麻烦?

解答:
frida在macOS上的inlinehook有问题,会造成app挂起无法恢复运行状态导致超时,系统强制终止进程。因为系统认为app卡死了。启动失败嘛。

下面我们看下ts文件:

其中,Utils.ts的代码如下:
```ts
export function log(...message: any): void {
    console.log(...message);
}

enum AccessType {
    Pointer,
    ObjectCFunction,
}

export type TargetFunction = {
    target: NativePointerValue,
    accessType: AccessType,
    msgTag: string//打印的日志标记
}

export type Tools = {
    watchMemory: (target: TargetFunction) => void;
}

export function HookApp(module: string, hookStart: (
    hook: (
      target: TargetFunction,
      leave: (
            ths: InvocationContext,
            retval: InvocationReturnValue
      ) => void,
      enter?: (
            ths: InvocationContext,
            args: InvocationArguments
      ) => void,
    ) => void,
    getPointer: (offsetMemmory: string | number | NativePointerValue | UInt64 | Int64) => TargetFunction,
    getClassMethod: (clazzName: string, clazzMethodSign: string) => TargetFunction,
    appBaseAddr: NativePointer,
    tools: Tools
) => void) {
    var appBaseAddr = Module.findBaseAddress(module)
    log(`App [${module}] 内存基址: ${appBaseAddr}`)
    let init = (appBaseAddr: NativePointer) => {
      let hook = (
            target: TargetFunction,
            leave: (ths: InvocationContext, retval: InvocationReturnValue) => void,
            enter?: (ths: InvocationContext, args: InvocationArguments) => void
      ) => {
            attachTarget(target, appBaseAddr, enter, leave, module)
      }
      hookStart(
            hook,
            (offsetMemmory: string | number | NativePointerValue | UInt64 | Int64) => {
                return {
                  target: appBaseAddr.add(offsetMemmory),
                  msgTag: "0x" + offsetMemmory.toString(16),
                  accessType: AccessType.Pointer
                }
            },
            (clazzName: string, clazzMethodSign: string) => {
                return {
                  target: ObjC.classes.implementation,
                  accessType: AccessType.ObjectCFunction,
                  msgTag: clazzName + " [" + clazzMethodSign + "]"
                }
            },
            appBaseAddr,
            {
                watchMemory: (target) => {
                  MemoryAccessMonitor.enable({
                        base: new NativePointer(target.target),
                        size: 16
                  }, {
                        onAccess: function (details) {
                            log("对内存[" + target.msgTag + "]访问来自", (details.from.sub(appBaseAddr)).toString())
                        }
                  })
                }
            }
      )
    }
    if (appBaseAddr != null) init(appBaseAddr)
}

function attachTarget(
    target: TargetFunction,
    appBaseAddr: NativePointer,
    enter?: (
      ths: InvocationContext,
      args: InvocationArguments
    ) => void,
    leave?: (
      ths: InvocationContext,
      retval: InvocationReturnValue
    ) => void,
    module?: string) {
    var line = "=".repeat(32);
    Interceptor.attach(target.target, {
      onEnter(this: InvocationContext, args: InvocationArguments) {
            log(line + ` \n${module} 进入函数 ` + target.msgTag + "\n")
            enter?.(this, args)
            log(line, "\n")
      },
      onLeave(retval) {
            log(line + ` \n${module} 退出函数 ` + target.msgTag + "\n")
            log("修改前返回值", retval)
            leave?.(this, retval)
            log("修改后返回值", retval, "\n" + line, "\n")
      },
    });
}
```

然后编译ts到js文件的指令:

build是编译,-w是监视文件修改自动编译。
我们执行watch的指令即可自动编译了。

```
注意:
关于nodejs前端代码的问题请不要发问,不会解答任何关于前端js的问题,因为我默认你会使用并知晓以上简单的基本js知识。

实在不会可以百度。
```

准备工作完成,那么我们开始hook函数`__int64 __usercall sub_100050480@<rax>(__int64 a1@<r13>)`看看:

我们把可执行文件的名称写在参数1中,这样就会自动锁定内存里这个同名进程进行注入。

然后我们获取这个指针所在的地址,开始进入函数的挂钩:

启动python文件,我们可以看到已经对函数成功挂钩。
那么前面我们知道他是对内存偏移进行判断:

其实到这个时候也不怕你们知道, v28 = sub_1000A1F78(); 函数sub_1000A1F78进去一看:

再回到AirCore文件里一搜:

函数6B860进去看下:


搜一下key发现是已经激活的提示文字。

那么此时回到AirBuddy文件,我们整个函数已经一目了然:
if ( *(a1 + 153) != 1 )
{
    已经激活
} else {
    需要激活
}

那么我们还犹豫什么?
只要在函数进入之前修改*(a1 + 153)的值即可。
但是怎么修改呢?
别急,我们看下反汇编:

cmp就是这个判断,我们要修改的就是r13+99h内存指针的值为0即可。

回到Frida,我们首先已经挂上钩子了:

所以我们先拿到CPU寄存器此时r13的指针,然后add(153),99H就是十进制的153.
先读一下内存,发现是1,那么按正常逻辑我们会发现确实和if ( *(a1 + 153) != 1 )条件不成立,所以未激活。
那么我们就狠狠的writeInt(0)!
保存,自动编译为_index.js,然后运行python文件让他自动注入app:


绿绿的,很喜欢。
是不是很简单?大家都学会了吧?下面我们用object-c写一遍去持久化注入App。

```
估计没几个人能认真看到这里,我偷偷喵两句:
这个软件真是不懂那个几十美元收的是什么钱,激活了也不知道新增了什么功能,完全不知道收费版和不收费有什么区别。难道是这个绿色和红色的区别?
大家都喜欢绿油油的?呃呃呃蝈楠那可真够下头的捏。
@ 腾讯公司 过来学习先进皮肤经验。(一开始没想到论坛真有这个人 还艾特出来了。。。)
```
### 第三步 dylib持续化注入
毕竟不能靠Frida先启动然后再启动app,这并不酷,也不符合我对高科技的想象。

新建项目


然后随便起个名:

点创建选择一个保存的路径即可。

然后我们打开.m文件开始编写代码。


直接把rd_route复制两个文件到项目里即可。

如图所示。
然后import一下几个基本库:
UI控件库是注入CleanMyMac的,可以不写。

然后我们在load中写自己的ObjectC代码

我们今天主要看AirBuddy,大家不要在意上面的一连串注入代码。



我们在frida里面直接能修改寄存器,OC里很遗憾不能直接操作。
但是我们有ASM!
直接写内联汇编啊铁子!(突然激动
我们首先屏蔽掉activate函数,这没用。


然后我们用_dyld_get_image_vmaddr_slide(0) + 0x100050480;加上函数偏移得到内存中这个函数的偏移地址。
为什么_dyld_get_image_vmaddr_slide参数选0捏?因为这是选择第一个镜像(也就是可执行文件本身的内存空间镜像基址),具体细节懒得说,百度搜一下大家就懂了。
记住写0就是表示App可执行文件本身,也就是你注入的那个进程本身。别乱写哦,因为app在运行时不止一个内存镜像。

然后我们伪造一个函数进行挂钩。
```c
int _0x100050480New() {
    NSLog(@"==== _0x100050480New called");
    __asm
    {   //内联汇编直接修改寄存器的值
      //对比原始二进制反汇编 cmp byte ptr , 1
      mov byte ptr , 0
    }
    NSLog(@"==== _0x100050480New call end");
    return _0x100050480Ori();//调用原函数恢复执行
}
```
接下来声明一下原始函数保存地址,这里直接转换成函数结构体可以直接调用
int (*_0x100050480Ori)();
定义原函数返回值int 无参数 加上 * 取该变量内存地址里的值以被下面挂钩函数赋新值:
rd_route((void *) _0x100050480, _0x100050480New, (void **) &_0x100050480Ori);
(void **) &_0x100050480Ori这里用(void **)进行类型转换,&取地址符传递地址进函数。
然后 *_0x100050480Ori 保存的就是原函数地址了,注意如果不写“ * ”你会得到_0x100050480Ori的内存地址而不是这个内存地址里面的值。

然后使用经典的mov指令在函数进入时将寄存器的值赋予0,并恢复原函数执行,这样原函数读到的就是新的r13寄存器里的这个0值啦!
```c
__asm
{   //内联汇编直接修改寄存器的值
    mov byte ptr , 0
}
```

按下Command + F9 狠狠的编译出dylib文件。


好耶!

现在拿出我们的注入文件去注入app:

insert_dylib文件是编译自项目:insert_dylib,代码在开头自己编译。我不提供的原因是老有阴谋论在那边发癫说这个可执行文件有后门 让我们关SIP一定是你的阴谋 我要控制所有人的MacBook 统治整个世界!我是淫魔是吧?阴谋论歇歇吧别搁那发癫了。

注入完成打开app:

成功。

### 第四步 注意事项
我们想修改内存的是主程序二进制文件,所以我们千万不要注入任何影响到偏移地址有关的文件,如AirCore和AirBuddy二进制文件,为什么呢?因为这样会造成文件偏移错误。我们的偏移地址是根据IDA读出的原始二进制来算的,如果你注入了这些文件地址就会发生移位,这就无法通过原地址进行正确的注入了。所以我挑了个好欺负的小文件。也别去注入几MB的大文件,手动来回复制文件很麻烦的。

## 注入文件和补丁
仅供参考学习使用,有些人不要发癫!
试试注入CleanMyMac 4.12.3 中国特供版,有彩蛋哦。
源码:
insert_dylib

rd_toute


编译好的二进制文件:



仅供参考学习使用。
注入方法:
sudo insert_dylib libInlineInjectPlugin.dylib /Applications/AirBuddy.app/Contents/Frameworks/LetsMove.framework/Versions/A/LetsMove /Applications/AirBuddy.app/Contents/Frameworks/LetsMove.framework/Versions/A/LetsMove

libInlineInjectPlugin.dylib文件建议固定放在某个位置 注入成功后移动或删除文件都会导致注入失败。

sudo insert_dylib <dylib库文件路径> <目标文件路径> <写出的新文件路径>

## 参考资料 Reference&Credit
1. 2023, Search Engine By Google.
2. 2023, Bing Global Search Engine By Microsoft.
3. 2023, MacOS动态注入的三种方式及hook方案, https://blog.csdn.net/tangsilian/article/details/89442802
4. 2023, macOS 逆向從入門到破解 Frida + Hopper + dylib 注入, https://beeeeemo.dev/2021/08/macos-%E9%80%86%E5%90%91%E5%BE%9E%E5%85%A5%E9%96%80%E5%88%B0%E7%A0%B4%E8%A7%A3-frida-hopper-dylib-%E6%B3%A8%E5%85%A5/
5. 2023, 404 WebSite by code-examples.net, https://code-examples.net/zh-CN/q/204273
6. 2023, JianShu by https://www.jianshu.com/p/3c8a9a6cee8d
7. 2023, 404 WebSite by https://github.com/rodionovd/rd_route
8. 2023, 使用Frida优雅调试010 Editor https://www.chinapyg.com/thread-134972-1-1.html
9. 2023,Objective-C的hook方案(一): Method Swizzling by https://blog.csdn.net/yiyaaixuexi/article/details/9374411
10. 2023,运行时注入方式破解最新Mac版010 Editor v9.0.1https://www.52pojie.cn/forum.php?mod=viewthread&tid=861809&extra=page%3D1%26filter%3Dtypeid%26typeid%3D377
11. 还有好多,懒癌犯了不想写了。

abcsf124 发表于 2023-2-8 10:46

不知道这个有没有合适的,可以问一下他【闲鱼】https://m.tb.cn/h.UNUXPMt?tk=nZP2d6oGLhH CZ0001 「快来捡漏【苹果二代蓝牙长键盘黑白妙控苹果二代蓝牙键盘妙控进水拆电池,键】」
点击链接直接打开

Vvvvvoid 发表于 2024-1-7 03:03

本帖最后由 Vvvvvoid 于 2024-1-7 03:07 编辑

QiuChenly 发表于 2024-1-7 01:22
直接注入这个二进制 但是要注意修复文件权限和可能出现的崩溃问题 这都可以通过hook修正。
还有一个问题
为啥 frida hook 是手动获取模块基址 ,然后 + 函数偏移 0x50480
而 dylib 注入是直接写死的函数地址 0x100050480 啊

xixicoco 发表于 2023-1-28 01:58

大佬厉害,应该是精华帖

wen704 发表于 2023-1-28 08:39

牛,花了半小时全看完了,很详细{:1_921:}{:1_921:}{:1_921:}

imosx 发表于 2023-1-29 10:21

谢谢大佬分享

huffaudreads 发表于 2023-1-29 10:32

学习了。

576239926 发表于 2023-1-29 13:31

看完了大神所有帖子,受益匪浅,希望继续分享

kyjzbiao 发表于 2023-2-7 13:40

Hmily 发表于 2023-2-7 14:50

@QiuChenly 是有人拿附件去卖了吗?在哪里我可以去查一下。

匆匆那年0211 发表于 2023-7-19 09:16

大佬牛逼、就是步骤太多了 太复杂了、诶 我等菜鸡搞不定啊
页: [1] 2 3
查看完整版本: AirBuddy2 2.6.3 的dylib注入方案 (2)