教你如何动态调试 iOS App(反编译 App) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
idevhan
V2EX    iDev

教你如何动态调试 iOS App(反编译 App)

  •  
  •   idevhan
    Cooooper 2018-04-17 17:25:11 +08:00 9222 次点击
    这是一个创建于 2737 天前的主题,其中的信息可能已经有所发展或是发生改变。

    教你如何动态调试 iOS App(反编译 App)

    开篇


    通过本文你能了解 iOS 逆向的基本知识,对 iOS App 的安全有一定了解。然后能举一反三,在自家 App 找到危险漏洞加以预防,保证用户数据安全。

    在安全领域,攻与防永远存在。哪怕是 iPhone 有着强大的安全防护机制,也挡不住那些极客们一次又一次的好奇,开发了很多强大且便利的工具。本文就是在这些极客们提供的工具的基础上完成的!

    准备工具


    HTTP(S) 抓包


    HTTP 抓包

    第一步:获取 MAC IP

    按下 Option 键,同时点击 Mac 菜单栏上的无线网 Icon,能看到当前电脑的 IP 地址。 或在终端输入 ifconfig en0 也可查看。

    第二步:设置代理

    保证手机和电脑在同一 WIFI 下,在手机上,点击“设置->无线局域网->连接的 WiFi ”,设置 HTTP 代理:

    服务器:为 Mac 电脑 IP 地址(如 192.168.1.122 )

    端口:8888

    第三步:抓包

    在电脑端,打开 Charles。使手机发生网络请求,Charles 会弹出一个询问的对话框

    点击“ Allow ”允许,Charles 会出现手机的 HTTP 请求记录列表。

    HTTPS 抓包

    第一步: 获取证书安装地址

    安装 SSL 证书到手机设备。点击 Help -> SSL Proxying -> Install Charles Root Certificate on a Mobile Device

    出现弹窗得到地址 chls.pro/ssl

    第二步:iPhone 安装证书

    在手机 Safari 浏览器输入地址 chls.pro/ssl,出现证书安装页面,点击安装,手机设置有密码的输入密码进行安装

    第三步:配置代理 host

    Charles 设置 Proxy。选择 Proxy -> SSL Proxying Settings...

    勾选 Enable SSL Proxying,点击 Add

    Host 设置要抓取的 HTTPS 接口,Port 填写 443。

    让手机重新发送 HTTPS 请求,可看到抓包。

    注意:不抓包请关闭手机 HTTP 代理,否则断开与电脑连接后会连不上网!

    拿到 .h 头文件

    从 AppStore 直接下载的 ipa, 苹果公司对其做了 FairPlay DRM 技术进行加密保护,无法直接使用 class-dump 工具获取头文件。但是如果是通过 development 打包出来的话的 App 的话,是可以直接使用 class-dump 查看所有头文件的,此部分介绍就是通过此情况来说明如何获取 .h 文件的。

    此处不再介绍 class-dump 工具的安装过程,具体步骤请直接百度。

    进入到 appName.ipa 所在目录,修改扩展名为 .zip ,然后解压文件,得到 appName.app 。

    然后执行:

    class-dump -H appName.app -o ./headers/

    命令执行完成后,会在当前目录下的 headers 目录里看到 app 所有头文件。

    如果添加参数 -A -S 会在头文件里标记处类方法和属性的 IMP 地址(模块偏移前基地址)。

    class-dump -H -A -S appName.app -o ./headers/

    SSH 访问手机文件目录


    在你的越狱手机上使用 Cydia 应用市场安装 OpenSSH,并保证 Mac 和 iPhone 处于同一个 WIFI 下,在 MAC 终端输入:

    ssh root@IP ,IP 替换为 iPhone 的 IP 地址

    输入默认密码:alpine

    即可进入 iPhone 终端。

    使用 Clutch 反编译 App


    第一步:重新签名 debugserver

    取得 debugserver 有两种方式。

    第一种是在 Mac 电脑中拿到

    进入路径 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/8.3/DeveloperDiskImage.dmg(其中路径里 8.3,代表 iOS 系统版本,需与准备的越狱手机系统版本保持一致)。双击 DeveloperDiskImage.dmg ,将目录里的 usr/bin/debugserver 复制到指定文件夹中。

    第二种是在越狱手机里拿到

    如果手机连接过手机并通过 XCode 调试过 app,会在手机里的 /Developer/usr/bin/ 目录下生成一个 debugserver 文件。通过 iFunbox 导出至 Mac 桌面。或使用 scp 命令 cpoy 出来。

    重签名 debugserver

    即给 debugserver 添加 task_for_pid 权限

    创建 entitlements.plist,添加如下四个 key:

    com.apple.springboard.debugapplications get-task-allow task_for_pid-allow run-unsigned-code 

    key 对应的 value 都设为设为 ture

    将 entitlements.plist 和 debugserver 放在同一个目录下,执行以下命令:

    codesign -s - --entitlements entitlements.plist -f debugserver

    此命令会重新签名 debugserver,将签名后的 debugserver 拷贝至手机系统的 /usr/bin/ 目录下。

    注意:不要将 debugserver 拷贝至 /Developer/usr/bin/ 路径下

    第二步: 通过 Clutch 拿到反编译后的 App 可执行文件

    将下载好的 Clutch 放入手机的 /usr/bin/ 路径下。然后,给 Clutch 赋予权限,通过 SSH 登录到手机,进入 /usr/bin/ 执行 chmod a+x ./Clutch

    通过命令 Clutch -i,列出所有的可被 Clutch 的应用。

    对指定序号的应用进行脱壳,如企业微信,序号是 1,命令是 Clutch -d 1。执行完成后,会得到脱壳后的 ipa。

    第三步:使用 class-dump 拿到 .h 头文件

    使用上文 [拿到.h 头文件] 介绍的方法拿到脱壳后的 App 头文件和并记下要打断点的方法的 IMP 地址。

    动态调试 App

    本文动态调试用到的调试器是 lldb。

    第一步:使 iPhone 进入等待挂载状态

    SSH 登录到手机,执行 ps -e 命令得到 App PID 或项目名称。

    进入 /usr/bin/ 执行 ./debugserver IP:port -a PID|appProjectName。 其中第一个参数 IP 可以替换为 Mac 电脑 IP 地址,或者使用 * 通配符,允许所有 IP 调试;第二个参数 port 随便写一个就行。第四个参数 可以指定要调试 App 的 PID 或项目名称。比如要调试的 PID 为 6019 的搜狗输入法项目名称为 SogouInput,则命令即为:

    ./debugserver *:1234 -a 6019./debugserver *:1234 -a ‘ SogouInput ’

    此命令执行完成后,app 会进入等到挂载状态,app 会被卡住点击无反应。正常现象!

    如果此命令报错,如出现 Segmentation fault: 11 等情况,说明 App 做了反动态调试保护。遇到此种情况,需先确定 App 采用了哪种保护方案,然后进一步找到对应措施,干掉它的反动态调试保护。

    第二步:监听进程,进入挂载状态

    重新打开一个 Mac 终端执行 lldb 进入 lldb 调试状态。然后输入

    process connect connect://iPhoneIP:port

    iPhoneIP 替换为 iPhone 的 IP 地址; port 改为刚才指定的端口,即 1234。

    待命令执行完成后,App 即进入挂载状态。

    第三步:获取 App 的 ASLR 偏 移量

    ASLR 偏移量其实就是虚拟内存的地址相对于模块基地址的偏移量。有两个概念需要熟悉一下:

    • 模块在内存中的起始地址 ---- 模块基地址
    • ASLR 偏移 ---- 虚拟内存起始地址与模块基地址的偏移量

    在 lldb 调试器模式下,执行 imge list -o -f

    模块偏移后的基地址 = ASLR 偏移量 + 模块偏移前基地址(方法的 IMP 地址)

    上面这个公式是尤为重要的,因为 Class-dump 中显示的都是“模块偏移前基地址”,而 lldb 要操作的都是“模块偏移后的基地址”。所以从 Class-dump 到 lldb 要做一个地址偏移量的转换。

    至此,已得到了 App 的 ASLR 偏移量和方法的 IMP 地址。

    第四步:打断点,调试

    在 lldb 模式下执行,br s -a 'ASLR 偏移量+ IMP',然后执行 c,使 App 跑起来,触发一个方法调用,就会进入断点模式。输入 po $arg1 打印第一个参数。

    然后,配合着抓包工具 Charles(比如分析网络请求加密逻辑) 和 Class-dump (比如修改某个类的方法返回值)等工具,你就可以随意动态调试 App 了,就像在 XCode 里调试一样!

    br 命令说明

    br dis 1 -- 禁用( disable )编号为 1 的断点

    br en 1 -- 启用( enable )编号为 1 的断点

    br dis -- 禁用所有断点

    br en -- 启用所有断点

    br del 1 -- 删除( delete )编号为 1 的断点

    br del -- 删除所有断点

    br list -- 列出所有断点

    使用 dumpdecrypted 破壳 App


    dumpdecrypted 脱壳工具的原理是:将应用程序运行起来( iOS 系统会先解密程序再启动),然后将内存中的解密结果 dump 写入文件中,得到一个新的可执行程序。

    第一步:生成 .dylib 文件

    在终端进入到下载后的目录中,cd dumpdecrypted-master,然后执行 make,即可生成 dumpdecrypted.dylib

    第二步:找到 App 的 Documents 文件夹路径

    通过 SSH 登录到 iPhone,然后执行 ps -e 查看进程,获取要破壳的进程 PID。然后执行 cycript -p PID 附加到 PID 进程上。最后执行 [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]得到 Documents 文件夹路径。

    第三步:开始破壳

    将第一步生成的 dumpdecrypted.dylib 拷贝到第二步得到的 .../Documents/ 路径下,命令如下: scp ~/dumpdecrypted.dylib root@IP:/var/mobile/Containers/Data/Application/2B4C6281-C015-4FF3-A8EC-5E5C7554D447/Documents(将路径里的 UDID 替换为你的要破壳的 App 的 UDID)

    进入 Documents 目录下,执行DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/ Application/BFED82A3-3238-4F41-B797-C1CB584CBE05/appProjectName.app/appProjectName(将路径里的 UDID 替换为你的要破壳的 App 的 UDID ;将 appProjectName 替换为要破壳 App 的项目名称)

    待命令执行完,会在当前目录生成一个名为 appProject.decrypted 的文件,这个就是破壳后的 App 可执行文件,要的就是它!使用 Class-dump 即可得到头文件。或使用 Hopper 或 IDA Pro 进行反编译。

    给你的 App 添加反动态调试机制


    ptrace

    为了方便应用软件的开发和调试,从 Unix 的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用 ptrace()。 通过 ptrace 可以对另一个进程实现调试跟踪,同时 ptrace 还提供了一个非常有用的参数那就是 PT_DENY_ATTACH,这个参数用来告诉系统,阻止调试器依附。

    所以最常用的反调试方案就是通过调用 ptrace 来实现反调试。

    sysctl

    当一个进程被调试的时候,该进程会有一个标记来标记自己正在被调试,所以可以通过 sysctl 去查看当前进程的信息,看有没有这个标记位即可检查当前调试状态。

    检测到调试器就退出,或者制造崩溃,或者隐藏工程,当然也可以定时去查看有没有这个标记。

    syscall

    为从实现从用户态切换到内核态,系统提供了一个系统调用函数 syscall,上面讲到的 ptrace 也是通过系统调用去实现的。

    在 Kernel Syscalls27 这里可以找到 ptrace 对应的编号。

    26. ptrace 801e812c T

    所以如下的调用等同于调用 ptrace:

    syscall(26,31,0,0,0);

    arm

    syscall 是通过软中断来实现从用户态到内核态,也可以通过汇编 svc 调用来实现。

    觉得不错的话,欢迎关注我的公众号哦!

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2962 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 14:09 PVG 22:09 LAX 07:09 JFK 10:09
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86