首页
论坛
课程
招聘
[原创]Android APP漏洞之战(10)——调试与反调试详解
2022-4-24 16:58 9830

[原创]Android APP漏洞之战(10)——调试与反调试详解

2022-4-24 16:58
9830

Android APP漏洞之战(10)——调试与反调试技巧详解

目录

一、前言

撰写了好长时间,终于写完这篇帖子了,这主要是为了解决当下APP中存在的一些常见调试检测策略,前面系列的文章介绍了很多Android APP中漏洞挖掘的手段和原理,但是面对当下防护手段如此复杂的APP,不掌握一些基本的逆向技巧,我们就更别谈进行APP漏洞挖掘了,本文将开始总结了当下APP的一些安全防护手段和技巧,帮助大家更加高效的进行漏洞挖掘。

 

本文通过收集了大量的资料,参考了看雪上众多大佬的帖子,肉丝大佬的知识星球等,本文的知识结构为:

 

本文第二节主要将检测防护手段的原理

 

本文第三节主要介绍当下APP中的动静态防护策略

 

本文第四节主要讲当下其他常见的反调试策略绕过方式

 

本文第五节将反调试技巧与案例结合,并列举了APP漏洞挖掘实例

二、相关介绍

1.模拟器检测

模拟器是当时比较流行的工具,可以帮助工作人员更加便捷的进行调试工作。而随着APP安全防护技术的进一步发展,模拟器检测技术不断进行完善,使得很多APP不能在模拟器上运行,下面本文收集了当下模拟器检测技术的情况,并在后面拿案例进行讲解

 

模拟器检测可以参考:

 

看雪sossai大佬的文章:Android模拟器检测体系梳理

 

看雪大佬Vancir大佬的文章:检测Android虚拟机的方法和代码实现

 

 

后面我们将拿一个具体案例来看看android模拟器检测如何具体实现

2.Android动静态调试方法

(1)静态分析

Android上一般使用GDA+jadx-gui+AndroidKiller对APP java层进行静态分析,一般使用ida对so层进行静态分析

 

java层静态分析:

 

我们拿到一个APP,一般先使用GDA查看是够有加壳,如果有加壳,我们则需要对其进行脱壳

 

image-20220424113633701

 

针对于加壳的类型一般分为dex加固和so层加固,我们大多时候只需要解决dex加壳问题,就可以满足我们的一般需求了

 

dex加壳一般分为三类:dex整体加壳、函数抽取、dex2c/VMP

 

dex脱壳解决方案:

1
2
3
dex整体加固:这种方法往往通过动态加载的形式,交换Application的执行,一般我们可以通过hook方法,找到dex_file的起始地址或大小,进行脱取,也可以通过定制Room方法对关键的函数进行插桩,代表有fdex2、Frida_Dump
函数抽取:这种方法往往通过将函数代码抽取放入so文件中,执行时再从so文件读取还原,我们一般可以通过被动调用延时Dump的方法,或主动调用ArtMethod中invoke函数,触发每一个函数,然后进行回填,代表有youpk和fart
VMP:通过定制的指令集进行解释,这时往往需要手工分析,找到指令的映射表,然后进行一步步解释

我们脱壳后,就进入静态分析的流程

 

首先我们需要找到函数的入口点函数:image-20220424171201780

 

我们在AndroidManifest里面找打Main的Activity,或通过AndroidKiller直接找到

 

image-20220424171232376

 

然后我们进入对应的入口函数,结合Activity的生命周期,执行流程一步步的分析源码

 

我们进行静态分析时,可以进行字符串定位来快速定位到我们需要定位的代码段

 

使用GDA+jadx-gui可以很好的查看静态代码段,使用AndroidKiller可以对代码进行修改,然后重新打包签名,当然后面需要面临的就是进一步的绕过签名机制

 

so层静态分析:

 

分析java层代码时,会可能碰到native函数,这样我们的分析就自然从java层过渡到了so层

 

首先我们确定jni函数对应的so文件

 

静态注册和动态注册的区别:

1
2
Java_完整包名_类名_方法名:静态注册
JNI_Onload:             动态注册

动态注册加载so文件的两种形式:

1
2
3
动态注册加载so文件两种方式:
    (1)System.loadLibrary("native-lib");
    (2)System.load(so文件绝对路径)

所以我们可以直接搜JNI_Onload来判断是否为动态注册

 

动态注册:

 

image-20220424132743389

 

静态注册:

 

image-20220424132820819

 

然后我们可以进一步进行分析so层中的代码段,下面是一些ida的快捷指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1)  空格键:切换文本视图与图表视图
(2)  ESC:返回上一个操作地址
(3)  G:搜索地址和符号
(4)  N:对符号进行重命名
(5)  冒号键:常规注释
(6)  分号键:可重复注释
(7)  Alt+M:添加标签
(8)  Ctrl+M:查看标签
(9)  Ctrl+S:查看节的信息
(10)  X:查看交叉应用
(11)  F5:查看伪代码
(12)  Alt+T:搜索文本
(13)  Alt+B:搜索十六进制
(14)  代码数据切换
    C-->代码/D-->数据/A-->ascii字符串/U-->解析成未定义的内容
(15)  拷贝伪C代码到反汇编窗口:右键>copy to -assembly   
(16) IDA可以修改so的hex操作数来修改so文件,右键点击“edit”进行修改,
       然后右键点击“edit-patchrogram”提交

(2)静态分析的防护策略

1
2
java层:dex加壳技术、混淆技术
so层:so加壳技术、ollvm高级混淆技术

(3)动态分析

Android中动态分析,java层我们一般使用Android StudioJeb两类工具进行动态调试,so层我们会使用IDAGDB来完成动态调试,后面我们会拿一些实质的案例来进行一步步的操作

<1>AndroidStudio动态调试

首先使用AndroidKiller对apk进行反编译,查看其反编译后的工程

 

image-20220424171303034

 

image-20220424152342814

 

把project文件导入Android stdio

 

image-20220424152413762

 

问题:导入报错,显示没有setting.zip,可能是Android stdio版本的问题,这里使用的是4.0版本

 

安装插件smalidea:

 

image-20220424152413762

 

配置Android stdio

 

1)给smail代码一个root权限

 

image-20220424152707730

 

2)配置项目的jdk, 已经配置后可以不用

 

3)配置远程调试

 

image-20220424152707730

 

添加远程调试remote

 

image-20220424152821854

 

4)建立连接

1
adb shell ps  显示当前的注册信息(adb shell ps | find)| grep

image-20220424152859681

 

查找到当前的进程PID

 

image-20220424152859681

 

开始转发端口

1
adb forward tcp:8700 jdwp:3924

或者将程序挂起:

1
adb shell am start -D -n My.XuanAo.LiuYao/.main(包名加进程)

点击调试:

 

image-20220424153053943

 

5)开始调试

 

下断点

 

image-20220424153053943

1
注意:Android stdio不能下断点,这是由于Android stdio 版本过高引起的

在虚拟机中运行APP,触发断点,既可以进行调试

 

image-20220424153053943

<2>IDA动态调试步骤
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
1.创建模拟器(最好使用真机)
2.在IDA里面找到android_server(dbgsrv目录)
3.把android_server文件放到手机/data/local/tmp
    adb push 文件名 /data/local/tmp
4.打开一个cmd窗口:运行android_server
    1)adb shell 连接手机
    2)给一个最高权限:su
    3)来到/data/local/tmp:cd /data/local/tmp
    4)给androi_server一个最高的权限:chmod 777 android_server
    5)查看android_server是否拥有权限:ls -l
    6)运行andorid_server: ./android_server(端口号默认是:23946
    补充:运行andorid_server并且修改端口号:./android_server -p端口号
5.端口转发:
    adb forward tcp:端口号 tcp:端口号(之前转发的端口号是什么,这里就是什么)
6.打开DDMS:观察程序的端口号
7.挂起程序:
    adb shell am start -D -n 包名/类名
    例子:adb shell am start -D -n com.example.javandk1/.MainActivity
    补充:此时观察DDMS,被调试的程序前面有一个红色的虫子;
8.IDA里面勾选三项
    1)打开ida,选择debugger -第二项-Remote ARMlinux(第四项)
    2)添加hostname和portt:
        hostname:主机号(默认127.0.0.1
        port:端口号(之前android_server运行时的端口号或者端口转发的端口号)
    3)出来进程列表:选择要调试的程序(可以ctrl+f,搜索包名)
    4)进来后,勾选三项:
    Suspend on process entry point程序入口点 断下
    Suspend on thread start/exit线程的退出或启动 断下
    Suspend on library load/unload库的加载和卸载 断下
补充:可以直接在这里F9(左上角有一个三角形)运行程序,然后放手;
     也可以,直接执行第九步,然后IDA运行程序
9.挂载、释放(放手)
    jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=端口号(是ddms里显示的端口8600)
    补充:此时观察DDMS,被调试的程序前面有一个绿色的虫子;
<3>GDB动态调试

GDB动态调试推荐使用可视化工具HyperPwn,GDB的命令操作手册如下:

 

GDB命令操作手册

 

首先,将gdbserver进行启动

 

image-20220424161910689

 

先使用objection来观察要调试的so文件

 

image-20220424162051255

 

例如,我们要调试libcamera_client.so

 

image-20220424162051255

 

我们此时查看目标进程的状态:

 

image-20220424162051255

 

此时我们就可以使用gdb去调试端口

1
./gdbserver 0.0.0.0:23946 --attach 11021

image-20220424162051255

 

我们再打开hyper

 

image-20220424162051255

1
gdb-multiach

image-20220424162051255

 

然后我们设置

1
2
set arch arm
set arm fallback-mode thumb

image-20220424162051255

 

然后我们远程连接

1
target remote 172.31.99.61:23946

ip是我们手机的ip地址,端口号是我们gdbserver转发的端口号,这里我们要注意我们的gdbserver不能关闭

 

image-20220424162051255

 

这样就进入了我们的调试界面

1
2
3
4
5
6
F8下一跳
F7步入
C 进入下一个断点
ctrl+shift+pageup 显示上一行状态
ctrl+shift+pagedown 显示下一行状态
如果没有调用,我们可以使用frida主动调用

(4)动态分析的防护策略

1
2
3
4
java层:
    android.debuggable=false
so层:
    通过检测ptrace值的变化

3.Root检测

root检测逐渐成为现在的APP防护的一种方式,而我们要进行hook等更多操作,必须要获得root权限

 

root检测目前一般分为下面的一些方法:

 

image-20220424141415510

(1) 检测目录中是否含su

1
2
3
4
5
6
          String[] stringArray = new String[]{"/system/app/Superuser.apk","/sbin/su","/system/bin/su","/system/xbin/su","/data/local/xbin/su","/data/local/bin/su","/system/sd/xbin/su","/system/bin/failsafe/su","/data/local/su","/su/bin/su"};
          //遍历数组路径
          //执行su
           String[] stringArray1 = new String[]{"/system/xbin/which","su"};
           process = Runtime.getRuntime().exec(stringArray1);
//遍历到说明含root,遍历不到说明为含root

(2)检测系统是否为测试版

编译Android源码系统时,如果我们编译生成的是测试版,是自动拥有root权限的,这时我们将su修改名字就可以绕过root检测了

1
cat /system/build.prop | grep ro.build.tags

image-20220424140832763

 

这返回结果“test-keys”,代表此系统是测试版

 

返回结果“release-keys”,代表此系统是正式版

(3)使用which命令查看是否有su

1
which su

image-20220424141214287

(4)检测Magisk或Superuser.apk

1
2
3
4
5
6
7
File file = new File("/system/app/Superuser.apk");
    if (file.exists())
    {
        Log.i(LOG_TAG, "/system/app/Superuser.apk exist");
        return true;
    }
//检测Magisk 是否安装包名为 com.topjohnwu.magisk

(5)执行Busybox

设备被root,很有可能被安装busybox

1
which busybox

image-20220424142340018

 

执行busybox:

1
2
3
4
5
6
7
8
9
10
String[] strCmd = new String[] {"busybox","df"};
    ArrayList<String> execResult = executeCommand(strCmd);
    if (execResult != null){
        Log.i(LOG_TAG,"成功");
        return true;
    }
    else{
        Log.i(LOG_TAG,"失败");
        return false;
    }

(6)访问私有目录

Android系统中私有目录必须要获取root权限才能进行访问,例如 /data、/system、/etc 等,可以通过读写测试来判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
Boolean writeFlag = writeFile("/data/su_test", fileContent);
            if (writeFlag) {
                Log.i(LOG_TAG, "write ok");
            } else {
                Log.i(LOG_TAG, "write failed");
            }
String strRead = readFile("/data/su_test");
            Log.i(LOG_TAG, "strRead=" + strRead);
            if (fileContent.equals(strRead)) {
                return true;
            } else {
                return false;
            }

(7)读取build.prop中的关键属性

1
2
getprop ro.build.type
getprop ro.build.tags

image-20220424143048945

 

我们可以看出这是测试版,很大可能会具有root权限

(8)检测市面上主流的模拟器

一般市面上的模拟器都带有root权限,比如我们可以使用上文的模拟器检测来进一步检测root,比如夜神模拟器nox

(9)检测hook框架特征

无论Xposed还是frida都需要root,我们也可以通过检测hook框架来判断是否进行root

4.hook检测

(1)Xposed检测

Xposed是一个动态插桩的hook框架,通过替换app_process原始进程,将java函数注册为native函数,从而获得更早的运行时机,Xposed检测详细可以参考我之前的文章Xposed定制

 

image-20220424143048945

 

图中的特征修改点就是我们可以进行检测的地方

(2)frida检测

frida和Xposed原理差不多,同样是一个动态插桩工具,详细的源码分析可以参考文章:frida源码分析

 

frida检测的途径很多,这里只简单介绍一些

 

端口和frida_server检测:

 

最简单的一种检测方式,厂商通过检测端口是否为固定的27047,还可以检测运行的frida_server名称

 

so层系统API检测:

1
2
3
遍历连接手机所有端口发送D-bus消息,如果返回"REJECT"这个特征则认为存在frida-server
直接调用openat的syscall的检测在text节表中搜索frida-gadget*.so / frida-agent*.so字符串,避免了hook libc来anti-anti的方法
内存中存在frida rpc字符串,认为有frida-server

so层非系统API检测:

 

遍历连接手机所有端口发送D-bus消息,如果返回"REJECT"这个特征则认为存在frida-server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
 * Mini-portscan to detect frida-server on any local port.
 */
for(i = 0 ; i <= 65535 ; i++) {
    sock = socket(AF_INET , SOCK_STREAM , 0);
    sa.sin_port = htons(i);
    if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
        __android_log_print(ANDROID_LOG_VERBOSE, APPNAME,  "FRIDA DETECTION [1]: Open Port: %d", i);
        memset(res, 0 , 7);
        // send a D-Bus AUTH message. Expected answer is “REJECT"
        send(sock, "\x00", 1, NULL);
        send(sock, "AUTH\r\n", 6, NULL);
        usleep(100);
        if (ret = recv(sock, res, 6, MSG_DONTWAIT) != -1) {
            if (strcmp(res, "REJECT") == 0) {
               /* Frida server detected. Do something… */
            }
        }
    }
    close(sock);
}

检测内存库来检测:

 

Frida 的各个模式都是用来注入的,我们可以利用的点就是 frida 运行时映射到内存的库。最直接的是挨个检查加载的库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char line[512];
FILE* fp;
fp = fopen("/proc/self/maps", "r");
if (fp) {
    while (fgets(line, 512, fp)) {
        if (strstr(line, "frida")) {
            /* Evil library is loaded. Do something… */
        }
    }
    fclose(fp);
    } else {
       /* Error opening /proc/self/maps. If this happens, something is off. */
    }
}

inlinehook检测frida:

 

frida实现hook一定实现了inlinehook技术,所以我们还可以通过inlinehook库来检测

 

参考文章:从inlinehook角度检测frida

三、动静态分析实例分析

1.静态分析与脱壳

实验案例:client.apk,漏洞银行.apk

(1)脱壳

我们打开client.apk,发现使用360加壳

 

image-20220424150652346

 

然后我们使用葫芦娃大佬Frida_Dexdump脱壳工具进行脱壳,项目代码:https://github.com/hluwa/frida-dexdump

1
2
(1)启动frida_server
(2)直接frida-dexdump -U -d -f 包名 -o 路径名

image-20220424150652346

 

image-20220424150652346

 

image-20220424151332577

(2)静态分析

我们脱壳完成后就进入正常的静态分析流程

 

首先,从入口点出发:

 

image-20220424151332577

 

image-20220424151332577

 

我们可以看见入口类最后一个函数是启动一个延时器,3秒后延时实例化SplachScreen$a()类,然后我们进入此类

 

image-20220424151332577

 

我们可以发现该类实现Runnable接口,启动一个线程,然后里面完成从当前的Activity跳转到MainActivity

 

进入MainAcitivity类:

 

image-20220424151332577

 

image-20220424151332577

 

我们简单分析一下代码逻辑,可以发现该APP实现了一些常见的反调试手段:

1
2
3
4
1)模拟器检测
2)动态调试检测
3)Root检测
4)Frida检测

2.动态调试过反调试

(1)java层

案例:wifiKiller.apk

 

这里我们使用AndroidStudio进行动态调试,发现没有debuggable的值,就默认ro.debuggable = false防止反调:

 

image-20220424151332577

 

Android studio在动态调试的时候出现错误:

 

image-20220424151332577

(2)java层过反调

java层过反调方法:

1
2
3
mprop模块
xposed模块
定制Room

这里我们使用第一种方法

1
2
3
./mprop ro.debuggable 1
getprop ro.debuggable
start;stop

image-20220424151332577

 

image-20220424151332577

 

我们发现ddms可以查看进程说明已经过java层反调试了

(3)so层

<1>IDA普通调试

样例:AliCrakme.apk

 

我们进行静态分析,分析到了native函数,就需要对so层进行分析了

 

image-20220424151332577

 

我们继续上面的案例,首先运行android_server (这里这样是防止android_server反调试)

 

image-20220424151332577

 

然后进行端口转发

1
adb forward tcp:39026 tcp:39026

然后附加程序

 

image-20220424151332577

 

image-20220424151332577

 

在module中查找对应的so文件

 

image-20220424151332577

 

image-20220424151332577

 

直接进入securityCheck函数

 

我们在函数头f2下一断点,开始动态调试

 

image-20220424151332577

 

image-20220424151332577

 

以上是可以直接将so文件,附加进来,但是有时IDA版本或者手机系统版本原因,我们不能在moudle模块中找到

 

解决办法:

1
绝对地址 = 相对地址(native函数)+ 基地址(so文件)

首先,我们Ctrl+s找到so文件对应的基地址

 

image-20220424151332577

 

其次我们相对地址就是我们静态调试时的函数地址

 

image-20220424151332577

 

相对地址=11AB+B3AFA000=B3AF A1AB 我们可以发现这和我们附加进来的地址一致

 

image-20220424151332577

 

这说明程序一定含有反调试的策略,我们接下来应该解决程序的反调试策略

(4)so层过反调

我们可以查看TrancePId值来验证(我们需要重新执行以上步骤动态调试,不过不运行)

 

首先,我们查看程序进程状态

1
ps |grep tong

image-20220424151332577

 

其次我们,根据找到的PID号,查看对应状态:

1
cat /proc/25196/status

image-20220424151332577

 

你发现这里的值不为0,你可以验证没有进行IDA调试时候的值,经过以上分析我们可以确定该APK采用了反调试策略

<1>IDA挂起动态调试

首先完成基础配置:

1
2
3
4
a.运行android_server
b.转发端口
c.打开ddms,查看进程状态
d.挂起程序 adb shell am start -D -n 包名/.MainActivity

image-20220424151332577

 

image-20220424151332577

 

你要获得后面的程序,可以打开程序后,输入adb shell dumpsys activity top 则可以查看

 

我们发现ddms上的进程前面出现红色小虫子,说明程序被挂起,而手机端也会显示挂起的界面

 

然后进行附加进程,挂载三项

 

image-20220424151332577

 

然后按F9运行,我们发现此时程序退出,这是因为我们此时是挂起调试,程序还没有初始化

 

我们运行:

1
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8609

image-20220424151332577

 

IDA附加so文件,我们点击取消就可以加载进来(要是没显示就按照上文地址的计算方法)

 

我们此时进入libCramke.so文件,并进入JNI_OnLoad方法(我们进入这里主要是为了解决反调试)

 

image-20220424151332577

 

我们按F9运行,并按F8进行单步步入

 

下面使用F8开始单步调试了,发现每次到达BLX R7这条指令执行完之后,JNI_OnLoad函数就退出了,这个地方存在问题,可能就是反调试的地方了。我们再次进入调试,看见BLX跳转的地方R7寄存器中是 pthread_create 函数

 

image-20220424151332577

 

到了这里我们就找到了出问题的地方,接下来我们只需要修改对应的位置就可以了。

 

可以把 BLX R7 这条指令给nop掉,也就是把这条指令变成空指令(相当于删除这条指令)这样apk就不会新建线程去执行检测代码了。

 

我们在直接静态分析so的IDA中找到 BLX R7的位置

 

image-20220424151332577

 

我们把这条指令给nop掉,我们打开其对应16进制的窗口,并把值改为0

 

image-20220424151332577

 

我们保存修改后的so文件

 

image-20220424151332577

 

我们保存,再AndroidKiller里面进行回编译,则得到绕过反调试的apk

四、其他防护过反调试

我们上面静态分析了漏洞银行的案例,我们发现其采用了模拟器检测、root检测、frida检测,下面我们依次进行反调试绕过

1.过模拟器检测

首先我们定位到该处的代码段

 

image-20220418112117049

 

经过分析我们可以分析关键变量:

1
str2----->d.a----->i3

即决定模拟器检测的变量是i3

 

image-20220418112558229

 

我们从下至上逐一分析代码逻辑:

 

image-20220418112917345

1
这里检测windows下bluestacks模拟器的文件夹是否存在,存在i3值即增加

image-20220418113041416

1
这里使用GLES20着色器来检测Bluestacks模拟器和Translator模拟器

image-20220418124110887

1
上面部分代码就是从sdk等方面检测各个模拟器,我们可以看见我们这里的夜神模拟器nox

我们分析了模拟器检测的代码,但是这里我们发现这部分代码并没有直接导致程序崩溃的代码段

 

我们继续分析,发现

 

image-20220418124626125

 

只有当我们进行root、frida等情况,才会将APP关闭,而此时我们还没有使用frida,这就是模拟器root的原因导致的

 

这里首先我们关闭模拟器的root设置,进行重启,按照判断,应该是可以正常安装

 

image-20220418125212962

 

再次安装,发现奇怪还是一样安装失败

 

image-20220418103815525

 

此时我们进一步分析,按理说就算检测到模拟器,检测root也应该是可以安装成功,只是打开时候结束,这里说明还有其他地方有防护

 

经过分析,我们在AndroidManifest.xml中找到一个关键属性hardwareAccelerated

 

image-20220418125732581

 

hardwareAccelerated=ture意味着APP计划利用移动设备中GPU资源来使其运行,但是很显然我们的模拟器是没有GPU的,所以就会导致直接崩溃,安装不上去

 

这里我们简单将hardwareAccelerated值改为false,然后重编译

 

image-20220418125954986

 

然后我们再次安装

 

image-20220418130031095

 

image-20220418130047867

 

程序成功安装,并可以打开,这里我们启动root可以进一步验证一下刚才的分析:

 

image-20220418130214218

 

果然程序就打开后,直接闪退,并报错检测到root,这和我们上面的分析一致,也证明这里我们解决了模拟器检测绕过

2过root检测

上面虽然我们关闭root可以解决模拟器检测绕过问题,但是很多时候没有root,我们就很受限了,这里我们尝试进一步绕过root检测

 

image-20220418130605478

 

这里我们可以发现只需要将a.R()的返回值修改就可以了,我们可以采用hook手段,如Xposed或frida

 

我们进一步分析,root检测实现:

 

image-20220418131313998

1
2
3
4
我们可以发现检测root的原理很简单:
1)列举所有su存在的集合
2)遍历集合,执行su,看是否成功
3)成功返回ture

绕过此处root方式很多,比如该程序没有签名校验机制,最简单直接修改代码,反编译,这里我们为了学习,通过hook技术,比如Xposed可以绕过,但是经过前面分析,该APP有frida检测,那我们就使用frida来进行测试

3.过frida检测

我们随便创建一个js脚本,然后启动frida

 

image-20220418133314857

 

image-20220418133401493

 

发现程序检测到Frida在运行,然后直接崩溃,我们进一步分析Frida检测的代码

 

image-20220418133529649

 

可以发现主要是fridaCheck()这个函数

 

image-20220418133622096

 

进一步分析,可以发现该方法是native方法,因此我们进一步进入so层分析,我们打开frida-checkso文件

 

image-20220418133837056

 

image-20220418134004354

 

没有JNI_Onload程序是静态注册

 

image-20220418135758838

 

image-20220418140558354

 

image-20220418141203374

 

代码逻辑很简单:

1
2
3
4
5
6
7
1)函数的返回值为result
2)v2值给result
3)connect函数是否>=0决定最后程序的返回值为true还是false
4)addr值可以发现就是结构体的首地址,即为0xA2690002
5)我们需要考虑二进制文件中的大小端序的问题,需要将值进一步的转变,将8位分为两半0xA2690x0002,然后变为十进制,前面变为0x69A2,即27042
6)所以我们可以发现connect使用该值就是其端口号27042
总结,APP检测Frida是否运行在27042的套接字上,如果允许在上面就返回true,从而可以判断程序在使用frida,然后关闭程序

这里我们分析结束,很简单只需要不将frida_server运行在27042端口上即可

 

image-20220418141845557

 

再次启动frida看是否生效

 

image-20220418142257172

 

image-20220418142344312

 

此时我们发现程序frida并未检测到,这说明我们上面的分析是正确的

 

接下来只需要编写hook代码,将root检测绕过即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test() {
    Java.perform(function () {
        var class_obj = Java.use("a.a.a.a.a");
        class_obj.R.implementation = function () {
            var result = this.R();
            console.log("result:"+result);
            return false;
        }
    })
}
 
function main(params) {
    test()
}
 
setImmediate(main)

image-20220418143707200

 

image-20220418143751714

 

这里我们就发现成功的hook程序,进入了正常的界面

4.过Xposed检测

过Xposed检测详细可以参考文章:Xposed定制

五、过反调试的APP漏洞挖掘

案例:漏洞银行.apk

 

前面我们采用了一些方法进行了反调试绕过,这里我们拿漏洞银行的例子,去联合我们前面的漏洞挖掘技巧和反调试技巧,展现如何过反调试后挖掘Android里面的一些漏洞

1.加密传输漏洞

经过我们上篇帖子,讲述了如何解决抓包中的安全防护绕过问题,参考Android APP漏洞之战(9)——验证码漏洞挖掘详解,当我们绕过这些常见的抓包防护后,最后就是hook和算法还原,我们前面也绕过了hook检测

 

我们配置好抓包环境:

 

image-20220418160104970

 

我们使用burpsuit进行抓包:

 

image-20220418160147202

 

我们发现这里是加密的字段,我们可以全文搜索enc_data

 

image-20220418160330922

 

image-20220418160416195

 

image-20220418160603102

(1)hook方法

我们就可以定位到e类中的两个函数段,这样我们只需要对这两处函数进行hook,将参数打印出来就是我们传输的字符串了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hookentry() {
    Java.perform(function () {
        var class_obj = Java.use("c.b.a.e");
        class_obj.a.implementation = function (arg0) {
            var result = this.a(arg0);
            console.log("arg0_a:"+arg0);
            console.log("result_a:"+result)
            return result;
        }
        class_obj.b.implementation = function (arg0) {
            var result = this.b(arg0);
            console.log("arg0_b:"+arg0);
            console.log("result_b:"+result)
            return result;
        }
    })
}

image-20220418161219954

 

我们进行注册:

 

image-20220418161319462

 

先看抓包情况:

 

image-20220418161407839

 

再看脚本hook值:

 

image-20220418161440033

 

这样我们就成功的将加密前的数据和加密后的数据打印出来了

 

我们通过动态调试验证一样

 

image-20220418195256625

(2)算法还原方法

上面我们利用hook的方法成功的获得解密后的数据,一般在做逆向开发过程时,还可以通过编写解密函数来实现解密

 

首先我们要分析加密逻辑

 

image-20220418162723283

 

我们可以发现这里就是先通过c函数进行加密,然后使用base64进行加密

 

编写解密函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const SECRET = 'amazing';
const SECRET_LENGTH = SECRET.length;
 
const operate = (input) => {
  let result = "";
  for (let i in input) {
    result += String.fromCharCode(input.charCodeAt(i)^SECRET.charCodeAt(i%SECRET_LENGTH));
  }
  return result;
}
 
const decrypt = (encodedInput) => { 
  let input = Buffer.from(encodedInput, 'base64').toString();
  let dec = operate(input);
  console.log(dec);
  return dec;
}
 
decrypt("Gk8UCQwcCQAABFhTTBQHHhIJS0JFEQwSCR4BFQVPW1gaCAFDEA==")

image-20220418165727819

 

这样也可以成功的解密

2.敏感信息披露漏洞

我们在前面的文章中也讲述了此类漏洞问题,主要是因为应用中采用了硬编码导致的

1
adb shell ps -ef | grep damn

image-20220418170232927

1
adb logcat | grep 8992

image-20220418184948147

 

这里我们可以发现,可以获取部分的敏感信息

六、实验总结

本文总结并一一分析了当下Android APP中常见的防护策略,并通过案例实操实现了反调试策略的过反调,学习并掌握本节可以更加进一步助力Android APP漏洞挖掘中的漏洞学习,最后我们将反调试技巧和漏洞挖掘实例进行结合,并实操了案例,本文后续会将实验案例逐一上传到知识星球中。

 

github首页:github

七、参考文献

1
2
3
4
5
6
7
8
9
https://bbs.pediy.com/thread-225717.htm
https://nszdhd1.github.io/2020/03/09/Magisk%E6%A3%80%E6%B5%8B/
https://mabin004.github.io/2018/07/31/Mac%E4%B8%8A%E7%BC%96%E8%AF%91Frida/
https://www.jianshu.com/p/f679cb404524
https://bbs.pediy.com/thread-269862.htm#msg_header_h3_4
https://bbs.pediy.com/thread-270269.htm
https://juejin.cn/post/6844903733248131079#heading-5
https://bbs.pediy.com/thread-268586.htm
https://blog.51cto.com/u_15308480/3140020

【公告】 讲师招募 | 全新“预付费”模式,不想来试试吗?

最后于 2022-4-27 15:27 被随风而行aa编辑 ,原因:
收藏
点赞11
打赏
分享
最新回复 (16)
雪    币: 301
活跃值: 活跃值 (183)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hackerbob 活跃值 2022-4-24 23:11
2
0
虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-25 09:20
3
0
hackerbob 虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 810
活跃值: 活跃值 (1725)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
危楼高百尺 活跃值 1 2022-4-25 09:38
4
0
虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-25 15:13
5
0
危楼高百尺 虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 2106
活跃值: 活跃值 (1861)
能力值: ( LV8,RANK:131 )
在线值:
发帖
回帖
粉丝
coneco 活跃值 2 2022-4-26 10:09
6
0
感谢分享,非常详细!
雪    币: 187
活跃值: 活跃值 (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
南枫 活跃值 2022-4-26 12:08
7
0
虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-26 14:58
8
0
coneco 感谢分享,非常详细!
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-26 14:58
9
0
南枫 虽然我看不懂,但看起来还是很牛逼的样子
雪    币: 210
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
szukodf 活跃值 2022-4-27 09:59
10
0
太精华了,多来点
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-27 11:04
11
0
szukodf 太精华了,多来点
感谢支持
雪    币: 6626
活跃值: 活跃值 (564)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
PPTV 活跃值 2022-4-27 11:54
12
0
收藏慢慢看
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-27 15:14
13
0
PPTV 收藏慢慢看
可以可以
雪    币: 186
活跃值: 活跃值 (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Andy_877594 活跃值 2022-4-27 15:17
14
0
getprop ro.build.tags,是test-keys,不一定是root了吧?
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-27 15:25
15
0
Andy_877594 getprop ro.build.tags,是test-keys,不一定是root了吧?
这里是的 厂家的发布的测试系统版本不一定是获取root,只能说存在很大可能是root,感谢修正
雪    币: 0
活跃值: 活跃值 (503)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_阿达西 活跃值 2022-4-28 19:03
16
0
@作者,大兄弟,截图中有个JEB调试,请教下用的JEB什么版本,我之前尝试用JEB调试过APP,观察变量值那个窗口,字符串的值显示不了, 我看你这个是正常的。请教下你用的什么手机,什么系统版本,jeb什么版本
雪    币: 11152
活跃值: 活跃值 (10225)
能力值: ( LV12,RANK:410 )
在线值:
发帖
回帖
粉丝
随风而行aa 活跃值 8 2022-4-28 20:22
17
0
wx_阿达西 @作者,大兄弟,截图中有个JEB调试,请教下用的JEB什么版本,我之前尝试用JEB调试过APP,观察变量值那个窗口,字符串的值显示不了[em_5], 我看你这个是正常的。请教下你用的什么手机,什么系统 ...
我好像使用的是3.0版本哦,一般用的就是google系列 nexus或pixel 字符串值显示不了 是不是没有修改类型呢
游客
登录 | 注册 方可回帖
返回