手把手教你如何破解付费MacApp

目录截图.png Typora的自带内容目录截图

复杂方案解决结果--->破解版传送门

前提准备

  • Hopper Disassembler(X,G,Esc,Space...,度娘/谷歌,后面简称Hopper

  • Xcode(App Store)

  • frida(前置条件配置好 python环境,下载好pip,随后用pip下载,具体度娘/谷歌找攻略即可)

  • 汇编指令基础掌握(je,jne,jmp,jle,mov,call,ret,nop,xor,lea,rax,rdi,rsi,rcx....)

  • lldb指令基础掌握(bt, image lookup --address ,p, c...)

  • 终端操作(codesign *,frida *)

  • 具备一定的编程思想

  • 基础逆向工程思维基础掌握(Executable,重签,覆盖entitlements)

小目标:学习下一些简单汇编知识以及市面上的一些付费App的付费模块的代码思路然后顺便逆向破解 PDF Expert

第一步

  • 目的---体验原装PDF Expert 修改任意pdf文件后保存查看效果

1.1体验未破解版流程

image

第二步

在保存操作前是没有提示升级到完整版这个弹窗的,所以我们在Xcode attach成功后可以尝试增加个符号断点windowWillLoad查看调用栈的关系

  • 目的---查看调用图层间的调用关系,从而尝试去定位到关键代码。

2.1 Xcode新建个工程

image

2.2 attach查看对应图层关系

2.2.1 Attach App
image

2.2.2 Debug View Hierarchy

image

查看图层关系 在增加符号断点windowWillLoad后在修改后的pdf界面触发保存操作出现弹窗发现没有停下断点,说明这条路行不通。

但是我们也获取到了一个关键信息:最上面的控件名称为 DMTrialController

第三步

  • 目的---使用frida通过已知弹出升级框的类 DMTrialController ,查找出具体调用的方法堆栈

3.1 frida-trace 查看对应方法调用关系

在终端上输入 frida-trace -m "-[DMActivationController *]" PDF\ Expert

​
 22103 ms  -[DMTrialController trialObject]
​
 23074 ms  -[DMTrialController trialObject]
​
 23074 ms  -[DMActivationController performActivationStepWithStep:0x66]
​
 23074 ms     | -[DMActivationController isRunning]
​
 23074 ms     | -[DMActivationController setNextPerformStep:0x66]
​
 23074 ms     | -[DMActivationController updateStepControllerForCurrentStep]
​
 23074 ms     |    | -[DMActivationController nextPerformStep]
​
 23074 ms     |    | -[DMActivationController confirmedNextPerformStep:0x66] </pre>

分析log输出在点击保存时触发的方法为-[DMActivationController performActivationStepWithStep:0x66]

记录下来回到第二步操作的2.4查看付费弹窗堆栈信息。

3.2 增加符号断点查看

image

再次保存操作后触发断点终端查看到嫌疑关键代码如下

 frame #1: 0x000000010227846c PDF Expert`___lldb_unnamed_symbol15828$PDF Expert + 380
 frame #2: 0x00000001022c5263 PDF Expert`___lldb_unnamed_symbol16722$PDF Expert + 371
 frame #3: 0x00000001022a1e37 PDF Expert`___lldb_unnamed_symbol16128$PDF Expert + 39 

随后 image寻址找到具体App调用时的地址信息

 (lldb) image lookup --address 0x0000000103818a7c
 Address: DevMateKit[0x0000000000029a7c] (DevMateKit.__TEXT.__text + 165540)
 Summary: DevMateKit`-[DMActivationController performActivationStepWithStep:]
(lldb) image lookup --address 0x00000001022c5263
 Address: PDF Expert[0x00000001003ff263] (PDF Expert.__TEXT.__text + 4180867)
 Summary: PDF Expert`___lldb_unnamed_symbol16722$PDF Expert + 371
(lldb) image lookup --address 0x00000001022a1e37
 Address: PDF Expert[0x00000001003dbe37] (PDF Expert.__TEXT.__text + 4036439)
 Summary: PDF Expert`___lldb_unnamed_symbol16128$PDF Expert + 39</pre>

得到可疑地址

A:0x0000000000029a7c

B:0x00000001003ff263

C:0x00000001003dbe37

第四步

  • 目的---使用Hooper查看可疑地址A,B,C。

4.1 科普使用Hooper

4.1.1 直接拽

image
image

4.1.2 稍等一会Hopper加载完成

4.1.3 追查付费弹窗底细

加载完成后在界面左侧输入刚才Xcode Attach控件名 DMTrialController

image

分析搜索结果发现并没有直接匹配的 DMTrialController 这玩意,猜想这个空间不是直接在主工程实现的,随后看跟 DMTrialController 命名类似的DMTrialWelcomeStepController 看上图可以找到文件路径并且发现关键字DevMateKit,其实早在Xcode Attach时查看图层就可以发现 这个付费弹窗的命名前缀是DMT 而主工程的命名前缀是PDF_Expert,很大概率付费弹窗是用的第三方库.

DevMateKit传送门

4.1.4 简单了解DevmateKit有哪些功能

这个就不在这介绍了,通过传送门下载的demo大概了解到DevMateKit是做一些付费弹窗,举报弹窗,kevlar代码混淆。

4.2 逐个排查可疑地址

4.2.1 地址A:0x0000000000029a7cHopper 信息分析

image

image

根据Hopper分析结果看出可疑地址A主要是在做一些绘制UI操作,我们主要目的是要改掉App中一个凭证字段信息类似 isActiva , isRegis 之类的字眼。排除掉可疑地址A。

4.2.2 地址B:0x00000001003ff263Hopper 信息分析
image

发现定位到的地址操作符是test,这个有着重大嫌疑,大概率是此地址去做是否注册判断。我们继续去排查可疑地址C。

4.2.3 地址C:0x00000001003dbe37Hopper 信息分析

image

由分析结果得知可疑地址C主要是做saveDocument:操作,并且看上图左边红色框可以得知这一顿操作没有做一些test或者是cmp或者是提前ret之类的操作,基本也可以排除掉可疑地址C。

4.3 主要调查重点嫌疑地址

4.3.1 重新查看可疑地址B:0x00000001003ff263的hopper数据,切换至控制流图(Control Flow Graph)

备注:快捷键 Space

image

查看上图分析一处逻辑判断为 al0x1test运算,但是此处逻辑地址为loc_1003ff259是由loc_1003ff11e的尾部的je跳转过来的。

je的来源是上一层的al0x1test运算,再往上看可以知道al是通过call sub_1003b21b0的返回值赋值的。

4.3.2 解析第一层梦境function sub_1003b21b0

双击进入 sub_1003b21b0的function实现的控制流图。进入目标func的控制流图后发现是个蛮庞大的函数,缩放一下页面看到整个流程图如下

image

分析上图控制流图结构,猜想如果是已购买用户的判断逻辑应该是一条清晰的流程也就是右侧箭头所指的通道。

接着着重看如何才可以走到阳光大道上

image

分析上图控制流图重点是通过cmp r13b, 0x3je loc_1003b22cd,翻译过来也就是判断r13b是不是0x3如果是0x3就去阳光大道,那么我们现在就想办法把r13b变成0x3

image

继续往上看得知r13beax怼过来的,而eax是通过function sub_100382d70 返回的。

那么现在关键就是这个sub_100382d70

4.3.3 解析第二层梦境function sub_100382d70

双击进入sub_100382d70的function。

image

分析下第二层梦境的大概实现,首先第一眼嫌疑最大的是_O7RH3WAr7wAQMdz5Xv这个是被call的function是被混淆过的,其次通过这个_O7RH3WAr7wAQMdz5Xv 返回的al最后是跟0x1test

那么现在冷静下思考有以下两个解决思路

  • 简单方案-直接改sub_100382d70提前返回0x3然后去走阳光大道(如果忘记了为何要返回0x3,可以复习下4.3.2章节)

  • 复杂方案-修改_O7RH3WAr7wAQMdz5Xv 内部实现,继续深究,改最根部判断。

第五步

  • 目的---使用Hooper修改关键汇编运算逻辑。

5.1 简单方案解法尝试

5.1.1 修改sub_100382d70实现

根据上述操作我们得知sub_100382d70做了一串操作,先push,后mov后push…,我们现在其实只要返回个0x3即可,那直接选中sub_100382d70第一行修改,输入mov rax,0x3 ,点Assemble and Go Next,再继续输入ret,继续Assemble and Go Next

image

修改后的结果如下,随后保存新的Executable文件

image

5.1.2 保存Executable文件

image

保存Executable文件时点Cancel

image

接下来就是验证简单方案是否可行了,把刚生成的新Executable文件替换我们目标App内的旧Executable文件,具体操作如下

image

5.1.3 覆盖entitlements文件

原版entitlements文件数据如下,随后吧identifier对应的那几行干掉

 <?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>com.apple.application-identifier</key>
 <string>3L68KQB4HG.com.readdle.PDFExpert-Mac</string>
 <key>com.apple.developer.team-identifier</key>
 <string>3L68KQB4HG</string>
 <key>com.apple.security.get-task-allow</key>
 <true/>
 </dict>
</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>com.apple.security.get-task-allow</key>
 <true/>
 </dict>
</plist> 

保存后覆盖目标App的entitlements文件

 codesign -f -s - --entitlements entitlementsFilePathPrefix/Entitlements.plist appPathPrefix/PDF\ Expert.app 

执行完终端输出提示/Applications/PDF Expert 3.app: replacing existing signature即表示完成替换

5.1.4 检验简单方案处理结果

随后重新打开目标App,首先检查当前App的是否注册状态如下图

image

随后尝试修改任意pdf文件保存,保存成功!

5.2 复杂方案解决尝试

5.2.1 _O7RH3WAr7wAQMdz5Xv的前世今生

早在上文4.1.4 简单了解目标App所用的到第三方库kevlar能支持代码混淆,我们先简单看看_O7RH3WAr7wAQMdz5Xv的函数结构大概如下

image

看不是很懂,我们切换至伪代码视角看看

image

这里我们发现个关键字符串 kevlar,那基本可以认定此处的混淆代码的出处是DevMateKit了。

DevMateKit提供的demo工程我们搜索kevlar

image

继续怼进DMKevlarApplication.h看看代码

//! Function help with running timer for advanced check
#define DMKRunNewIntegrityCheckTimer DzVpwUg0VXKMIfCPA
FOUNDATION_EXTERN void DMKRunNewIntegrityCheckTimer(NSUInteger num, NSTimeInterval checkFrequency);
​
//! Checks if applicaion activated
#define DMKIsApplicationActivated PfCuPgJSp5KVlvc8W1
FOUNDATION_EXTERN BOOL DMKIsApplicationActivated(DMKevlarError *outKevlarError);
​
//! Returns user license info
#define DMKCopyLicenseUserInfo dReea3NiUFGwgD52YPa
FOUNDATION_EXTERN CFDictionaryRef DMKCopyLicenseUserInfo(void) CF_RETURNS_RETAINED;
​
//! Forces license validation request on DevMate server
#define DMKValidateLicense i2rRAQi8BfdE2G9geRSu
FOUNDATION_EXTERN void DMKValidateLicense(void (^completionHandler)(NSError *errorOrNil));
​
//! Deactivates application and invalidates license info
#define DMKInvalidateLicense kLLTbFMUP234v8xDp6Uck
FOUNDATION_EXTERN BOOL DMKInvalidateLicense(void);
​
/**
 This category will extend functionality of NSApplication to be complies with Kevlar concept of protection.
 Rigth now, some helper inteface have been declare there, because it is kind of complicated to load category.
 */
#define com_devmate_Kevlar YC2eXYjMnR
@interface NSApplication (com_devmate_Kevlar) 

发现好多混淆函数,简单看注释//! Checks if applicaion activated 理论上这个函数应该是我们要找的最关键函数。

  • 那么如何核实FOUNDATION_EXTERN BOOL DMKIsApplicationActivated(DMKevlarError *outKevlarError);就是我们要找的function呢?

现在手头上有下载好的DevMateKit的demo工程,那么直接run一个.app文件出来丢到Hopper分析对比就可以了

image

上图是run的CustomTrialExample这个target后的截图,此时记录下可疑函数的混淆标记为PfCuPgJSp5KVlvc8W1

随后把这个CustomTrialExample.app丢到Hopper一顿分析后直接搜索混淆标记PfCuPgJSp5KVlvc8W1

image

这下就基本上真相大白了,根据下图对比得知

image

也就是我们基本核实

FOUNDATION_EXTERN BOOL DMKIsApplicationActivated(DMKevlarError *outKevlarError)

就是函数_O7RH3WAr7wAQMdz5Xv上辈子的初始形态了。

5.2.2 _O7RH3WAr7wAQMdz5Xv重写

  • DMKIsApplicationActivated主要做了两个事

    • 完整函数返回了BOOL

    • 传入指针DMKevlarError *outKevlarError 内部可能做修改

  • 我们要重写成什么样子?

    • 鉴于此function是要返回个是否已激活/付费,那尝试下完整函数返回固定为0x1

    • 常规来说已激活/付费的用户调此function理论上不应该有error存在,也就是我们需要把传入指针对应的地址的内容变成0x0

那么改成下图的样子也就基本上没啥毛病了

image

转成伪代码一看就懂了

int _O7RH3WAr7wAQMdz5Xv(int arg0) {
    rdi = arg0;
    if (rdi != 0x0) {
            *rdi = 0x0;
    }
    return 0x1;
}

那么我们就把我们重写的目的都实现了,接下来就是见证奇迹的时刻了

5.2.3 检验复杂方案处理结果

重写 _O7RH3WAr7wAQMdz5Xv后和上文5.1.25.1.35.1.4做法一致,最后验证出来结果也是一样的可以保存成功!

总结

快捷键补充(windows/Mac)

hopper修改 alt+a / option+a

hopper保存 win+shift+e / cmd+shift+e

hopper寻址 g

hopper查引用 x

声明:此文仅用于学习用途,请勿用于非法用途。

转载请注明出处。谢谢!


逆向技术积累相关链接

破解<Cornerstone> by Chen华锋 //本人逆向编程的引路人


YY Flutter技术积累相关链接

flutter多实例实战 by共田君

一行代码教你解决FlutterPlatformViews内存泄露 by
AShawn

手把手教你在Flutter项目优雅的使用ORM数据库 by
williamwen1986

flutter通用基础库flutter_luakit_plugin by
williamwen1986

github - flutter_luakit_plugin使用例子 by
williamwen1986

手把手教你编译Flutter engine by 共田君

手把手教你解决 Flutter engine 内存泄漏 by 共田君

github - 编译产物下载 修复内存泄漏后的flutter engine(可直接使用)by 共田君

github demo - 修复内存泄漏后的flutter engine by 共田君

持续更新中...

推荐阅读更多精彩内容

  • 一.微讲解 首先,明确湿地的概念。在现行的人教版、中图版、鲁教版、湘教版四套高中地理教材中,只有湘教版必修三...
    一粒沙子在简书阅读 4,964评论 1 4
  • 挑战自己这个事情, 21天本就是个挑战, 无论怎样的挑战, 持续21天都是个痛苦的蜕变过程。 今天的任务是互推引粉...
    马春颖阅读 85评论 0 0