android 动态调试

工具

JEB

IDA Pro7 for mac

root 使用Android Studio原生模拟器

JEB动态调试

添加debug权限 android:debuggable="true"

1
2
3
4
5
6
7
<application
        android:theme="@7F1101D3"
        android:label="@7F10001B"
        android:icon="@7F0D000F"
        android:debuggable="true"  #可调试
        android:allowBackup="true"
        android:supportsRtl="true"

手机开启开发者模式,允许USB调试,使用adb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 列出模拟器
adb devices

# 连接模拟器(夜神)
~ adb connect 127.0.0.1:62001
connected to 127.0.0.1:62001


# attach app
adb shell am start -D -n 包名/类名
-n 表示启动一个activity
-D 表示将应用设置为可调试模式


~ adb shell am start -D -n com.zj.wuaipojie/.ui.MainActivity
Starting: Intent { cmp=com.zj.wuaipojie/.ui.MainActivity }

运行JEB

1
sudo sh jeb_macos.sh

搜索定位,文字或者控件ID,也可以直接再bytecode 环绕搜索(JEB已经把apk转换成一份smali)

image-20221126213343043

定位到对应smali代码,转换成Java代码方便查看,Java那边也可以通过解析反找smail,cmd+B 下断点,双击可以调到函数声明,交叉引用(x)可以引用函数的地方。

image-20221126214208164

debug

image-20221126214943898

会弹出

image-20221126215035514

使用这些代码跳转,和debug调试

image-20221126215247175

数据类型不一定对需要修改类型看到正确的值

image-20221126215726618

frida

监控和修改APP行为,适合临时调试的场景

安装服务端

检查CPU版本

1
2
3
4
5
6
7
8
9
# 连接模拟器(夜神)
~ adb connect 127.0.0.1:62001
connected to 127.0.0.1:62001

➜  ~ adb shell
dream2lte:/ getprop ro.product.cpu.abi
x86


https://github.com/frida/frida/releases 最新的下载,试着装一下看看是否适配,frida-server开启后,模拟器是否正常,打开点击app之类。可以查看到Frida server与Frida-tools对应关系。如果最新可以用,pip就不指定版本

根据abi和服务器类型,选择下载frida-server-15.2.2-android-x86.xz

image-20221127113951107

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 重命名发送到模拟器
adb push frida-server /data/local/tmp



# 模拟器操作
adb shell

su
cd /data/local/tmp
dream2lte:/data/local/tmp  chmod 755 frida-server
dream2lte:/data/local/tmp  ll
total 52348
-rwxr-xr-x 1 root root 53604108 2022-11-26 22:15 frida-server


安装客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pip install frida-tools==11.0.0

frida --version
15.2.2

# 测试展示已安装app
 -U, --usb             connect to USB device
  -a, --applications    list only applications
  -i, --installed       include all installed applications



frida-ps -Uai

 PID  Name       Identifier                        
4  ---------  ----------------------------------
3211  图库         com.android.gallery3d             
3899  游戏中心       com.bignox.app.store.hd           
3184  相机         com.android.camera2               
2573  设置         com.android.settings              
   -  Amaze      com.amaze.filemanager             

helloworld

1
2
3
4
5
6
7
8
9
10
11
12
13
# attach app
  -U, --usb             connect to USB device
 -F, --attach-frontmost
                        attach to frontmost application
  -l SCRIPT, --load SCRIPT
                        load SCRIPT

使用js脚本hook当前app,按保存自动reload脚本

frida -UF -l [js path]



image-20221127115915381

在AndroidMainfest.xml里面查看想控制的activity。或者使用MT

image-20221127114919703image-20221127115045719image-20221127115227744

拿到activity name

  • hook相同方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
function main() {
    Java.perform(() => {
        // Java类路径
        // 静态内部类: 包名.类名$内部类名
        // 匿名内部类: 包名.类名$1  (按顺序排单个类文件smail)
        let activity = Java.use("包名.类名");
        activity.check.implementation = function (a,b) {
            return true
        }
        // 重载  frida会有错误提示可以直接选
         activity.check.overload("int","类全路径").implementation = function (a,b) {
           	// 类静态参数修改
          	activity.attr.value = "fdafafa"
	          send(activity.attr.value)
            return true
        }
        // 遍历重载
        for(var i = 0; i < messageDigest.update.overloads.length; i++){
              messageDigest.update.overloads[i].implementation = function(){
                // 参数
								if(arguments.length == 1){
                    send(arguments[0]);
                    // 调用函数返回值,apply省略参数个数
                    a = this.update.apply(this)
                  	return this.update.apply(this,arguments)
                  }
              }
         }
      
      
        // 构造函数
        activity.$init.implementation = function (a,b) {
            // 实例化对象
            var b = useObj.$new(xx,xx)
            send(b.attr.value)
            return this.$init()
        }
      
         // 非静态类字段修改
        Java.choose("com.zj.wuaipojie.ui.ChallengeFourth", {
                onMatch: function (obj) {
                    // 遍历对象修改
                    obj.attr.value = 1
                    // 方法名冲突,属性加下划线
                    obj._attr.value = 2
                },
                onComplete: function () {
                //    遍历完调用
                }
            })
              
    });
}

setImmediate(main)

签名

signapk 源码 com/android/signapk/sign.java

对APK签名后会有个META-INF里面有三个文件

  • MANIFEST.MF

    对Apk中的每个文件(除了这三个文件外)做一次算法(SHA-1数据摘要+Base64编码),保存到MANIFEST.MF文件中,Name是文件路径

    image-20221130142415137

  • CERT.SF

    对MANIFEST.MF整个文件做一次算法(数据摘要+Base64编码),然后放到SHA-1-DIgest-Manfest

    image-20221130142758594

    然后对MANIFEST.MF文件中每个条目做一次算法(数据摘要+Base64编码),在保存

  • CERT.RSA

    对CERT.SF文件做用私钥计算出签名,将签名和包含公钥内容存档到CERT.RSA中

如果apk内容被篡改,必然会引起这些文件的变动。因为安装APK时候会做签名校验,没有签名的私钥也不能仿造签名,所以必须重签名。

so

why

  • java处理音视频效率低,在数据运算和实时渲染游戏上面
  • 无法直接操作硬件
  • 无法调用本地C库,so可以,也有丰富的C/C++库支持
  • Java容易被反编译,加密代码用C/C++写

JNI(java native interface)让java和其他语言做交互。

语言的交互主要解决数据类型的转换,JNI就是规范。Java只和JNI交互,JNI和其他语言交互。

NDK里面有个头文件jni.h,定义了数据的映射关系,数据转换的函数和调用java的函数等。

java程序执行时候,jvm通过JNI来调用库文件里面的C代码。C是运行在linux进程中执行的,而java跑在jvm上面

JNI注册

  • 静态注册函数 ,在IDA Export可以搜索
    • 必须遵循注册规则
    • 名字过长
    • 第一次调用需要搜索,影响效率
1
2
3
4
# 前两个固定  JNIEnv jvm指针  jobject 调用该方法的类
Java_com_ndk_myapplication_MainActivity_stringFromJNI(JNIEnv* env,jobject obj,,, )

Java_包名_类名_方法名
  • 动态注册

    • 需要我们手动建立联系,增加了代码量但提高效率
    • 允许自己定义函数名字

    通过 RegisterNatives 方法手动完成 native 方法和 so 中的方法的绑定,这样虚拟机就可以通过这个函数映射表直接找到相应的方法了。

    JNI_OnLoad为入口,调用env->RegisterNatives(调用类,注册方法methods,methods个数)

    methods 是JNINativeMethod类型的

    1
    2
    3
    4
    5
    // Java层方法名,(方法参数)返回值 ,so对应函数指针(目标)
    static const JNINativeMethod nativeMethod[] = {
            {"text",        "(Ljava/lang/String;)I", (void *) native_text},
            {"static_text", "(Ljava/lang/String;)I", (void *) native_staic_text}
    };
    

    找到对应的so函数名字

IDA

linux 下的elf文件(.elf .so )

lib 通常对应设备abi(CPU架构类型)

  • arm64-v8a arm64
  • armeabi-v7a arm32
  • x86 32
  • x86_64 64

逆向分析时选择32-bit IDA分析32位程序,64-bit IDA 分析64位程序。

image-20221130221409435

view 介绍

Exports

优先看,供Java调用的函数表,crtl+F搜索

image-20221201095642780

IDA-VIEW

空格展开看汇编地址

image-20221201091626060

  • B 跳转指令
  • BL 带返回的跳转指令
  • BLX 带返回和状态切换的跳转指令
  • BX 带状态切换的跳转指令

Pseudocode

通常取地址参数就是返回值,F5抓换位伪C代码,参数个数或类型可能错误,逻辑靠谱。可以通过TAB切换去

image-20221201091950953

解决偏移

image-20221201092435407

parse jni.h

image-20221201092531790

遇到类似的问题,导入失败

image-20221211153639419

删掉对应行数,删除jni.h的两个导包#include <stdint.h>#include <stdarg.h>即可

实在不行,把第一个参数改成**_JNIEnv ** NewStringUTF Cstring 转成Jstring

image-20221201093344211

Hex view

  • 修改汇编
  • 查看一些明文

image-20221211204820439

左边是地址偏移值,内存字节,内存字符串

1
1 byte = 8bit

string view

查看so出现的字符串

image-20221201100003783

IDA静态

IDA 动态

找到dbgsrv目录,里面有android_server

免root执行andoid_server

通过Android Studio的Device File Explorer upload到对应的应用目录下,adb是没有权限push文件进去

image-20221201193138914

1
2
3
4
5
6
7
8
9
10
11
12
13
14
adb shell

# 关键一步 获取/data/data/package 目录下权限
run-as com.ndk.myapplication

cd /data/data/com.ndk.myapplication

chmod 755 android_server

./android_server                                                                                                   
IDA Android 32-bit remote debug server(ST) v1.22. Hex-Rays (c) 2004-2017
Listening on 0.0.0.0:23946...


IDA

先打开app,再attach

image-20221201193316110

image-20221201193422299

image-20221201195238248

image-20221201193700520

开始debug, 按F9让程序执行

image-20221201200109589

modules,先搜索so名字,再搜方法名

image-20221201201831586

image-20221201201755882

双击进入定位到函数汇编位置,F5转伪代码,与静态时候看到差别很大,IDA为了效率无法看清,双开IDA动静结合看伪代码,定位断点位置

image-20221201201949429

IDA view image-20221201202835833

断点触发PC值 就是当前地址, x1寄存器,

  • arm 32位R0~R3前四个寄存器放函数参数,R0还会放函数返回值,再多参数放到栈 ,SP是栈顶寄存器地址
  • arm 64位R0~R7前八位放函数参数,其他和32位一样。

image-20221201211308436

追踪寄存器的值

image-20221201212854427

可能遇到的问题

  • android_server 报错

    可能会遇到的问题

    1
    2
    3
    ./android_server       
      
    socket: Operation not permitted
    

    app 的androidmanfest.xml需要添加网络权限

    1
    <uses-permission android:name="android.permission.INTERNET"/>
    
  • android_server位数和设备不一致,IDA报错

    1
    2
    Create segment (12C00000-42C00000, sel 00000000):
    Illegal segment bitness.
    

    ida位数和server不一致,IDA报错

    1
    2
    Incompatible debugging server:
    address size is 8 bytes, expected 4
    
  • attach app 没开, IDA报错

    1
    Bogus or irresponsive remote server
    

思路

  • 抓包

    • 清除缓存,有可能重新安装,或者新建模拟器
    • 保留完整抓包,deviceid 之类
  • 搜索

    • 连接URL ,URL/发送参数,算法关键字
  • log输出,JEB 主要是获取值,R.id

  • hook xposed frida (不用担心修改apk内容,带壳hook,不需要还原相关算法,可能写在so混淆)

  • so黑盒可以单独直接调用so,但是so层有可能会调用Java层代码

  • 主动hook

    image-20221130163842507

    手机端相当于hook了,通过参数,调用手机端对应算法。(app挂,多手机端)