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

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


前提准备

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

  • Xcode(App Store)git

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

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

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

  • 终端操做(codesign *,frida *)windows

  • 具有必定的编程思想sass

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

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


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

第一步

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

1.1体验未破解版流程

image

第二步

在保存操做前是没有提示升级到完整版这个弹窗的,因此咱们在Xcode attach成功后能够尝试增长个符号断点windowWillLoad查看调用栈的关系ide

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

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 共田君

持续更新中...