QiuChenly 发表于 2023-4-11 19:29

破解Paste: 基于LLDB堆栈跟踪与Frida调试

本帖最后由 QiuChenly 于 2023-4-12 00:44 编辑


  没有注入补丁。因为Paste只有一个主程序文件,不方便注入。
    本来这个帖子是有剧情的,但是情感失利,妹子常规好感度完美开局48小时内打成残血副本:
    油腻男的聊天风格直接触发妹子的无视微信消息大招,绷不住了,绝望下编写此帖,实在没心情创作了😭😭😭😭😭
    现在是半夜00:44还在emo😭😭😭😭😭😭😭😭😭😭

## 0x01 寻找入口点与分析

  放错图了。这里应该是未激活的状态,差不多意思意思吧这样子。

  因为楼主买不起苹果电脑手机平板,所以只能幻想自己左苹果右华为胸口纹嘉然脑门贴nanami的雄姿。
  老样子搜索字符串,这次用Sublime Text。
  根据上图的内容,我们尝试搜索“立即开始免费试用”:


这里"licensing.paywall.title"我们到ida搜索一下:

按下x看一下xref引用看看函数细节:

这里函数又被两个地方调用,我们直接用lldb看一下函数调用堆栈.我们记下这个函数地址方便后面下断点.

加载后我们先输入c让他继续运行。
然后我们给内存地址:0x1002253E0下断点: br s -a 1002253E0
随后输入r重启app,可以看到直接被断下了:

那么我们往上查堆栈看是谁触发这个Call:
```
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x00000001002253e0 Paste`___lldb_unnamed_symbol16296
Paste`___lldb_unnamed_symbol16296:
->0x1002253e0 <+0>:push   rbp
    0x1002253e1 <+1>:mov    rbp, rsp
    0x1002253e4 <+4>:lea    rax,    ; "licensing.paywall.title"
    0x1002253eb <+11>: movabs rsi, -0x8000000000000000
Target 0: (Paste) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
* frame #0: 0x00000001002253e0 Paste`___lldb_unnamed_symbol16296
    frame #1: 0x00007ff817b19033 libdispatch.dylib`_dispatch_client_callout + 8
    frame #2: 0x00007ff817b1a267 libdispatch.dylib`_dispatch_once_callout + 20
    frame #3: 0x000000010022544a Paste`___lldb_unnamed_symbol16297 + 42
    frame #4: 0x00000001001f8045 Paste`___lldb_unnamed_symbol14918 + 293
    frame #5: 0x00000001001f8905 Paste`___lldb_unnamed_symbol14923 + 37
    frame #138: 0x00000001002489e6 Paste`___lldb_unnamed_symbol17865 + 86
    frame #139: 0x0000000100248800 Paste`___lldb_unnamed_symbol17861 + 16
    frame #140: 0x0000000100247a8e Paste`___lldb_unnamed_symbol17812 + 302
    frame #141: 0x000000010006127a Paste`___lldb_unnamed_symbol5177 + 778
    frame #142: 0x0000000100066423 Paste`___lldb_unnamed_symbol5291 + 195
    frame #143: 0x0000000100068970 Paste`___lldb_unnamed_symbol5356
    frame #144: 0x0000000100067870 Paste`___lldb_unnamed_symbol5313
    frame #145: 0x0000000100068930 Paste`___lldb_unnamed_symbol5354
```
那么我们重点关注栈3-5-138-145,因为下面都是系统库,所以我们直接看#3看看有没有发现:
```c
void *sub_100225420()
{
if ( qword_100466E50 != -1 )
    swift_once(&qword_100466E50, sub_1002253E0);
return &unk_10046CF50;
}
```
qword_100466E50必然不是-1,所以这里没有可检查的必要,我们继续回溯到#142: 0x0000000100066423:
```c
__int64 __fastcall sub_100066360()
{
__int64 v0; // r13
__int64 *v1; // r14
__int64 v2; // r15
__int64 v3; // r12
__int64 v4; // rax
__int64 v5; // r14
__int64 v6; // rbx
void *v7; // r14
__int64 v8; // rdi
__int64 v9; // r13
__int64 v10; // rbx
__int64 v11; // r15

v2 = *v1;
v3 = *v1;
swift_task_dealloc(*(_QWORD *)(*v1 + 48));
if ( v0 )
    swift_errorRelease(v0);
v4 = *(_QWORD *)(v2 + 24);
v5 = *(_QWORD *)(v4 + 80);
v6 = *(_QWORD *)(v4 + 88);
sub_10000B980(v4 + 56, v5);
if ( ((*(__int64 (__fastcall **)(__int64, __int64))(v6 + 8))(v5, v6) & 1) != 0 )
{
    if ( *(_BYTE *)(v2 + 56) == 1 )
    {
      (*(void (**)(void))(v2 + 32))();
    }
    else if ( (sub_100068390() & 1) != 0 )
    {
      sub_100066460();
    }
}
else
{
    v7 = *(void **)(v2 + 40);
    v8 = *(_QWORD *)(v2 + 24);
    v9 = *(_QWORD *)(v2 + 32);
    v10 = *(_QWORD *)(v8 + 280);
    v11 = *(_QWORD *)sub_10000B980(v8 + 144, *(_QWORD *)(v8 + 168));
    swift_retain(v10);
    sub_100060F70(v11, v9, v7, v10);
    swift_release(v10);
}
return (*(__int64 (**)(void))(v3 + 8))();
}
```
可以看到这里(v6 + 8)这个指针所在的地址函数如果等于0 就执行到了我们的#142堆栈。
```c
else
{
    v7 = *(void **)(v2 + 40);
    v8 = *(_QWORD *)(v2 + 24);
    v9 = *(_QWORD *)(v2 + 32);
    v10 = *(_QWORD *)(v8 + 280);
    v11 = *(_QWORD *)sub_10000B980(v8 + 144, *(_QWORD *)(v8 + 168));
    swift_retain(v10);
    sub_100060F70(v11, v9, v7, v10);
    swift_release(v10);
}
```

所以我们的工作重点放在这里这个判断上,在lldb中给这个地址下断点:
```c
__text:00000001000663A8 loc_1000663A8:                        ; CODE XREF: sub_100066360+3E↑j
__text:00000001000663A8               mov   rax,
__text:00000001000663AC               lea   rdi,
__text:00000001000663B0               mov   r14,
__text:00000001000663B4               mov   rbx,
__text:00000001000663B8               mov   rsi, r14
__text:00000001000663BB               call    sub_10000B980
__text:00000001000663C0               mov   r13, rax
__text:00000001000663C3               mov   rdi, r14
__text:00000001000663C6               mov   rsi, rbx
__text:00000001000663C9               call    qword ptr <-----这里下断点
__text:00000001000663CC               test    al, 1
__text:00000001000663CE               jz      short loc_1000663E1
```
lldb中输入br s -a 0x1000663c9,然后输入r重启,可以看到自动断下:
```
Process 28613 resuming
Process 28613 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
    frame #0: 0x00000001000663c9 Paste`___lldb_unnamed_symbol5291 + 105
Paste`___lldb_unnamed_symbol5291:
->0x1000663c9 <+105>: call   qword ptr
    0x1000663cc <+108>: test   al, 0x1
    0x1000663ce <+110>: je   0x1000663e1               ; <+129>
    0x1000663d0 <+112>: cmp    byte ptr , 0x1
Target 0: (Paste) stopped.
(lldb)
```
我们来StepInto 这个Call:

我们IDA看一下这个地址所在的函数是干嘛的:
```c
__int64 __fastcall sub_1001FBF10(__int64 a1, __int64 a2)
{
__int64 v2; // rax
__int64 v3; // r13
void *v4; // rsp
void *v5; // rsp
__int64 v6; // rdi
__int64 v7; // rax
unsigned int v8; // ebx
_QWORD v10; // BYREF

v10 = v2;
sub_10000B6A0(&unk_100454320);
v4 = alloca(sub_10000C800(&unk_100454320, a2));
v5 = alloca(sub_10000C800(&unk_100454320, a2));
v6 = *(_QWORD *)(*(_QWORD *)v3 + 16LL);
swift_retain(v6);
_s7Combine19CurrentValueSubjectC5valuexvg();
swift_release(v6);
sub_1001FC3D0(v10, v10, &unk_100454320);
v7 = sub_1001F95A0(0LL);
v8 = (*(__int64 (__fastcall **)(_QWORD *, __int64, __int64))(*(_QWORD *)(v7 - 8) + 48LL))(v10, 1LL, v7);
if ( v8 != 1 )
    sub_1001FC400(v10, &unk_100454320);
LOBYTE(v8) = v8 != 1;
sub_1001FC400(v10, &unk_100454320);
return v8;
}
```
可以看出v8返回值为0或者1,因为他有一个v8!=1的判断在这里,那么我们如果Hook 0x1001FBF10这个函数的返回值是否就ok了呢?
编写Frida代码进行内存注入:
```ts
import { HookApp, log } from "./Utils.js";

HookApp("Paste", (hook, point, method, baseAddr, tools) => {
// setTimeout(() => {
//   hook(point(0x1f9f10), (ths, r) => {
//   // r.replace(ptr(2));
//   });
// }, 3000);
hook(point(0x1fbf10), (ths, r) => {
    r.replace(ptr(1));
});
});
```
狠狠的按下F5启动,我们看看是否注入成功:

这里没有去修改有效订阅的那个函数,因为修改了这个信息之后会因为无法读取订阅数据而导致App崩溃,而且Paste只检查了0x1fbf10这个函数,所以我们直接返回1即可完成。
## 0x02 总结
文中使用的Frida js全文如下:
```js
var appBaseAddr = Module.findBaseAddress("Paste");
var func = appBaseAddr.add(0x1fbf10)
Interceptor.attach(func, {
    onEnter(this1, args) {
    },
    onLeave(retval) {
      retval.replace(0x1)
    },
})
```
安装python和Frida后,frida -p pid -l main.js启动注入。pid是进程列表中的进程id。

jack_king 发表于 2023-4-26 10:42

mac m1 使用 rosetta 打开 paste。执行 frida -ppid -l main.js 的时候报错 Failed to attach: module not found at "/usr/lib/libSystem.B.dylib"

楼主这种问题有没有遇到? frida 和 rosetta都升级最新版了,还是没解决,网上也没找到类似的问题

QiuChenly 发表于 2023-4-12 01:13

hoochanlon 发表于 2023-4-11 20:27
那么“0x01 寻找入口点与分析”标题里的最后一段话,“返回1”,意思就是保持订阅试用期内咯。

也并不是因为他的判断逻辑是如果订阅有效那么这个地址函数一定会返回1 不区分是试用期还是有效授权期 因为这个函数就是一个判断用户是否为vip的功能

hoochanlon 发表于 2023-4-11 20:27

那么“0x01 寻找入口点与分析”标题里的最后一段话,“返回1”,意思就是保持订阅试用期内咯。

Jooing 发表于 2023-4-11 21:49

跪着看完大神贴

pjy612 发表于 2023-4-11 21:54

{:301_972:}仙侠风没有了。。。

不过还是好厉害

csdoc 发表于 2023-4-11 22:02

高产的高手!&#128077;

m0fx 发表于 2023-4-11 23:23

膜拜大佬,涨姿势了{:1_893:}

deTrident 发表于 2023-4-11 23:55

学习了,原来可以这样注入,查找关键点费了很多功夫吧

xixicoco 发表于 2023-4-11 23:59

大佬厉害啊

SuperMelon 发表于 2023-4-12 05:04

大佬真牛逼{:1_893:}
页: [1] 2 3
查看完整版本: 破解Paste: 基于LLDB堆栈跟踪与Frida调试