ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android逆向利器JEB:解密加固APK的完整工作流

Android逆向利器JEB:解密加固APK的完整工作流 简介JEB是Android应用静态分析领域的业内标准凭借准确的反编译结果、高容错性与可扩展API著称这份由jilinaok整理的“android逆向神器JEB完全版”工具包面向安全研究人员、移动应用分析工程师及Android逆向学习者可用于APK反编译、代码还原、反混淆与恶意样本分析。压缩包共含76个文件整体约25.88MB核心组件包括jeb.jar主程序、jebdi.jar反编译引擎、swt跨平台图形库、jython-standalone依赖以及覆盖Windows、Linux、macOS系统的bat与sh启动脚本。附带30个sig签名库可明显提升识别精度py脚本配合plugins、_sigs目录便于编写插件对源文件实施反混淆等二次处理目录结构清晰按需取用较为方便。下载后即可搭建可用的JEB分析环境也可结合官方API开发自动化分析插件大幅提升静态分析效率省去自行整合各类依赖的时间。目前已有2898人学习下载适合Android逆向入门与进阶场景收藏使用。1. 打不开的加密APK我为什么优先开JEB而不是jadx做android逆向的人电脑上通常摆着三件套jadx、Frida和JEB。jadx处理常规APK确实顺手但一旦遇到带加固壳、调用链被抽走、核心逻辑埋进so的样本jadx经常只能给你一堆混淆后的空壳方法。我最近接了一个测试样本jadx打开只有两个类方法体全是空而JEB里能完整看到smali指令、Java伪代码、native层汇编还能直接在寄存器级别下断点。这份资源就是把JEB的完整工作台、解析器配置和常用脚本打包到一起适合做Android逆向分析的从业者、跑样本的测试工程师以及想从“只会看jadx”进阶到“能跟踪底层指令”的人。2. JEB的三种代码视图smali、伪代码与原生指令之间如何联动2.1 从DEX字节码到三种中间表示类型推断与简化器JEB拿到一个DEX文件后内部会做两轮工作先解析出smali级别的指令流再做类型推断和控制流恢复生成接近Java语法的伪代码。关键点是它同时保留这两层表示中间还有一个IR中间表示供脚本和插件使用。jadx的做法是直接输出Java源码过程里丢了很多字节码细节遇上编译器优化过的代码就容易出现方法体空白或逻辑错乱。JEB默认对伪代码的处理偏“高保真”宁可多保留寄存器变量也不激进简化代码这让你能反推原始指令的执行顺序。打开同一个混淆类对比过就很明显jadx把两个方法合并成一个变量名重排得“很干净”而JEB的伪代码里依然能看到与smali寄存器一一对应的临时变量。这不是JEB笨而是它刻意保留字节码结构。我第一次用的时候也嫌它生成的代码不够“优雅”后来要追一个被优化掉的赋值语句才发现高保真表示能直接看到v0、v1之间每一处move指令jadx那个版本已经把中间步骤折叠没了。对逆向来说保真比好看重要得多。如果你想让JEB的输出更接近jadx那种可读性可以打开反编译选项里的Simplifier简化器并关闭Preserve Register Names保留寄存器名。这两个开关直接影响伪代码风格做算法还原建议开Simplifier做指令级别的混淆分析建议关闭直接看原始寄存器名更直观。我这个习惯是从一次混淆样本的对比中养成的开了简化后一个三层嵌套的循环被还原成了Java风格的for循环但关闭简化后还能看到循环变量的物理寄存器变化后者对定位“哪次迭代篡改了结果”特别有用。2.2 原生层联动把libxxx.so拖进同一工程xref怎么串起来JEB不只会解析DEX它对ELF的反汇编支持也在主力范围内ARM、ARM64、Thumb、x86指令集都能反汇编。更实用的是它允许你把APK里的so和dex放在同一个工程里然后建立跨层交叉引用。举个例子Java层有个native方法你在伪代码里看到调用点是一个JNI函数直接按X键可以跳到so里对应的导出符号实现不需要像以前那样在IDA和jadx之间来回切。我一般会把APK整体拖进JEB导入时勾选解析原生库这样工程树里会同时出现DEX单元和ELF单元。追路径的时候先看Java层伪代码里调用了哪个native函数再看so的导出表里对应符号的地址双击跳进汇编视图从函数开头往下一行行捋。ARM的Thumb模式有个坑指令边界和ARM模式不同JEB会自动识别判断但如果你把断点打在THUMB指令中间调试器会报错后面避坑章节我会专门说这个。JEB还有个细节so文件的字符串引用也能参与xref。有时候Java层完全不知道一个加密密钥但so的.rodata里会放一串硬编码的AES Key你在JEB的字符串视图里搜索“key”或固定长度十六进制字符串能顺藤摸瓜找到引用它的汇编指令。配合寄存器跟踪基本能把一个JNI加密函数还原到“输入—密钥—算法—输出”的完整链路。这比单独拿IDA分析so再自己拼关系图省不少时间。2.3 工作台布局把快捷键和视图用成肌肉记忆JEB的主界面分成几个区左侧项目浏览器显示所有单元和类中间是代码编辑器右侧是xref交叉引用面板和变量窗口底部是调试控制台和脚本输出。第一次用容易在面板之间迷路但只要把几个快捷键形成肌肉记忆效率能翻倍。我把最常用的几个整理成了习惯表。快捷键作用使用场景G跳转到地址/类/方法输入so偏移或类名直接定位X查看交叉引用查某个函数被谁调用、调用了谁F5切换伪代码/smali/汇编在同一视图中切换表示层Esc返回上一个跳转位置追调用链时快速回退Tab切换十六进制视图查看常量池和明文密钥右侧的xref面板其实是核心。点中一个方法名面板会列出所有调用者和被调用者还能按“字符串引用”“常量引用”“方法引用”筛选。我拿到一个回调函数后习惯先看它的引用列表如果这个函数只被系统框架调用说明它是入口如果被三四个业务方法调用多半是公共工具函数值得重点分析。用这个逻辑圈定核心函数比在一堆类名里盲目扫快很多。3. 从APK到关键入口导入解析、Manifest定位与xref追踪的完整流程3.1 导入APK时的解析器与选项设置把APK拖进JEB窗口后导入向导会弹出解析器选择。APK这种格式会自动拆分成DEX、资源、原生库三个部分但有几个选项影响后续分析深度。默认解析器会处理DEX和resources.arsc如果你的目标主要看Java层逻辑保持默认就行如果样本的so值得做深度逆向把“解析原生库”和“解析debug信息”都勾上这样符号表能保留更多。解析完成后如果类数量可疑地少说明壳把dex抽走了后面避坑章节再细说。导入前我习惯先用命令行看一轮基础信息否则打开JEB才发现包名都不一样就浪费时间了。下面这串命令是每批样本都要跑的基线。file target.apk aapt dump badging target.apk | head -30第一条命令确认文件类型防止有人把zip改成apk后缀。第二条命令读取Manifest摘要重点看包名、versionCode、launchable-activity和权限申请列表。head -30只截前30行够看主信息避免输出刷屏。拿到launchable-activity后就能在JEB里直接按G跳转到对应类不必在几百个类里翻。权限列表倒是常被忽略的地方一个工具类App如果申请了读通讯录和定位权限说明它大概率有额外采集逻辑这类入口值得优先跟。这些基础信息配合JEB的导入报告能快速判断样本的复杂度。如果你发现它申请了十几种权限但业务功能很少基本可以确定是要重点分析的触发型样本。JEB导入完成后可以在Console看到解析摘要里面有类数量、方法数量、字符串数量我都会扫一眼和另一款工具的输出交叉比对差太多就要怀疑解析环节出了问题。3.2 定位入口从AndroidManifest到第一个业务函数JEB的Manifest视图里能看到完整的AndroidManifest.xml解析结果包含所有组件、权限和应用类。一般我会先看launchable-activity那是App启动的类然后看有没有exported的service或receiver。这类导出组件可以被外部触发经常是分析者在样本里埋的控制入口。定位到主Activity后跳进onCreate方法按F5切成伪代码再看它加载了哪个布局、绑定了哪个按钮事件。按钮注册点会调用setOnClickListener回调方法里往往就是第一个业务函数。顺着这个回调往深处追通常能看到校验逻辑、网络请求、本地存储读写。我处理一个登录类样本时的习惯是先找输入框字段对应的组件ID再查这个ID在哪个方法里被读取读取点就是账号密码进入业务层的位置。这里可以用一小段JEB的Python脚本把所有Activity列出来。把它保存成.py文件用JEB的File→Run Script加载就能在Console输出当前工程里所有Activity类名不需要手动在项目树里翻。from com.pnfsoftware.jeb.client.api import IScript from com.pnfsoftware.jeb.core.units.code.android import IDexUnit class FindActivities(IScript): def run(self, ctx): eg ctx.getEnginesContext() if eg is None: print(no active context) return for prj in eg.getProjects(): for unit in prj.getUnits(): if isinstance(unit, IDexUnit): for cls in unit.getClasses(): name cls.getName() parent cls.getSuperclass() if parent and android.app.Activity in parent: print(name)这段脚本逻辑不复杂ctx.getEnginesContext()拿当前JEB打开的引擎上下文getProjects()遍历所有已打开的工程getUnits()取工程下的代码单元用isinstance(unit, IDexUnit)过滤出DEX单元最后遍历每个类检查其父类是不是Activity的子类。注意getSuperclass()在不同版本返回的可能是字符串或类对象所以用if parent and android.app.Activity in parent这样的写法兼容性更好。如果你打开了多个APK输出会按工程顺序分组可以每处理完一个工程后加个分隔打印否则结果会混在一起。3.3 用xref和数据流图把核心函数圈出来入口定位只是第一步真正难的是从入口到核心算法的调用链。JEB的xref面板在这里是主力工具。选中一个方法按X查看它的调用者逐层向上追踪反过来看它内部的引用能发现哪些字段和常量参与了计算。我处理过的一个样本主Activity只做界面展示真正的加密函数藏在某个业务类的静态方法里入口Activity与核心类之间隔了五层监听器和回调接口手动翻很容易断链。JEB的“Generate Code Graph”功能可以把当前函数画成控制流图图上每个块对应一段直线执行指令分支条件、循环、跳转目标都标得清清楚楚。我一般会生成核心校验函数的图保存成图片再在图上标注关键分支。比如某个函数里有两个分支左边是对错误输入的异常处理右边才是正常加密流程标识在图上比反复上下滚动代码直观得多。字符串回溯也是圈定核心函数的好路子。在字符串搜索视图里输入特征值比如HTTP头、加密算法名、密钥常量值双击引用该字符串的方法再用xref反向追。注意有些混淆器会把字符串做编码隐藏JEB的字符串视图里看不到明文这时候可以切到十六进制视图手工搜或者直接用后面会讲到的脚本批量提取分析。4. 动态调试与寄存器观察附加App、下断点与smali级改写的实操记录4.1 搭建可调试环境把App变成debuggable动态调试前需要让目标App能附加调试器Android系统默认对非debuggable包是拒绝附加的。常见做法是准备一个root过的模拟器或测试机把包名加进调试白名单。下面这串命令是我每次处理测试样本都会走的固定流程。adb get-state adb root adb shell setprop ro.debuggable 1 adb shell am set-debug-app -w com.sample.appadb get-state确认设备连接状态输出device才说明设备在线。adb root让adbd以root权限运行老版本系统可以临时提升Android 10以上的模拟器一般也开放这个开关。setprop ro.debuggable 1把全局调试开关打开注意这个属性在部分ROM里需要重启adbd才能生效执行后建议adb shell stop adb shell start重启框架。am set-debug-app -w com.sample.app指定目标包名为调试App-w表示启动时等待调试器接入这样App一启动就会停在入口等待JEB attach。这些命令只对测试环境有效正规的分析样本一般在导入前就确认过用途。我自己只在自有授权的测试样本上跑这串命令比如公司内部开发的demo或下载的CTF靶场。每次跑完会顺手adb shell setprop ro.debuggable 0恢复环境免得后续跑别的样本时被系统调试策略误伤。JEB连接调试器的方式是Debug→Start Debugger选择ADB设备目标选择刚才设置的包名。连接成功后左侧会多出一个调试视图显示当前线程栈和寄存器值。很多人在这里卡住是因为设备选了Android Studio用的那个模拟器端口JEB默认读取的adb设备列表里混入了其他连接先adb devices确认设备ID再在JEB的调试器选择窗口里对号入座。4.2 在smali层下断点观察密码校验的寄存器和内存静态分析能画出调用链但真正定位“哪个寄存器存的是密码明文”还是要靠动态调试。我处理过一个校验类函数方法签名是checkPassword(String)smali代码看着像标准逻辑但在哪一步拿到密码字节数组是关键。下面是一段简化后的smali结构基本还原了这类函数的执行顺序。.method public checkPassword(Ljava/lang/String;)Z .registers 4 # p1是方法入参对应外部传入的密码明文 const-string v0, meglv_secret_2024 invoke-virtual {p1}, Ljava/lang/String;-getBytes()[B move-result-object v1 # v0是硬编码密钥v1是密码转出的字节数组 invoke-static {v0, v1}, Lcom/sample/core/Crypto;-encrypt(Ljava/lang/String;[B)[B move-result-object v2 # 比对结果并返回布尔值 ... .end method注意smali里.registers 4声明了这个方法使用4个寄存器参数p1是第一个入参实际对应保存密码明文的引用。调试时在invoke-virtual那行下断点启动App触发登录断点停下后在变量窗口里查看p1的内容可以确认密码是否原样传入。const-string v0那行把硬编码密钥加载到v0这类字符串常量就是后面做算法还原的关键输入。我曾经在一个支付类样本里看到密钥直接写在smali里不跑动态调试根本不会注意它和后续加密之间的赋值关系。动态调试时的寄存器窗口默认显示当前帧的寄存器值有些值是引用类型可以直接展开看字符串内容。比看伪代码更直观的一点是你能看到move-result-object v1执行前后v1的变化从而确认getBytes()的结果是否真的进入了下一次加密调用。如果加密函数内部对参数做了二次拷贝断点还得继续往下打盯住每一次move指令直到数据落到某个最终调用点。4.3 用脚本快速定位调试目标避免手动翻类面对几百个类不记得入口路径时JEB的脚本引擎能帮你直接找到调试目标。脚本的思路与方法定位类似遍历所有DEX单元里的类匹配方法签名中包含特定特征名的方法打印其所在类与地址。这样调试器启动后可以直接输入地址让JEB跳到断点位置。from com.pnfsoftware.jeb.client.api import IScript from com.pnfsoftware.jeb.core.units.code.android import IDexUnit class FindMethod(IScript): def run(self, ctx): eg ctx.getEnginesContext() if eg is None: return for prj in eg.getProjects(): for unit in prj.getUnits(): if isinstance(unit, IDexUnit): for cls in unit.getClasses(): for m in cls.getMethods(): if checkPassword in m.getName(): print(m.getSignature())这段脚本的原理是遍历所有类的所有方法用checkPassword in m.getName()做模糊匹配。m.getSignature()返回方法的完整签名包含返回类型和参数列表打印出来就能快速复制到G跳转框里。我自己会在特征串里同时试几个关键词比如encrypt、check、verify一次跑出所有候选方法再按签名判断哪个是真正入口。这个做法在native层分析里同样有效只不过要把getMethods()换成ELF单元的函数列表API。JEB的调试器接口本身也支持脚本控制比如通过IDebuggerUnit添加断点、恢复执行、读取内存但这些API在不同版本里差异较大。我的建议是用Console的API浏览器先查当前版本的IDebuggerUnit方法名再写脚本不然换了版本代码就跑不起来了。图形界面下断点对你来说已经够用脚本化适合做批量样本的自动化分析流程单样本调试还是用界面点更快。5. JEB使用避坑与排查四种常见的翻车现场与处理顺序5.1 反编译结果和jadx对不上谁错了现象同一个APKjadx里能看到很“干净”的Java代码JEB的伪代码里却充满冗余变量和goto式跳转看起来像编译器优化前的中间产物。原因两个工具对字节码的还原策略不同。jadx倾向于激进简化会把寄存器赋值链折叠成更短的Java表达式JEB默认保证还原过程中不丢失字节码信息所以保留了大量中间赋值。这不算JEB的bug是它的设计取向。解决打开JEB的反编译选项勾选Simplifier关闭Preserve Register Names再重新解析。如果JEB的伪代码还是觉得绕直接看smali视图那才是原始指令的忠实体现。我在处理VMP加固样本时反而会关闭Simplifier因为简化后的伪代码看不懂它在做什么原始寄存器操作顺序才能看出壳的跳转规律。5.2 dex列表空白类数量为0现象APK导入后工程树里DEX单元存在但展开后只有几个类甚至清零。Console里不报错但类列表就是空的。原因APK做了整体加固或者dex抽取。壳在加载时将真正的dex解密到内存安装包里的dex只是空壳JEB直接解析文件当然看不到原始类。有些抽取壳会把方法体抽到单独的二进制段里普通的dex解析器不认识这段数据。解决先不要怀疑JEB用strings命令扫一遍APK看看有没有常见的壳特征如libjiagu.so、libDexHelper.so之类的加固标志。确认加固后先脱壳动态dump出内存中的完整dex再把这个dex拖进JEB重新解析。解析前在导入向导里手动指定DEX解析器并勾选“识别加密dex”相关选项。如果你装的是带脚本插件的完整版资源里面有一批脱壳辅助脚本直接调用比手写稳定。5.3 native层断点打不上或程序启动就闪退现象so文件的JNI函数上下好断点启动调试后App秒退或者JEB提示断点地址无效。有时候断点能打上但运行到断点前程序就自己终止了。原因一是so被加花指令或控制流混淆断点地址落在Thumb模式的偶数指令边界之外二是代码里有反调试检测ptrace机制发现调试器挂载后主动自杀三是地址计算错误Java层拿到的native方法地址和so模块基址加偏移的换算少算了一段。解决先在JEB的ELF单元里查看so模块的基址和函数偏移确认要下断点的地址是不是指令边界。再用IDA的ARM插件对比一下同一地址的反汇编结果确认JEB的Thumb切换是否正确。反调试检测比较猛的话静态分析优先先把so的实现逻辑读出来再决定是否值得绕过检测跑动态调试。我在一个强混淆样本上最终放弃了动态调试直接靠JEB的静态反汇编还原了整个加密流程时间反而更短。5.4 中文乱码与字符串编码问题现象字符串视图里中文显示成\uXXXX或者直接变成乱码搜索人名字符串搜不到。有些韩文、日文样本更明显所有字符串都是转义序列。原因JEB解析字符串池时默认按修改版UTF-8处理但部分APK用的是UTF-16编码的字符串池尤其是脱壳后的dex重打包文件编码标志在dex头里不一致。解决在字符串视图的设置里尝试切换编码模式或者导出字符串后用Python统一解码。下面这行处理逻辑适合批量修python3 -c sopen(strings.txt,encodingutf-8).read(); print(s.encode(utf-8).decode(unicode_escape))这段命令用unicode_escape把\uXXXX转成真实字符再按UTF-8打印。如果输出的中文还是乱码说明源文件不是简单的unicode转义需要先确认dex字符串池的实际编码再用对应编码解码。我一般会先用strings -e S target.apk看编码类型-e S是16位小端编码如果文件里能搜到中文明文说明编码方向对了。5.5 adb连接不上调试器一直卡在等待attach现象JEB启动调试器后停在“Waiting for debugger”adb列表里设备正常但JEB就是连不上去。换个真机样本反而秒连。原因Android版本差异和设备品牌定制系统导致调试协议握手不一致。部分模拟器的adb实现不完整或者系统的ro.debuggable策略在开机后被服务覆盖。也有可能在JEB里选中了错误的设备多设备环境下调试目标选错。解决先单独用adb命令验证调试会话是否建立adb shell ps -A | grep 包名看目标进程是否在等待调试器。如果进程状态正常检查JEB的Debugger窗口里目标端口是否被其他工具占用。很多人在Android Studio和JEB混用时adb端口被Studio占用JEB连不上。统一用JEB自带的ADB路径或者先关掉Android Studio再调试基本能解决。从那以后我拿到新样本都会用统一的环境脚本初始化adb并确认端口再开JEB省了太多排查时间。6. 把JEB当分析引擎headless批量解析与自动化验证的进阶玩法6.1 命令行模式一次跑完整个样本库的headless解析图形界面适合精读单个样本批量分析时必须切换成headless命令行模式。JEB安装目录下提供了独立的headless脚本命令行参数和控制台几乎一致但不用启动GUI。./jeb_headless.sh -c -i sample.apk -o sample.jdb-c表示执行一次命令模式任务-i指定输入APK路径-o指定输出的JEB工程文件路径。跑完后会生成一个.jdb工程文件下次用GUI打开这个文件直接继续分析不需要重新解析。批量的场景我会写个循环脚本把目录下所有APK都跑一遍配合--reportjson导出结构化分析报告然后统一比对类数量、方法数量、可疑字符串。报告里的字段够我用Python写个自动告警规则。命令行模式对内存消耗还是比较大建议样本量不超过10个时连续跑量大就分批执行。多说一句headless模式不会加载图形界面插件有些依赖界面交互的功能不可用但解析器和脚本引擎都在。如果你要批量执行的是字符串提取或xref统计headless完全够用。6.2 用工程文件做回归对比混淆字典版本变了没有导出工程文件的价值不止于保存状态还能做版本对比。同一App不同版本的APK分别生成.jdb文件然后用脚本提取两个工程里的类名列表差集就是新增或移除的类。这招在验证混淆字典是否生效时特别有用。我常遇到“自定义混淆字典不生效”的问题怀疑打包时字典没真正参与R8混淆但每次看打包日志都太慢。直接把两个版本的工程文件跑一遍对比脚本哪个类没被混淆立即暴露。技巧是把jdb当作工程快照来用分析完一个样本后在关键节点另存一份后续改动不会污染原始工程。追踪一个复杂混淆样本时我会在入口定位完保存一份核心算法还原完再保存一份每份都能返回对应时间点的分析状态。这比反复重新解析一个APK可靠得多解析器版本升级了也不影响历史工程。我自己的习惯是每次拿到新样本先跑一遍headless报告把基础数据和可疑点都列出来再决定要不要进GUI做深度分析。把简单的体力活交给命令行界面窗口留给真正需要人脑判断的动态调试和算法还原。希望帮到你。本文还有配套的精品资源点击获取
返回列表