定位关键代码

代码注入法

通常,一个程序在发布时不会保留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

再次输入后就可以出现日志了

2025-01-14T08:50:22.png

栈跟踪法

栈跟踪法同样属于代码注入的范畴,它主要是手动向反汇编后的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

2025-01-15T03:17:00.png

设置完后点击OK,ida就会执行远程的debugnativeapp

调试android原生动态链接库

调试libdebugjni.sojnistring方法

启动调试服务

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)

附加成功后就可以看到进程列表了

2025-01-15T05:50:49.png

搜索进程debugjni就可以定位到关键的进程,双击该进程

为了确保调试器附加成功后libdebugjniso.so已经被加载到内存中,此时可以在程序中点击一次“设置标题”按钮来让系统加载它。选择com.droider.debuginiso进程,点击 OK 按钮后稍等片刻 IDAPro 会进入调试器界面,但此时的代码不是运行在动态链接库的领空,要想调试动态链接库还得为动态链接库中的函数设置断点。将debugjniso.apk程序中的libdebugjniso.so 文件解压到本地磁盘,开启另一个 IDA Pro 实例并载入它,找到 jniString() 方法的代码如下:

2025-01-15T05:55:54.png

从上面的反汇编代码中可以看出,jniString() 方法的代码起始处位于0xC38,回到 ida 调试窗口,按下快捷键CTRL+S打开段选择对话框,查找libdebugjniso.so动态链接库的基地址

2025-01-15T05:57:28.png

根据内存地址=基地址+偏移地址的计算方法,可以得出jniString()方法的内存地址为0b5dcc38。点击界面上OK或Cancel按钮关闭段选择对话框,然后按下快捷键G,打开地址跳转对话框,在“JumpAddress”一栏中输入0x0b5dcc38

2025-01-15T05:59:42.png

0x0b5dcc38下按f2下断点

设置好后,点击查询的设置标题按钮就可以开始调试了

ps:留个坑,这里不知道为啥点击按钮后断点无法断成功,提示“could not set temporary breakpoint: did not continue execution”

F7进入函数,F8单步调试,F9跳到下一个断点,F2下断点,G调到函数地址

发表评论