定位关键代码
代码注入法
通常,一个程序在发布时不会保留Log输出信息,要想在程序的特定位置输出信息还需要手动的进行代码注入。所谓的代码注入是指首先反编译Android程序,然后在反汇编出的smali 文件中添加 Log 调用的代码,最后重新打包程序运行来查看输出结果。
本小节实例为一个注册码验证模拟程序,输入用户名与注册码后点击注册按钮,程序会判断注册码是否正确,并弹出相应的提示消息
现在我们的需求是:不修改程序,找出用户名admin的注册码。
使用 apktool 进行反编译
java -jar apktool_2.9.3.jar d -f app-debug.apk -o output
定位到关键代码
...
.line 69
.local v5, "userSN":Ljava/lang/String;
invoke-virtual {v5, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
move-result v6
:try_end_0
.catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0
if-nez v6, :cond_3
...
加入 Log.v 的反汇编
const-string v3, "SN"
invoke-static {v3, v5},Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I
使用 apktool 重新打包
java -jar apktool_2.9.3.jar b output
签名后运行起来用 logcat 监听日志
adb logcat -s SN:V
随便输入注册码后,日志出现了正确的注册码
注意:这里我使用的是雷电模拟器,发现logcat
监听不到日志。
解决方法是:用adb
连接指定的端口
adb connect 127.0.0.1:5555
再次输入后就可以出现日志了
栈跟踪法
栈跟踪法同样属于代码注入的范畴,它主要是手动向反汇编后的smali 文件中加入栈跟踪信息输出的代码。与注入Log输出的代码不同的是,栈跟踪法只需要知道大概的代码注入点。而且注入代码后的反馈信息比Log注入要详细的多。
本次的案例是找到弹出toast的地方,定位到如下代码
.method private c()V
.locals 2
.prologue
.line 27
const-string v0, "who called me?"
const/4 v1, 0x0
invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v0
invoke-virtual {v0}, Landroid/widget/Toast;->show()V
.line 29
return-void
.end method
在invoke-virtual {v0}, Landroid/widget/Toast;->show()V
下面加入栈跟踪代码
new Exception("print trace").printStackTrace();
反汇编代码为
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
查看日志
adb -s 127.0.0.1:5555 logcat -s System.err:V *:W
...
01-15 10:45:15.267 3277 3277 W System.err: java.lang.Exception: print trace
01-15 10:45:15.267 3277 3277 W System.err: at com.droider.stackTrace.MainActivity.c(MainActivity.java:27)
01-15 10:45:15.267 3277 3277 W System.err: at com.droider.stackTrace.MainActivity.b(MainActivity.java:23)
01-15 10:45:15.267 3277 3277 W System.err: at com.droider.stackTrace.MainActivity.a(MainActivity.java:19)
01-15 10:45:15.267 3277 3277 W System.err: at com.droider.stackTrace.MainActivity.onCreate(MainActivity.java:15)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.Activity.performCreate(Activity.java:7144)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.Activity.performCreate(Activity.java:7135)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2934)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3089)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
01-15 10:45:15.267 3277 3277 W System.err: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
Method Profiling
作用是在执行程序时记录下每个被调用的API名称,分析人员只需查看API的调用序列即可知道这段代码的具体用途。这个功能十分强大,DDMS中也提供了类似的调试方法,它就是Method Profiling(方法剖析)。
在 DDMS 的 Devices 窗口中选择com.droider.methodprofiling
程序,点击 Devices 旁边工具栏上的“Start Method Profling”按钮开启 Method Profiling
使用 ida pro 调试android原生程序
调试android原生程序
调试一般的 Android 原生程序可以采用远程运行与远程附加两种方式来调试,本小节介绍如何以远程运行的方式来调试原生程序。
将程序复制到android设备
将 idapro 软件目录的 android_server 复制到 /data/local/tmp
adb -s 127.0.0.1:5555 push debugnativeapp /data/local/tmp
adb -s 127.0.0.1:5555 push android_server /data/local/tmp
加上可执行权限
adb -s 127.0.0.1:5555 shell chmod 777 /data/local/tmp/debugnativeapp
adb -s 127.0.0.1:5555 shell chmod 777 /data/local/tmp/android_server
接着执行下面命令,启动ida pro android调试服务
adb -s 127.0.0.1:5555 shell /data/local/tmp/android_server
IDA Android x86 64-bit remote debug server(ST) v8.3.28. Hex-Rays (c) 2004-2023
2025-01-15 11:11:34 Listening on 0.0.0.0:23946...
程序提示调试服务器已经启动,并且监听了23946号端口。打开另一个命令提示符执行以下命令开启端口转发。
adb -s 127.0.0.1:5555 forward tcp:23946 tcp:23946
现在启动 IDA Pro 主程序,点击菜单项Debugger-Run→Remnole ArmLinux/Android debugger
,打开调试程序设置对话框。在Application
栏中输入/data/local/tmp/debugnativeapp
在Directory栏中输入/data/local/tmp/
,在 HostName 一栏中输入 localhost
设置完后点击OK,ida就会执行远程的debugnativeapp
调试android原生动态链接库
调试libdebugjni.so
的jnistring
方法
启动调试服务
adb -s 127.0.0.1:5555 shell /data/local/tmp/android_server
开端口转发
adb -s 127.0.0.1:5555 forward tcp:23946 tcp:23946
打开ida的debugger-attach-remote armlinux/android debugger
这里注意几点:
- adb shell 进入后要以root运行 android_server 否则会在process看不到所有的进程
- 避免反调试:最好把android_server改名(比如改为as),端口改为其它端口(比如23944)
附加成功后就可以看到进程列表了
搜索进程debugjni
就可以定位到关键的进程,双击该进程
为了确保调试器附加成功后libdebugjniso.so
已经被加载到内存中,此时可以在程序中点击一次“设置标题”按钮来让系统加载它。选择com.droider.debuginiso
进程,点击 OK 按钮后稍等片刻 IDAPro 会进入调试器界面,但此时的代码不是运行在动态链接库的领空,要想调试动态链接库还得为动态链接库中的函数设置断点。将debugjniso.apk程序中的libdebugjniso.so 文件解压到本地磁盘,开启另一个 IDA Pro 实例并载入它,找到 jniString() 方法的代码如下:
从上面的反汇编代码中可以看出,jniString() 方法的代码起始处位于0xC38,回到 ida 调试窗口,按下快捷键CTRL+S打开段选择对话框,查找libdebugjniso.so动态链接库的基地址
根据内存地址=基地址+偏移地址
的计算方法,可以得出jniString()
方法的内存地址为0b5dcc38。点击界面上OK或Cancel按钮关闭段选择对话框,然后按下快捷键G,打开地址跳转对话框,在“JumpAddress”一栏中输入0x0b5dcc38
在0x0b5dcc38
下按f2下断点
设置好后,点击查询的设置标题按钮
就可以开始调试了
ps:留个坑,这里不知道为啥点击按钮后断点无法断成功,提示“could not set temporary breakpoint: did not continue execution”
F7进入函数,F8单步调试,F9跳到下一个断点,F2下断点,G调到函数地址