1、快速定位关键 smali 代码
1.分析流程
搜索特征字符串
搜索关键api
通过方法名来判断方法的功能
2.快速定位关键代码
反编译 APK 程序,AndroidManifest.xml => 包名/系统版本/组件
程序的主 activity(程序入口界面)
每个 Android 程序有且只有一个 主Activity
分析程序的执行流程
需重点关注的 application
application 执行时间
授权验证
3.定位关键代码的技巧
信息反馈法 ( 资源id/字符串 )
特征函数法 ( api函数 )
顺序查看法 ( 分析程序执行流程 / 病毒分析 )
代码注入法 ( 动态调式 / 插入log / 查看logcat/分析加解密 )
栈跟踪法 ( 动态调式 / 函数调用流程)
Method Profiling ( 方法剖析 => 动态调式 / 热点分析/ 函数调用流程 )
6 种定位关键代码的方法,当然还有其他方法,以后遇见了补充上:
- 1. 信息反馈法( 资源id / 字符串 )。根据程序正常运行的提示信息进行定位,例如:错误提示,运行提示等等。可以在程序中直接搜索字符串,当提示信息在 String.xml 资源文件中的时候,可以根据 R.java 的映射文件,查找对应的资源id,然后在 smali 或者在 ida 窗口中进行搜索
- 2. 特征函数法( api 函数 )。根据程序提示信息的方法,在对应的 Android 的 API 下断点,通过 API 来检索相关的代码。
- 3. 顺序查看法(分析程序执行流程 / 病毒分析)。从 AndroidManifest.xml 中找到 主 Activity 界面,然后顺序分析代码,通常在分析病毒软件时使用。( 通过 UI 进行分析, adb shell dumpsys activity top )
- 4. 代码注入法。( 动态调试 / 插入log / 查看logcat / 分析加解密 )。动态调试,又叫插桩,即在关键反汇编代码处插入可以输出 logcat 调试信息的代码
- 5. 栈跟踪法( 动态调试 / 函数调用流程 )。栈跟踪法属于动态调试的方法,原理是输出运行时栈调用跟踪信息,然后查看函数调用序列来理解方法的执行流程
- 6. Method Profiling( 动态调试 / 热点分析 / 函数调用流程 )。动态调试方法。主要用于热点分析和性能优化。除了可以记录每个函数赵勇的CPU时间外,还可以跟踪所有的函数调用关系,并提供比栈跟踪法更详细的函数调用序列报告。
2、monitor 使用
monitor 位于 SDK 的 tools 目录下,直接运行即可。可以调试连接的终端设备(包含 仿真器 与 真机 ),也可以查询终端设备运行状态,终端设备进程状态,线程状态,文件系统,日志信息(logcat)等。以及控制终端设备完成一些操作。还可以向目标机发送短信、打电话,发送地理位置信息等。可以像 gdb 一样 attach 某一个进程调试。
monitor 总的来说它是一款性能分析工具,可以帮助开发者快速了APP的运行情况。具体可见 https://developer.android.com/studio/profile/monitor 。
在安卓平台上,每个应用都运行在自己的进程上,同时每个应用也都运行在自己的虚拟机(VM)上。每个VM公布了唯一的端口号以供调试器连接。
当 monitor 启动后,会连接到adb。当有设备连接上,VM监测服务就在 adb 和 monitor 之间创建,它会通知 DDMS 设备上的VM是启动了还是终止了。一旦VM是运行的,monitor 就获取VM的进程ID(pid),通过adb和设备上的adb守护进程(adbd)建立到 VM调试器的连接。到此,monitor 就可以使用约定的线协议与VM通信。
monitor 给设备上的每个VM分配一个调试端口。通常,monitor 分配的第一个可调试端口号是8600,下一个是8601,依次往下类推。当调试器连接 到已分配的一个端口时,VM上的所有通信都会被关联到调试器。一个调试器只能连接一个单独的端口,但是monitor 同时可以处理多个连接的调试器。
默认的,monitor 也会监听monitor 的“基本端口”(默认为8700)。基本端口是一个端口转发端口,可以通过8700端口接受来自VM所有调试端口的通信并可以发送信息到调试器。这就允许你将调试器连接到8700端口,然后可以调试所有设备上的虚拟机。在monitor 设备视图下,转发的通信可以被当前所 选进程终止。
接下来的屏幕截图会在Eclipse中显示标准的monitor 屏幕视图。如果你是从命令行启动的monitor ,截图会略有不同,但绝大部分功能是相同的。注意这个特殊进程,com.android.email 它在模拟器上运行时的调试端口是8700,而分配给它的端口是8606。这就表明 monitor 当前将 8606 端口转发到静态调试端口 8700。
主界面(手机root后才会显示所安装应用):
Android Device Monitor :DDMS、HierarchyViewer。
ddms 已经被官方废弃,使用 monitor 代替
Monitor 可以看做 DDMS 和 HierarchyViewer(层次结构)的组合。
DDMS( Monitor ) 功能详解
Android SDK 中的 ddms 使用详解:https://blog.csdn.net/x83853684/article/details/80643131
首先 DDMS 被分为三个部分。左上角为 Device 面板,详细罗列了与电脑相连的终端设备的信息。右上角为详细的功能选项卡,下方为日志信息以及终端信息.
Devices 面板
1,左边显示了所有当前能找到的所有模拟器或设备列表和每个设备当前正在运行的虚拟机列表。虚拟机是按程序的包命来显示的。
2,通过这些列表可以找到运行着想调试的activity的虚拟机。每个虚拟机旁边的是“debugger pass-through”端口,链接到其中一个端口就会链接到设备上对应的虚拟机。不管如何,在用DDMS时,只需要链接到8700端口,因为DDSM 转发所有的通信到当前选择的虚拟机。这样,就不用在每次切换虚拟机是重新配置debugger端口。
3,当一个正在运行的程序调用waitForDebugger()函数时,客户端名字旁边会显示一个红色的icon,知道debugger连上对 应的虚拟机,这是debugger会变成绿色。
4,如果看到叉icon,着意味着DDMS用于不能打开虚拟机的端口而不能建立debugger与虚拟机建立连接。如果看到所有的虚拟机是这样, 很可能是有另外一个DDSM实例在运行。
这个面板包含了所有的与IDE相连的设备列表以及每个设备上运行的进程的列表,如下图所示:
device 窗口列出了 模拟器(或 真机)中所有的进程,显示进程时会显示进程ID (上图中online那一列显示的即是终端上运行的进程的ID) 以及与进程相关联的端口号,连接端口号从 8600 端口依次往下增加,8700 是 DDMS 接收所有连接终端返回信息的端口,即是 base 端口。Devices 面板顶端从左往右有多个按钮,
上面一排的按钮功能,可以把鼠标放上面会自动显示按钮说明。如果你没有运行或调试程序的话,这些图标是不可用的!
当你选中某个进程,并按下调试进程按钮时,如果 eclipse 中有这个进程的代码,那就可以进行源代码级别的调试。有点像gdb attach。图片抓取按钮可以把当前android的显示桌面抓到你的机器上,也是非常有用。
开始方法分析:
- 1.在设备选项,选择要进行方法分析的进程
- 2.点击 Start Method Profiling按钮。
- 3.与应用进行交互,开始要分析的方法
- 4.点击 Stop Method Profiling按钮。DDMS停止分析应用并打开Traceview,它包含了在点击Start Method Profiling和Stop Method Profiling之间方法分析收集到的信息.
下面一一分析这些按钮的功能
1. Debug: 实现使用DDMS对代码进行调试,使用该功能的前提是 IDE中具有该运行进程的源代码,否则该按钮为灰色,功能无法使用.
2. Update heap: 实现对进程中的堆进行更新的操作。只有当选择这个按钮后,在右侧的功能面板中的heap选项卡中就能够看见当前进程的堆使用情况:
点击 Cause GC 可以触发虚拟机的垃圾回收机制。
3. Dump HPROF file: 将当前进程堆使用情况生成文档,使用这个功能可以更加详细的分析当前堆的情况,有利于查找内存泄等问题。
4. Cause GC: 触发垃圾回收机制,可以点击后查看当前进程的堆使用情况
5. Update thread: 这个功能与update heap一样,当点击了这个按钮才能在右侧面板的thread选项卡中查看当前进程的所用的线程运行状态
6. Start Method Profiling: 开始进行方法分析。这个功能比较重要,后面单独写文章分析
7. Stop: 终止当前选中的进程。
8. Screen Capture: 截屏按钮,捕获当前设备的屏幕状态,该功能具有一定的延时
功能面板
功能面板从左到右有多个选项卡分别是:
1. Threads: 这个不必多说,表示当前进程中的所有线程状态。线程视图列出了此进程的所有线程。
ID: 虚拟机分配的唯一的线程ID,在Dalvik里,它们是从3开始的奇数。
Tid: linux的线程ID,For the main thread in a process, this will match the process ID.
Stauts: 线程状态,
running: 正在执行程序代码
sleeping:执行了Thread.sleep()
monitor: 等待接受一个监听锁。
wait: Object.wait()
native: 正在执行native代码
vmwait: 等待虚拟机
zombie: 线程在垂死的进程
init: 线程在初始化(我们不可能看到)
starting:线程正在启动(我们不可能看到)
utime: 执行用户代码的累计时间
stime: 执行系统代码的累计时间
name: 线程的名字
2. Heap: 表示当前进程堆使用情况。展示一些堆的状态,在垃圾回收其间更新。当选定一个虚拟机时, VM Heap视图不能显示数据,可以点击右边面包上的带有绿色的”Show heap updates”按钮,然后在点击”Cause GC “实施垃圾回收更新堆的状态。
3. Allocation Tracker: 分配跟踪器,后面单独写文章分析。在这个视图里,我们可以跟踪每个选中的虚拟机的内存分配情况。点击”Start Tracking”后点击”Get Allocations “就可以看到。
4. NetWork Statistics: 网络分析功能。
5. File Explorer: 浏览终端的文件系统,进行文件相关操作。通过 Device > File Explorer 就可以打开 File Explorer。这里可以浏览文件,上传上载删除文件,当然这是有相应权限限制的( 只有 root 权限才能查看 )。 在这里面可以进行将外部文件导入到终端中,或者将终端文件导出,或者删除终端文件,具体操作是右上角三个按钮:
文件操作还是比较重要的,比如一个应用涉及到了SQLite数据库使用,此时可以使用这个功能,导出数据库文件单独分析
6. Emulator Control: 可以实现往模拟器中打电话,发送短信,发送地理位置坐标等功能。
在这里,可以模拟一些设备状态和行为。
Telephony Status:改变电话语音和数据方案的状态,模拟不同的网络速度。
TelePhony Actions:发送模拟的电话呼叫和短信到模拟器。
Location Controls:发送虚拟的定位数据到模拟器里,我们就可以执行定位之类的操作。可以收工的在Manual里输入经度纬度发送到模拟器,也可以通过 GPX和KML文件。
有了AndroidStudio 如果启动了模拟器,可以直接在模拟器上实现相关信息的发送而不需要借助于DDMS
7. System Information: 这个选项卡里面可以查看终端的CPU负载以及内存使用情况。具体的试过就知道了。
LogCat and Console
这一部分就是系统运行产生的日志信息以及终端打印的信息。比较关心的还是LogCat。
这里的 LogCat 与 AndroidStudio 中的 LogCat 是一样的。使用 LogCat 可以根据程序中的运行日志判断当前程序运行的状态。终端设备一般运行较多的进程,每个进程运行都有大量的日志产生。因此一般需要使用 过滤器过滤其他进程信息。过滤器在 Saved Filter 中的以添加过滤器:
如果指定要观察某一个进程的日志信息,那过滤器就使用PID(进程ID)进行过滤即可,进程ID可以在Devices面板中得到,过滤的等级根据自己代码中写的等级酌情考虑,这样就能够实现只是观察一个进程的日志信息。
示例:去掉 "车来了" app 的广告
首先使用 AndroidKiller 反编译 "车来了" app ,找到 包名
然后 打开 monitor 过滤 com.ygkj.chelaile.standard 的 log,通过观察 log 可以发现,log 中出现了 " 成功发送简单上报广告时间埋点 " 之类的信息,右键 -》 Fiter similar messages... 即可看到详细信息,
复制 广告的网址,然后在 AndroidKiller 中 全文件查找,并替换为空,导致 拼接 的 URL 无法访问,从而达到去掉广告的效果:
示例:通过 monitor 层级结构,快速定位找到关键代码
:https://blog.csdn.net/qq_34149335/article/details/82491997
3、smali 之 log 插桩
分析反编译代码之大神器
如果说使用dex2jar和JD-GUI获得了一个APP反编译后的JAVA代码,再结合smali代码调试器来进行调试还不够爽,不够畅快的话,下面将介绍一个帮助分析代码执行流程的大神器。这个神器优点很多,不过遗憾的是它有一个致命的缺点!就是威力太大,能让使用它的人快速分析出一个复杂APP的执行流程,快速定位关键之处进行修改以达到各种目的,尤其对于像我一样的Android逆向新手来说,这是非常致命的。为什么非常致命?因为使用了该神器后,1个小时就找到了关键代码,弄清楚执行逻辑,1天之内就实现了程序,解决了外行人看来难度很高的问题。由此带来的后果就是自我感觉良好,自己感觉自己很牛逼,蒙蔽了自己的双眼,终日沉溺在这种骄傲的状态中,从而不能继续虚心刻苦学习技术知识,久而久之,在技术水平上落后别人一大截,对自身发展造成严重影响!所以使用该神器前必须清楚地认识到可能带来的这些弊端,确认自己能调整好心态以后再继续往下看,否则请按ALT+F4关闭。
一般的商业APP代码量巨大,而且做过混淆处理。我所面对的这个APP反编译后仅JAVA代码文本就达到了100多MB,做过混淆处理后,代码里几乎看不见一个局部变量的名字,大部分的函数名、类成员变量名都是abcdefg之类,且反编译后的代码看起来怪怪的。如果仅仅是静态分析,读这些代码,将会是一件非常痛苦的事情。尤其是对我这种Android正向开发都不会的新手来说,某个按钮点击的响应函数在哪里,下拉刷新的响应函数在哪里,找起来很困难。好不容易找到了登录按钮的响应函数,顺着函数调用一层一层往里看,又遇到了一些抽象方法和异步操作,无法仅从按钮响应函数的调用栈上找到最后发送网络请求的关键代码。虽然可以结合smali调试器来分析,下断点后查看实现了抽象方法的具体对象是什么,查看调用栈理清调用结构,但整个工作还是进行得很缓慢。再加上调试器各种奔溃和不准,搞去搞来各种心烦,导致了一个严重的后果,就是搞着搞着就不由自主的打开了游戏,以调节郁闷的心情,从而搁置了项目进度。
思来想去,我觉得与其主动去分析它的代码执行流程,不如让它来主动告诉我它的代码执行流程。怎么告诉?首先想到的是打日志,在上篇文章中我们通过打开调试开关,修改它的smali代码重定向它的日志到android.util.Log,然后打开DDMS在LogCat中看到了它的全部日志。不过这样还是远远不够,因为它的日志只记录了一些运行中的状况和错误。我的目的是想让它的日志告诉我,它调用了哪些函数,以及调用的先后顺序。在上篇文章中讲到,我们也可以通过TraceView来分析它从登陆按钮点下到登录结果出来的这个过程中调用的所有函数,不过TraceView给出的结果充斥着很多很多的系统函数,而且难以看出调用顺序,非常不好用。如果能让这个app通过日志主动告诉我们它调用了哪些函数,且不包含系统函数,仅仅是它自身代码的函数,那该多好。
首先想到的方法就是手工修改它的smali代码,插入日志。假如目标APP有一个函数的smali代码如下:
.method protected getLoginPassword()Ljava/lang/String;
.registers 2
.prologue
.line 809
iget-object v0, p0, Lcom/ali/user/mobile/login/ui/AliUserLoginActivity;->mPasswordInput:Lcom/alipay/mobile/commonui/widget/keyboard/APSafeEditText;
invoke-virtual {v0}, Lcom/alipay/mobile/commonui/widget/keyboard/APSafeEditText;->getSafeText()Landroid/text/Editable;
move-result-object v0
invoke-interface {v0}, Landroid/text/Editable;->toString()Ljava/lang/String;
move-result-object v0
return-object v0
.end method
在这段smali代码里插入三行代码:
const-string v0, "InjectLog"
const-string v1, "com.ali.user.mobile.login.ui.AliUserLoginActivity.getLoginPassword()"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
这三行smali代码对应的JAVA代码是:
android.util.Log.d("InjectLog", "com.ali.user.mobile.login.ui.AliUserLoginActivity.getLoginPassword()");
当然你可以可以插入其它各种各样的代码,你可以先创建一个Android项目,把JAVA代码写出来,编译,再反编译,再把对应的smali代码复制出来粘贴进去。
好了,插入完成以后,重新打包,安装,运行。每当执行这个函数的时候,我们就可以在LogCat中看到输出,我们就知道它调用了这个函数,包名、类名、函数名都有了。试想,如果我们把它整个app的所有smali代码中的所有函数全部插入这三行smali代码,重新打包,安装,运行。然后,就可以冲一杯咖啡,打开DDMS,打开LogCat,静静的看着它把所有的函数调用流程输出来。然后我们在APP上点一下登录,整个登录过程暴露无遗!函数的调用顺序就是日志输出的时间顺序,没有任何杂质,没有系统函数,那么纯净,那么完美。此刻它仿佛没有了任何秘密,一丝不挂的站在你面前,任由你炽热的目光在它身上那些精致的部位上扫来扫去。
且慢,先擦干口水。事情没有那么容易,就像追一个漂亮的女生一样,你送一次礼物就想追到她?做白日梦吧!至少得经历岁月的洗礼,长时间保持高层次的灵魂交流,才能进一步深入发展。也就是说你需要在这个app的上万个函数中插入这三行smali代码,看到这里,首先要做的事情就是先把你手中的铁锤收起来,不要打我。
我们当然不可能手工去完成这上万次操作,我们是程序员,这种重复枯燥的工作自然让程序来完成。我们可以写一个文本分析的小程序,遍历反编译出来的代码目录下的所有smali文件,利用字符串搜索法和正则表达式,找出一个个的函数,并且从smali文件第一行.class的定义中获取包名和类名,然后从.method的定义中获取函数名、参数、返回值信息,生成上面的三行smali代码,插进去。
"自动化"、"批量"、"文本处理"、"脚本",想到这些关键词,自然就想到了Python,用它来干这个事情将会更加得心应手。经过几小时奋战,完成了这个批量插代码的Python脚本。自动批量插入后对APP重新打包,安装,运行,奔溃了。仔细想想,对,还有寄存器的问题没有处理,有的函数本来是没有局部变量,没有使用寄存器的,".method"定义的函数块中的第一行为".registers 0",而我插入的代码里用到了两个寄存器。后来修改了Python脚本,在分析每个函数的时候也检查registers,如果registers小于2个则改为2个。重新插入代码后,对APP重新打包,安装,运行,还是奔溃了。后来看了这篇文章:https://liuzhichao.com/p/919.html 了解了registers的意义,发现我自己写的Android APP反编译以后每个函数第一行都是.locals,而我反编译的这个商业APP的代码中每个函数第一行都是.registers。使用.registers声明寄存器数量有个很大的不好之处就是参数寄存器也包含在.registers声明的寄存器中,如果一个函数有两个参数,没有局部变量,那么这个函数会声明.registers 2,我的脚本检测到这里认为寄存器够用,然后就插入了代码,而我插入的代码里用到了v0和v1寄存器,在赋值的时候把参数寄存器的内容覆盖了,因此带来了一些问题。而且如果它的代码里本来就使用了v0、v1寄存器,我直接这样把代码插进去,也会带来一些影响。
要解决这个问题,可以继续完善Python脚本,对它的smali代码进行更多的分析,分析这个函数已经用了多少个寄存器,有多少个参数寄存器,序号分别是什么,然后再生成合适的插入代码并修改.registers数量,但这样做的话就比较麻烦了。偷懒是我的作风,而这种做法显然不符合我的作风,于是我觉得不应该再继续完善Python脚本,而是精简Python脚本。Python脚本不再分析它smali代码中的函数,而是直接不管三七二十在每个函数中插入一行对void PrintFunc()的调用代码。void PrintFunc()是我自己写的函数,无参数无返回值,调用它对应的smali代码大概是这样的:
invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V
不使用任何寄存器,没有返回值,显然这样的代码插入到目标APP中的函数中,不会对宿主函数造成任何影响。同时新建一个Android应用项目,写下如下JAVA代码:
package com.hook.testsmali;
import android.util.Log;
public class InjectLog
{
public static void PrintFunc()
{
Thread cur_thread = Thread.currentThread();
StackTraceElement stack[] = cur_thread.getStackTrace();
Log.d("InjectLog", stack[3].toString() + "[" + cur_thread.getId() + "]");
}
}
注意,我的包名是com.hook.testsmali,类名是InjectLog,函数名是PrintFunc()。你写的JAVA代码不必和我一样,但调用PrintFunc()的smali代码要和JAVA代码的包名、类名、函数名一致。
从上面的代码可以看到,在PrintFunc()的实现上,先获取调用栈的信息,然后取出调用栈中下标为3的元素,这个正是调用void PrintFunc()的调用者的函数名、包名、类名,然后随同线程ID信息,通过日志输出。随后精简我们的Python脚本,Python脚本需要干的事情就是遍历所有smali文件,向smali中的每一个函数中插入invoke-static {}, Lcom/hook/testsmali/InjectLog;->PrintFunc()V 完毕!然后我们对自己写的这个APP进行反编译,拿到这个void PrintFunc()函数的smali代码文件InjectLog.smali,在目标APP的代码文件夹中创建这个路径com/hook/testsmali然后把InjectLog.smali放进去,重新打包目标APP,安装。然后打开DDMS,设置LogCat过滤器过滤出Tag为InjectLog的日志。
接下来,嘘!!运行目标APP,此后在目标APP中的每一次点击,都是那么酣畅淋漓,LogCat中的日志如潮水般喷涌而出,四处飞溅。对,就是那么的丝滑,那么的畅快,那么的清澈,有没一丝杂质。效果如下图所示,每一个过程,每一个步骤,都赤裸裸的展现在你面前,看得你面红耳赤!
我们也可以不输出线程ID,因为LogCat已经标示出TID来了,但是导出日志后这个TID就不见了,所以还是输出一下。日志的输出时间顺序就是函数的调用顺序,顺着这个调用顺序看dex2jar和JD-GUI反编译出的JAVA代码,定位超快、效率超高,分分种种搞出些事情来。我指的是那些你侬我侬的情话。
需要注意的是在向目标APP的smali代码的每个函数中插代码时,并不是所有函数都需要插,类的静态构造函数一般不需要插,因为它是用来初始化类的静态成员的,没有太多的关键代码。synthetic函数也不需要插,我不知道synthetic函数是什么意思,但是发现smali中的synthetic函数并没有被dex2jar和JD-GUI反编译为JAVA函数,所以忽略它。另外抽象方法也不需要插,这个不用解释。当然这个批量自动插代码的程序可以使用任何编程语言实现,它只是个文本处理工具。
另外在实际使用中,并不一定要给目标APP的所有函数插代码,我们可以先根据包名猜测一下它的功能,然后对这个包下的所有函数进行插代码。
最后,千万不要低估此神器的威力,我在使用过程中屡试不爽,结合smali代码调试器,很快就分析出登录按钮点下去后干了什么,以及最终发送了什么HTTP请求,收到什么响应内容。亲手试一试,相当亦可赛艇!
思路总结:通篇实际上讲的是一个插入代码打LOG来跟踪代码执行流程的思路,因为在smali下单步调试APP很麻烦,而且有的APP有反调试功能,单步调试没走几步就闪退。本文的做法就是写个程序/脚本,批量扫描目标APP反编译出来的所有smali文件,找到里面的每一个.mthod函数块,在每个函数块第一行插入我们自己写的打LOG代码,而我们自己写的打LOG代码通过读取调用栈输出调用者的函数名。重新打包目标APP后,APP执行他的每一个函数,都会执行我们的打LOG代码,从而输出了自己的函数名,这样我们可以从日志中看到目标APP的代码执行流程。
示例:log插桩脚本、分析支付宝
:https://blog.csdn.net/CharlesSimonyi/article/details/90691417
完善后的日志注入工具。项目地址:https://github.com/encoderlee/android_tools
需要先安装 Python3。用法示例:
1.先用 apktool 解包 apk 到目录 f:\alipay_10.1.30
2.执行 inject_log.py -c 目标是 apktool 解包出的文件夹根目录
inject_log.py -c f:\alipay_10.1.30\
这一步作用其实是把 InjectLog.smali 拷贝到 f:\alipay_10.1.30\smali\com\hook\tools\ 中,InjectLog.smali 就是 InjectLog.PrintCaller()函数的实现,对于每个apk项目,这个步骤只需做一次
3.注入代码:
① 只对一个 smali 文件注入代码
inject_log.py f:\alipay_10.1.30\smali_classes5\com\alipay\mobile\payee\ui\PayeeQRActivity.smali
② 对一个目录下的所有 smali 文件注入代码,不递归子目录
inject_log.py f:\alipay_10.1.30\smali_classes5\com\alipay\mobile\payee\ui\
② 对一个目录及它所有子目录下的所有 smali 文件注入代码,递归子目录
inject_log.py -r f:\alipay_10.1.30\smali_classes5\com\alipay\mobile\payee\ui\
小技巧:可以把 inject_log.py 放到一个目录中,把这个目录添加到 PATH 环境变量中
示例:log 插桩、Toast 弹窗、smali代码编写和植入
如果仅仅用 Smali 来分析代码,效果其实不如用 dex2jar 和 jd-gui 更直观,毕竟看反编译的 java 代码要更容易一些。
但 Smali 强大之处就是可以随心所欲的进行插桩操作。何为插桩,引用一下 wiki 的解释:
- 程序插桩,最早是由 J.C. Huang 教授提出的,它是在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针(又称为“探测仪”),通过探针的执行并抛出程序运行的特征数据,通过对这些数据的分析,可以获得程序的控制流和数据流信息,进而得到逻辑覆盖等动态信息,从而实现测试目的的方法。
插桩时注意:不要影响程序上下文,即插入的代码中,不能影响下面代码中使用的寄存器或者变量。
安卓从开发到逆向(四),smali 插桩:安卓从开发到逆向(四),smali插桩_大壮python爬虫开发的博客-CSDN博客
以 " 好搜小说大全.apk " 为例:
下载地址:https://download.csdn.net/download/freeking101/12380255
打开 好搜小说大全.apk
定位当前界面:cmd ---> adb shell dumpsys activity top
可以看到当前界面是 MainActivity ,
下面开始分析过程。。。
首先使用 "查壳工具" 检测下 app 有没有加壳,如图:
可以看到 好搜小说大全.apk 没有加壳, 没加壳好办,直接使用 AK 打开 "好搜小说大全.apk",找到 对应 smali 代码(如果加壳,需要先脱壳,然后再反编译分析,这里不涉及脱壳。)
通过上面找到的 MainActivity ,进入对应的目录( smali ---> com ---> reader ---> activity ---> MainActivity )
在 activity 目录中找到 MainActivity :
插入 log 代码:
中文 转 Unicode 编码:
替换完成后,一定要保存,然后编译,生成 修改后的 apk
安装 apk
打开 DDMS 或者 monitor,然后根据包名进行 log 过滤,
使用 DDMS 过滤 log,运行程序即可看到 log 输出。
使用 monitor 查看效果一样:
插入 弹窗 道理一样。
保存,编译,运行
也可以自己写 smali 代码,引入自定义的 smali 包,强制弹出窗口等操作。。。
示例:Log 日志的插入和分析、toast方法、栈跟踪
:https://blog.csdn.net/weixin_42680210/article/details/90384358
在安卓逆向中,常常用到 栈跟踪、toast方法、Log日志的插入 等,以便于我们更好的分析代码,下面就演示下这三个方法的使用及简单的分析。。。
1. 插入 Log 信息输出:
在方法的开始处,右击 ---> 插入代码 ---> Log信息输出
插入代码:
然后 反编译 apk,并安装到模拟器,打开 ddms 查看 log 日志
这里我们就看到了刚刚在更新代码添加的 log 信息。
2. toast 弹窗的输出
简单起见,在刚刚的 log 代码位置下面,右击 ---> 插入代码 ---> toast输出 插入一段 toast 代码
反编译 apk,安装后我们查看效果
此时我们也可以在 ddms 中查看到日志输出,由此我们可以分析:
java.lang.Exception: denglibo Toast callstack! strTip=我是toast提示
at android.widget.Toast.show(Toast.java:127)
at com.reader.activity.MainActivity.showMustUpdateDialog(MainActivity.java:327)
程序先执行了 MainActivity 文件的 showMustUpdateDialog 方法,然后执行了 show 方法( 注意:程序的运行顺序由下往上执行输出 )。通过简单的插入 toast输出,就可以查看程序的执行顺序,方便我们分析复杂的程序执行顺序。
3. 栈跟踪的使用
在刚刚的 toast 代码下面,我们右击->插入代码->stack trace栈跟踪 插入一段栈跟踪代码
smali 代码:
# 栈跟踪
new-instance v0, Ljava/lang/Exception;
const-string v1, "print trace"
invoke-direct {v0, v1}, Ljava/lang/Exception; -><init>(Ljava/lang/String;)V
invoke-virtual {v0}, Ljava/lang/Exception; ->printStackTrace()V
# 栈跟踪
回编译 apk,安装,打开安装好的 apk 和 ddms 查看
同样的我们也可以通过栈跟踪的方法查看到程序执行的顺序!
4、methodprofiling(方法分析)
协议分析常用
【Android】开发优化之 --- 调优工具:TrackView、Method Profiling:https://blog.csdn.net/fcly2013/article/details/38080333
TrackView、Method Profiling
Android SDK 自带的 tool TrackView 位于 sdk 的 tools 目录下。用法为:进入到 tools下,运行
traceview e:\loginActivityTracing.trace
即可。那 trace 文件怎么生成的呢。
生成 trace 文件
1、使用代码生成,想调哪调哪。
只需要在需要调用的地方调用
Debug.startMethodTracing("loginActivityTracing");
和结束调用的地方调用
Debug.stopMethodTracing();
就会在 sd 卡的根目录生成 loginActivityTracing.trace 文件。
没有代码呢,可以使用 DDMS 来生成。
这是开始,然后同一个按钮再点一次就生成 .trace 文件并打开了。
执行之后可以看到这样一个图片:
TraceView 界面信息介绍
TraceView 界面包括 时间面板 和 方法面板
- (1) 时间面板 ( Timeline Panel ):时间面板展示了每个线程的执行情况,其中的[1]main即为ui主线程。移动到某个位置可以查看该点对应的方法的执行信息,点击方法面板则会选中相应的方法。可以左键按住不放选中区域放大局部精细查看,不同方法用不同颜色标注
- (2) 方法面板 ( Profile Panel ):方法面板展示了所有方法的执行情况,点击某个方法可以查看在对应线程上的执行时间区域,并会显示其父方法及子方法。每个方法包括如下信息列,可点击某列进行排序,从而确定产生性能问题的函数:Incl Cpu Time, Excl Cpu Time, Incl Real Time, Excl Real Time, Incl Cpu Time%, Excl Cpu Time%, Incl Real Time%, Excl Real Time%, Calls+RecurCalls/Total, Cpu Time/Call, Real Time/Call 。
所有的 Time 都是以毫秒计算。每列具体含义及作用如下:
- a. Incl表示将所有子函数耗时也计算在内,Excl则表示不包括子函数的调用时间。对比可以确定耗时操作发生是自身还是子函数中。
- b. Cpu Time表示占用cpu执行的时间,Real Time包括Cpu Time以及等待、切换的时间等,所以一般都大于Cpu Time。对比可以判断耗时操作是否在cpu执行段内。
- c. 上面四个指标对应的%表示函数在总时间的占比。方便查看某个函数的时间占比。
- d. Calls+RecurCalls/Total表示被外部调用次数+递归次数/总次数。可以查看调用次数是否符合自己预期。
- e. Cpu Time/Call, Real Time/Call表示总的Cpu Time及Real Time与总调用次数的比例。查看每次调用的耗时,一般可通过简单此项确定每个函数的性能。
常用字段的属性说明如下:
列名 描述
Name 该线程运行过程中所调用的函数名
Incle Cpu Time 某函数占用的CPU时间,包含内部调用其他函数的CPU时间
Excl Cpu Time 某函数占用的CPU时间,但不含内部调用其他函数所占用的CPU时间
Incl Real Time 某函数运行的真实时间,含调用其他函数所占用的真实时间
Excl Real Time 某函数运行的真实时间,不含调用其他函数所占用的真实时间
Call +Recur Calls/Total 某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call 某函数调用CPU时间与调用次数的比,相当于该函数平均执行时间
Real Time/Call 某函数调用CPU的真实时间;