吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6581|回复: 21
收起左侧

[MacOS逆向] AirBuddy2 2.6.3 的dylib注入方案 (2)

  [复制链接]
QiuChenly 发表于 2023-1-27 22:06
本帖最后由 QiuChenly 于 2023-1-27 22:18 编辑

AirBuddy2 2.6.3 的dylib注入方案(2)

上篇概要

妙控键盘2电源开关坏了,不知道怎么修,就不写了。
有兴趣的直接翻我帖子上一篇看。
DF33884F-9E52-4459-BB94-34BDF7A7BADB_1_105_c.jpg
就这个小开关,全网找不到卖的。有知道怎么修的朋友给个提示。
现在关机没用,拨回去按两下又通电了,好烦。放包里严重误按。

准备工具

  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源代码下载地址:
Click me goto 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破解后无限弹窗问题。

打开软件寻找目标。
16748200710851.jpg
Your copy of AirBuddy is not activated.
这行字我们去搜一下。
我们直接搜索整个app文件里面:
16748201466722.jpg

好 我们已经找到结果,下面就是根据字符串的引用key去ida搜索了。
16748201900543.jpg
此时多个文件我们怎么检查呢?
16748202423792.jpg
注意看,这些文件是一个framework和一个xpc文件中存在的。xpc就不多说了,就没有意义。我们直接重点关注AirCore.framework文件。

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

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

16748204383430.jpg
我们查到了索引,那么这里很可能就是调用方了。跳过去看下:
16748205033331.jpg
继续跳!
来到函数6B4C0.
16748205229411.jpg

继续查找:
16748205653334.jpg
有两个调用方,那么我们看看第一个:
16748205900153.jpg
事情此时变得有趣了起来:这个一看就很不对劲的函数没有任何调用方!
下面那个函数也是如此,那么他是如何被调用的呢?

由于这不是在主进程中,所以我们大胆假设:这是一个导出函数!
16748207061651.jpg
我们到Exports里面复制一部分的文字搜索,果然。

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

我们直接搜主程序试试:
(主程序: /Applications/AirBuddy.app/Contents/MacOS/AirBuddy)
16748207727278.jpg
果然不出所料。

我们继续跟进去看看他是如何被调用的:
16748208343595.jpg
到这里我们继续在函数名上按下X查找引用:
16748208576833.jpg

又跳到了一个符号表:
16748208831363.jpg

继续跳:
16748208995163.jpg
终于逮住了。

我们继续追查:
16748209313919.jpg
原来如此。我们F5看下伪代码:
16748210080329.jpg
他是来自一个叫100050480的函数调用:

16748210787238.jpg
在这个函数的入口中,a1所在的r13寄存器对内存+153的偏移处进行判断,如果不等于1那么执行这里的A1F78函数(101行),否则执行刚刚找到的NotActivated。
16748212014049.jpg
而且注意最上面一个return sub_100017DF0(v19,v20),也就是说如果这个内存偏移地址的值不等于1就会返回这个函数的返回值而不执行下面的NotActivated。也就是界面上红色的X。

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

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

第二步 注入内存修改内存指针所处的值

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

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

下面我们看下ts文件:
16748216060079.jpg
其中,Utils.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[clazzName][clazzMethodSign].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文件的指令:
16748218707435.jpg
build是编译,-w是监视文件修改自动编译。
我们执行watch的指令即可自动编译了。

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

实在不会可以百度。

准备工作完成,那么我们开始hook函数__int64 __usercall sub_100050480@<rax>(__int64 a1@<r13>)看看:
16748218043920.jpg
我们把可执行文件的名称写在参数1中,这样就会自动锁定内存里这个同名进程进行注入。

然后我们获取这个指针所在的地址,开始进入函数的挂钩:
16748221299856.jpg
启动python文件,我们可以看到已经对函数成功挂钩。
那么前面我们知道他是对内存偏移进行判断:
16748221908090.jpg
其实到这个时候也不怕你们知道, v28 = sub_1000A1F78(); 函数sub_1000A1F78进去一看:
16748222391968.jpg
再回到AirCore文件里一搜:
16748222702574.jpg
函数6B860进去看下:
16748222941547.jpg
16748223197370.jpg
搜一下key发现是已经激活的提示文字。

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

那么我们还犹豫什么?
只要在函数进入之前修改*(a1 + 153)的值即可。
但是怎么修改呢?
别急,我们看下反汇编:
16748224384340.jpg
cmp就是这个判断,我们要修改的就是r13+99h内存指针的值为0即可。

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

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

估计没几个人能认真看到这里,我偷偷喵两句:
这个软件真是不懂那个几十美元收的是什么钱,激活了也不知道新增了什么功能,完全不知道收费版和不收费有什么区别。难道是这个绿色和红色的区别?
大家都喜欢绿油油的?呃呃呃蝈楠那可真够下头的捏。
@ 腾讯公司 过来学习先进皮肤经验。(一开始没想到论坛真有这个人 还艾特出来了。。。)

第三步 dylib持续化注入

毕竟不能靠Frida先启动然后再启动app,这并不酷,也不符合我对高科技的想象。

新建项目
16748231731920.jpg

然后随便起个名:
16748231970122.jpg
点创建选择一个保存的路径即可。

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

直接把rd_route复制两个文件到项目里即可。
16748233192993.jpg
如图所示。
然后import一下几个基本库:
UI控件库是注入CleanMyMac的,可以不写。

然后我们在load中写自己的ObjectC代码
16748233952045.jpg
我们今天主要看AirBuddy,大家不要在意上面的一连串注入代码。

16748234566506.jpg

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

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

然后我们伪造一个函数进行挂钩。

int _0x100050480New() {
    NSLog(@"==== _0x100050480New called");
    __asm
    {   //内联汇编直接修改寄存器的值
        //对比原始二进制反汇编 cmp byte ptr [r13+99h], 1
        mov byte ptr [r13+99h], 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值啦!

__asm
{   //内联汇编直接修改寄存器的值
    mov byte ptr [r13+99h], 0
}

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

好耶!

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

注入完成打开app:
16748247844169.jpg
成功。

第四步 注意事项

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

注入文件和补丁

仅供参考学习使用,有些人不要发癫!
试试注入CleanMyMac 4.12.3 中国特供版,有彩蛋哦。
源码:
insert_dylib
insert_dylib-master.zip (13 KB, 下载次数: 39)


rd_toute
rd_route-master.zip (10.34 KB, 下载次数: 32)

编译好的二进制文件:
insert_dylib.zip (8.47 KB, 下载次数: 43)


libInlineInjectPlugin.dylib.zip (10.44 KB, 下载次数: 42)

仅供参考学习使用。
注入方法:
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.1  https://www.52pojie.cn/forum.php?mod=viewthread&tid=861809&extra=page%3D1%26filter%3Dtypeid%26typeid%3D377
  11. 还有好多,懒癌犯了不想写了。

免费评分

参与人数 5吾爱币 +7 热心值 +5 收起 理由
来阻 + 1 + 1 谢谢@Thanks!
腾讯游戏 + 1 + 1 谢谢@Thanks!
chinawolf2000 + 1 + 1 热心回复!
wxw1145897898 + 3 + 1 文章不错 学习了
deadlybugs + 1 + 1 大佬威武,新春快乐

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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
牛,花了半小时全看完了,很详细
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
大佬牛逼、就是步骤太多了 太复杂了、诶 我等菜鸡搞不定啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-23 02:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表