首页
社区
课程
招聘
[原创]《安卓逆向这档事》第二十二课、抓包学得好,牢饭吃得饱(下)
发表于: 2024-11-16 13:14 19734

[原创]《安卓逆向这档事》第二十二课、抓包学得好,牢饭吃得饱(下)

2024-11-16 13:14
19734

1.了解hook抓包与混淆对抗
2.了解底层网络自吐
3.了解ebpf抓包
4.简单实战加解密协议

1.教程Demo
2.r0capture&ecapture
3.Reqable
4.wireshark

Hook 抓包是一种截取应用程序数据包的方法,通过 Hook 应用或系统函数来获取数据流。在应用层 Hook 时,通过查找触发请求的函数来抓包,优点是不受防抓包手段影响,缺点是抓包数据不便于我们分析和筛选。
常见安卓网络开发框架

【译】OkHttp3 拦截器(Interceptor)
拦截器是 OkHttp 提供的对 Http 请求和响应进行统一处理的强大机制,它可以实现网络监听、请求以及响应重写、请求失败充实等功能。
OkHttp 中的 Interceptor 就是典型的责任链的实现,它可以设置任意数量的 Intercepter 来对网络请求及其响应做任何中间处理,比如设置缓存,Https证书认证,统一对请求加密/防篡改社会,打印log,过滤请求等等。
OkHttp 中的拦截器分为 Application Interceptor(应用拦截器) 和 NetWork Interceptor(网络拦截器)两种

参考项目:
OkHttpLogger-Frida
源码解析:
定位OkHttpClient关键点

拦截器加载关键点

使用操作:
1.将 okhttpfind.dex 拷贝到 /data/local/tmp/ 目录下(顺带设置一下777权限)
2.执行命令启动frida -U wuaipojie -l okhttp_poker.js 可追加 -o [output filepath]保存到文件
3.执行find()和hold()方法看看效果

问题:如果app不是用okhttp开发的呢?或者混淆定位不到?
[原创]android抓包学习的整理和归纳
r0capture开源地址



Hook实现

通过拦截 Java 中的 socketWrite0socketRead0 方法,在数据发送和接收时收集相关信息并发送给指定的接收方,以便进行监控或调试

Hook实现

拦截了 SSLOutputStreamSSLInputStream 类的 writeread 方法,在进行数据读写时获取当前的调用栈信息

拦截 sendtorecvfrom 函数,捕获发送和接收的数据包。onEnter 钩子函数用于在函数调用前处理参数,获取文件描述符和缓冲区地址,调用 hexdump 打印缓冲区内容以便查看实际发送或接收的数据

Hook实现

r0capture简介

wireshark下载地址

what-is-ebpf
eBPF是一个运行在 Linux 内核里面的虚拟机组件,它可以在无需改变内核代码或者加载内核模块的情况下,安全而又高效地拓展内核的功能。
图片

ecapture
官方案例
eCapture主要利用了eBPF和HOOK技术:

eCapture 的工作原理涉及到用户态和内核态。用户态就是运行应用程序的地方,比如各种 App。在这个区域中,eCapture 通过一个共享的模块(Shared Object)获取应用程序的网络数据。然后,它将这些数据传递给内核态的 eBPF 程序进行分析和处理。
在内核空间,eCapture 通过 eBPF 插件捕捉网络层的数据流,比如数据包是从哪里来的、发到了哪里去。这一过程不需要修改应用程序本身,所以对系统性能影响很小。
图片

安卓设备的内核版本只有在5.10版本上才可以进行无任何修改的开箱抓包操作(如果你的设备是安卓13,应该可以正常使用ecapture。低于13的安卓设备,如果内核是5.10,理论也是可行的。 因为安卓使用的linux内核的ebpf环境受内核版本号的影响,而工作良好的ebpf接口是在内核5.5版本时才全部使能。)
可通过adb命令查看自己的设备的内核版本

下载地址

使用说明

服务端代码:

协议实现:

百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压

炒冷饭汇总抓包姿势-上
安卓 App 逆向课程之四 frida 注入 Okhttp 抓包中篇

框架名称 描述 GitHub 地址
Volley 由Google开源的轻量级网络库,支持网络请求处理、小图片的异步加载和缓存等功能 https://github.com/google/volley
Android-async-http 基于Apache HttpClient的一个异步网络请求处理库 https://github.com/android-async-http/android-async-http
xUtils 类似于Afinal,但被认为是Afinal的一个升级版,提供了HTTP请求的支持 https://github.com/wyouflf/xUtils3
OkHttp 一个高性能的网络框架,已经被Google官方认可,在Android 6.0中底层源码已经使用了OkHttp来替代HttpURLConnection https://github.com/square/okhttp
Retrofit 提供了一种类型安全的HTTP客户端接口,简化了HTTP请求的编写,通常与OkHttp配合使用 https://github.com/square/retrofit
OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();
 
Request request = new Request.Builder()
    .url("https://www.52pojie.cn/")
    .header("User-Agent", "OkHttp Example")
    .build();
 
Response response = client.newCall(request).execute();
response.body().close();
OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();
 
Request request = new Request.Builder()
    .url("https://www.52pojie.cn/")
    .header("User-Agent", "OkHttp Example")
    .build();
 
Response response = client.newCall(request).execute();
response.body().close();
/**
 * 查找并配置OkHttpClient的Client和Builder类。
 * 该方法通过反射扫描指定类的字段和方法来确定其是否符合OkHttpClient的结构特征。
 * 如果找到符合的类,则会进一步配置和注入相关拦截器。
 *
 * @param classes   当前扫描的类
 * @param className 类名,用于查找和调试
 */
private void findClientAndBuilderAndBuildAnd(Class classes, String className) {
    try {
        // 确认类是final且静态
        if (Modifier.isFinal(classes.getModifiers())
                && Modifier.isStatic(classes.getModifiers())) {
 
            int listCount = 0;         // 记录List类型字段的数量
            int finalListCount = 0;    // 记录final修饰的List字段数量
            int listInterfaceCount = 0;// 记录List中包含接口的字段数量
            Field[] fields = classes.getDeclaredFields();
            Field.setAccessible(fields, true); // 设置字段访问权限
 
            for (Field field : fields) {
                String type = field.getType().getName();
                if (type.contains(List.class.getName())) {
                    listCount++; // 判断字段是否为List类型
 
                    // 检查List是否是接口类型
                    Class genericClass = getGenericClass(field);
                    if (null != genericClass && genericClass.isInterface()) {
                        listInterfaceCount++;
                    }
                }
 
                // 判断字段是否为final修饰的List类型
                if (type.contains(List.class.getName()) && Modifier.isFinal(field.getModifiers())) {
                    finalListCount++;
                }
            }
 
            // 符合OkHttpClient特征的条件检查
            if (listCount == 4 && finalListCount == 2 && listInterfaceCount == 2) {
                // 获取并确认OkHttpClient的包结构和父类
                Class OkHttpClientClazz = classes.getEnclosingClass();
                if (Cloneable.class.isAssignableFrom(OkHttpClientClazz)) {
                    OkCompat.Cls_OkHttpClient = OkHttpClientClazz.getName();
 
                    if (null != classes && null != classes.getPackage()) {
                        Compat_PackageName = classes.getPackage().getName();
                    }
 
                    Class builderClazz = classes;
 
                    // 查找并注入拦截器
                    find_interceptor(builderClazz);
 
                    // 查找OkHttpClient相关类
                    findClientAbout(OkHttpClientClazz);
 
                    findTag1 = true; // 标记找到目标
                }
            }
        }
    } catch (Throwable th) {
        // 捕获所有异常以防止中断流程,但不处理
    }
}
 
/**
 * 查找并注入Interceptor拦截器到Builder类中。
 * 此方法会扫描Builder类的字段,找到符合拦截器的字段并进行配置。
 *
 * @param builderClazz 需要查找的Builder类
 */
private void find_interceptor(Class builderClazz) {
    // 检查包名是否符合条件
    if (!checkPackage(builderClazz)) return;
 
    Field[] declaredFields = builderClazz.getDeclaredFields();
    Field.setAccessible(declaredFields, true); // 设置字段访问权限
    int index = 0; // 用于计数找到的拦截器字段
 
    for (Field field : declaredFields) {
        // 检查字段是否为final修饰的List类型且包含接口
        if (List.class.isAssignableFrom(field.getType()) && Modifier.isFinal(field.getModifiers())
                && getGenericClass(field).isInterface()) {
            if (index == 0) {
                // 注入自定义Interceptor,提供给JS调用的回调
                findInterceptor(field);
                index++;
            }
        }
    }
}
/**
 * 查找并配置OkHttpClient的Client和Builder类。
 * 该方法通过反射扫描指定类的字段和方法来确定其是否符合OkHttpClient的结构特征。
 * 如果找到符合的类,则会进一步配置和注入相关拦截器。
 *
 * @param classes   当前扫描的类
 * @param className 类名,用于查找和调试
 */
private void findClientAndBuilderAndBuildAnd(Class classes, String className) {
    try {
        // 确认类是final且静态
        if (Modifier.isFinal(classes.getModifiers())
                && Modifier.isStatic(classes.getModifiers())) {
 
            int listCount = 0;         // 记录List类型字段的数量
            int finalListCount = 0;    // 记录final修饰的List字段数量
            int listInterfaceCount = 0;// 记录List中包含接口的字段数量
            Field[] fields = classes.getDeclaredFields();
            Field.setAccessible(fields, true); // 设置字段访问权限
 
            for (Field field : fields) {
                String type = field.getType().getName();
                if (type.contains(List.class.getName())) {
                    listCount++; // 判断字段是否为List类型
 
                    // 检查List是否是接口类型
                    Class genericClass = getGenericClass(field);
                    if (null != genericClass && genericClass.isInterface()) {
                        listInterfaceCount++;
                    }
                }
 
                // 判断字段是否为final修饰的List类型
                if (type.contains(List.class.getName()) && Modifier.isFinal(field.getModifiers())) {
                    finalListCount++;
                }
            }
 
            // 符合OkHttpClient特征的条件检查
            if (listCount == 4 && finalListCount == 2 && listInterfaceCount == 2) {
                // 获取并确认OkHttpClient的包结构和父类
                Class OkHttpClientClazz = classes.getEnclosingClass();
                if (Cloneable.class.isAssignableFrom(OkHttpClientClazz)) {
                    OkCompat.Cls_OkHttpClient = OkHttpClientClazz.getName();
 
                    if (null != classes && null != classes.getPackage()) {
                        Compat_PackageName = classes.getPackage().getName();
                    }
 
                    Class builderClazz = classes;
 
                    // 查找并注入拦截器
                    find_interceptor(builderClazz);
 
                    // 查找OkHttpClient相关类
                    findClientAbout(OkHttpClientClazz);
 
                    findTag1 = true; // 标记找到目标
                }
            }
        }
    } catch (Throwable th) {
        // 捕获所有异常以防止中断流程,但不处理
    }
}
 
/**
 * 查找并注入Interceptor拦截器到Builder类中。
 * 此方法会扫描Builder类的字段,找到符合拦截器的字段并进行配置。
 *
 * @param builderClazz 需要查找的Builder类
 */
private void find_interceptor(Class builderClazz) {
    // 检查包名是否符合条件
    if (!checkPackage(builderClazz)) return;
 
    Field[] declaredFields = builderClazz.getDeclaredFields();
    Field.setAccessible(declaredFields, true); // 设置字段访问权限
    int index = 0; // 用于计数找到的拦截器字段
 
    for (Field field : declaredFields) {
        // 检查字段是否为final修饰的List类型且包含接口
        if (List.class.isAssignableFrom(field.getType()) && Modifier.isFinal(field.getModifiers())
                && getGenericClass(field).isInterface()) {
            if (index == 0) {
                // 注入自定义Interceptor,提供给JS调用的回调
                findInterceptor(field);
                index++;
            }
        }
    }
}
/**
 * hookRealCall - 拦截 OkHttp 的 RealCall 类的网络请求。
 * 该方法通过拦截 RealCall 类的 `enqueue`(异步请求)和 `execute`(同步请求)方法,
 * 实现对网络请求和响应的捕获和处理。
 *
 * @param {string} realCallClassName - OkHttp RealCall 类的完整类名。
 */
function hookRealCall(realCallClassName) {
    Java.perform(function () {
        console.log(" ...........  hookRealCall  : " + realCallClassName)
 
        // 获取 RealCall 类
        var RealCall = Java.use(realCallClassName)
 
        // 检查是否定义了 Cls_CallBack 类(用于异步请求拦截)
        if ("" != Cls_CallBack) {
            // 拦截 RealCall 类中的异步方法 enqueue
            RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) {
                // 获取 callback 的类
                var realCallBack = Java.use(callback.$className)
 
                // 拦截 callback 中的 onResponse 方法,修改返回的响应数据
                realCallBack[M_CallBack_onResponse].overload(Cls_Call, Cls_Response).implementation = function(call, response) {
                    // 使用自定义的 buildNewResponse 方法创建新的响应数据
                    var newResponse = buildNewResponse(response)
                    // 继续执行原始的 onResponse 方法,传入新的响应数据
                    this[M_CallBack_onResponse](call, newResponse)
                }
 
                // 调用原始的 enqueue 方法,传入修改后的 callback
                this[M_Call_enqueue](callback)
                // 释放 callback 类引用
                realCallBack.$dispose
            }
        }
 
        // 拦截 RealCall 类中的同步方法 execute
        RealCall[M_Call_execute].overload().implementation = function () {
            // 调用原始的 execute 方法,获取响应数据
            var response = this[M_Call_execute]()
            // 使用自定义的 buildNewResponse 方法创建新的响应数据
            var newResponse = buildNewResponse(response)
            // 返回新的响应数据
            return newResponse;
        }
    })
}
/**
 * hookRealCall - 拦截 OkHttp 的 RealCall 类的网络请求。
 * 该方法通过拦截 RealCall 类的 `enqueue`(异步请求)和 `execute`(同步请求)方法,
 * 实现对网络请求和响应的捕获和处理。
 *
 * @param {string} realCallClassName - OkHttp RealCall 类的完整类名。
 */
function hookRealCall(realCallClassName) {
    Java.perform(function () {
        console.log(" ...........  hookRealCall  : " + realCallClassName)
 
        // 获取 RealCall 类
        var RealCall = Java.use(realCallClassName)
 
        // 检查是否定义了 Cls_CallBack 类(用于异步请求拦截)
        if ("" != Cls_CallBack) {
            // 拦截 RealCall 类中的异步方法 enqueue
            RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) {
                // 获取 callback 的类
                var realCallBack = Java.use(callback.$className)
 
                // 拦截 callback 中的 onResponse 方法,修改返回的响应数据
                realCallBack[M_CallBack_onResponse].overload(Cls_Call, Cls_Response).implementation = function(call, response) {
                    // 使用自定义的 buildNewResponse 方法创建新的响应数据
                    var newResponse = buildNewResponse(response)
                    // 继续执行原始的 onResponse 方法,传入新的响应数据
                    this[M_CallBack_onResponse](call, newResponse)
                }
 
                // 调用原始的 enqueue 方法,传入修改后的 callback
                this[M_Call_enqueue](callback)
                // 释放 callback 类引用
                realCallBack.$dispose
            }
        }
 
        // 拦截 RealCall 类中的同步方法 execute
        RealCall[M_Call_execute].overload().implementation = function () {
            // 调用原始的 execute 方法,获取响应数据
            var response = this[M_Call_execute]()
            // 使用自定义的 buildNewResponse 方法创建新的响应数据
            var newResponse = buildNewResponse(response)
            // 返回新的响应数据
            return newResponse;
        }
    })
}
D:\Program Files\WORKON_HOME\frida16\frida-agent-example>frida -U wuaipojie -l okhttp_poker.js
     ____
    / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Redmi K30 (id=30d9b4bf)
Attaching...
 
------------------------- OkHttp Poker by SingleMan [V.20201130]------------------------------------
API:
   >>>  find()                                         检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数
   >>>  switchLoader("okhttp3.OkHttpClient")           参数:静态分析到的okhttpclient类名
   >>>  hold()                                         开启HOOK拦截
   >>>  history()                                      打印可重新发送的请求
   >>>  resend(index)                                  重新发送请求
----------------------------------------------------------------------------------------
[Redmi K30::wuaipojie ]-> find()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
likelyClazzList size :352
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var Cls_Call = "okhttp3.Call";
var Cls_CallBack = "okhttp3.Callback";
var Cls_OkHttpClient = "okhttp3.OkHttpClient";
var M_rsp$builder_build = "build";
var M_rsp_newBuilder = "newBuilder";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Redmi K30::wuaipojie ]-> hold()
[Redmi K30::wuaipojie ]->  ...........  hookRealCall  : okhttp3.RealCall
 
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
| URL: http://192.168.124.21:5000/get_user_data
|
| Method: GET
|
| Request Headers: 0
|     no headers
|
|--> END
|
| URL: http://192.168.124.21:5000/get_user_data
|
| Status Code: 200 / OK
|
| Response Headers: 5
|   ┌─Server: Werkzeug/2.3.3 Python/3.10.11
|   ┌─Date: Sun, 27 Oct 2024 04:27:52 GMT
|   ┌─Content-Type: application/json
|   ┌─Content-Length: 104
|   └─Connection: close
|
| Response Body:
|   {"user_data":"{\"user_id\": \"zj2595\", \"is_vip\": true, \"vip_level\": \"5\", \"coin_amount\": 115}"}
 
|
|<-- END HTTP
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
D:\Program Files\WORKON_HOME\frida16\frida-agent-example>frida -U wuaipojie -l okhttp_poker.js
     ____
    / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Redmi K30 (id=30d9b4bf)
Attaching...
 
------------------------- OkHttp Poker by SingleMan [V.20201130]------------------------------------
API:
   >>>  find()                                         检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数
   >>>  switchLoader("okhttp3.OkHttpClient")           参数:静态分析到的okhttpclient类名
   >>>  hold()                                         开启HOOK拦截
   >>>  history()                                      打印可重新发送的请求
   >>>  resend(index)                                  重新发送请求
----------------------------------------------------------------------------------------
[Redmi K30::wuaipojie ]-> find()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
likelyClazzList size :352
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var Cls_Call = "okhttp3.Call";
var Cls_CallBack = "okhttp3.Callback";
var Cls_OkHttpClient = "okhttp3.OkHttpClient";
var M_rsp$builder_build = "build";
var M_rsp_newBuilder = "newBuilder";
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[Redmi K30::wuaipojie ]-> hold()
[Redmi K30::wuaipojie ]->  ...........  hookRealCall  : okhttp3.RealCall
 
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
| URL: http://192.168.124.21:5000/get_user_data
|
| Method: GET
|
| Request Headers: 0
|     no headers
|
|--> END
|
| URL: http://192.168.124.21:5000/get_user_data
|
| Status Code: 200 / OK
|
| Response Headers: 5
|   ┌─Server: Werkzeug/2.3.3 Python/3.10.11
|   ┌─Date: Sun, 27 Oct 2024 04:27:52 GMT
|   ┌─Content-Type: application/json
|   ┌─Content-Length: 104
|   └─Connection: close
|
| Response Body:
|   {"user_data":"{\"user_id\": \"zj2595\", \"is_vip\": true, \"vip_level\": \"5\", \"coin_amount\": 115}"}
 
|
|<-- END HTTP
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
// 使用 Java.use 方法获取 java.net.SocketOutputStream 类,并重写 socketWrite0 方法
Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
    // 调用原始的 socketWrite0 方法
    var result = this.socketWrite0(fd, bytearry, offset, byteCount);
     
    // 创建一个消息对象用于存储数据
    var message = {};
    message["function"] = "HTTP_send"; // 标识为 HTTP 发送操作
    message["ssl_session_id"] = ""; // SSL 会话 ID 为空
 
    // 获取本地地址和端口
    message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
    message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());
 
    // 获取远程地址和端口
    message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
    message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
 
    // 获取调用栈信息
    message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
 
    // 将要发送的数据拷贝到内存中
    var ptr = Memory.alloc(byteCount);
    for (var i = 0; i < byteCount; ++i)
        Memory.writeS8(ptr.add(i), bytearry[offset + i]);
 
    // 发送消息和数据
    send(message, Memory.readByteArray(ptr, byteCount));
     
    // 返回原始方法的结果
    return result;
}
 
// 使用 Java.use 方法获取 java.net.SocketInputStream 类,并重写 socketRead0 方法
Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
    // 调用原始的 socketRead0 方法
    var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
     
    // 创建一个消息对象用于存储数据
    var message = {};
    message["function"] = "HTTP_recv"; // 标识为 HTTP 接收操作
    message["ssl_session_id"] = ""; // SSL 会话 ID 为空
 
    // 获取远程地址和端口(作为源地址)
    message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
    message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
 
    // 获取本地地址和端口(作为目标地址)
    message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
    message["dst_port"] = parseInt(this.socket.value.getLocalPort());
 
    // 获取调用栈信息
    message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
 
    // 如果读取到的数据字节数大于 0,将数据拷贝到内存并发送
    if (result > 0) {
        var ptr = Memory.alloc(result);
        for (var i = 0; i < result; ++i)
            Memory.writeS8(ptr.add(i), bytearry[offset + i]);
        send(message, Memory.readByteArray(ptr, result));
    }
 
    // 返回原始方法的结果
    return result;
}
// 使用 Java.use 方法获取 java.net.SocketOutputStream 类,并重写 socketWrite0 方法
Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
    // 调用原始的 socketWrite0 方法
    var result = this.socketWrite0(fd, bytearry, offset, byteCount);
     
    // 创建一个消息对象用于存储数据
    var message = {};
    message["function"] = "HTTP_send"; // 标识为 HTTP 发送操作
    message["ssl_session_id"] = ""; // SSL 会话 ID 为空
 
    // 获取本地地址和端口
    message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
    message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());
 
    // 获取远程地址和端口
    message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
    message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
 
    // 获取调用栈信息
    message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
 
    // 将要发送的数据拷贝到内存中
    var ptr = Memory.alloc(byteCount);
    for (var i = 0; i < byteCount; ++i)
        Memory.writeS8(ptr.add(i), bytearry[offset + i]);
 
    // 发送消息和数据
    send(message, Memory.readByteArray(ptr, byteCount));
     
    // 返回原始方法的结果
    return result;
}
 
// 使用 Java.use 方法获取 java.net.SocketInputStream 类,并重写 socketRead0 方法
Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
    // 调用原始的 socketRead0 方法
    var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
     
    // 创建一个消息对象用于存储数据
    var message = {};
    message["function"] = "HTTP_recv"; // 标识为 HTTP 接收操作
    message["ssl_session_id"] = ""; // SSL 会话 ID 为空
 
    // 获取远程地址和端口(作为源地址)
    message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
    message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());
 
    // 获取本地地址和端口(作为目标地址)
    message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
    message["dst_port"] = parseInt(this.socket.value.getLocalPort());
 
    // 获取调用栈信息
    message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
 
    // 如果读取到的数据字节数大于 0,将数据拷贝到内存并发送
    if (result > 0) {
        var ptr = Memory.alloc(result);
        for (var i = 0; i < result; ++i)
            Memory.writeS8(ptr.add(i), bytearry[offset + i]);
        send(message, Memory.readByteArray(ptr, result));
    }
 
    // 返回原始方法的结果
    return result;
}
// 拦截 SSLOutputStream 类的 write 方法
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
    // 调用原始的 write 方法
    var result = this.write(bytearry, int1, int2);
    // 获取当前调用栈的字符串形式,存储 SSL 数据写入时的调用栈
    SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
    // 返回原始方法的结果
    return result;
}
 
// 拦截 SSLInputStream 类的 read 方法
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
    // 调用原始的 read 方法
    var result = this.read(bytearry, int1, int2);
    // 获取当前调用栈的字符串形式,存储 SSL 数据读取时的调用栈
    SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
    // 返回原始方法的结果
    return result;
}
// 拦截 SSLOutputStream 类的 write 方法
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
    // 调用原始的 write 方法
    var result = this.write(bytearry, int1, int2);
    // 获取当前调用栈的字符串形式,存储 SSL 数据写入时的调用栈
    SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
    // 返回原始方法的结果
    return result;
}
 
// 拦截 SSLInputStream 类的 read 方法
Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
    // 调用原始的 read 方法
    var result = this.read(bytearry, int1, int2);
    // 获取当前调用栈的字符串形式,存储 SSL 数据读取时的调用栈
    SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
    // 返回原始方法的结果
    return result;
}
函数名称 描述
native.socketWrite0 这是一个 native 方法,负责从 Java 层向底层网络接口写入数据。
libopenjdk.so.NET_Send 这是 libopenjdk.so 中的一个函数,调用底层的 sendto 方法,用于发送数据。
libc.so.sendto 这是一个底层系统调用函数,将数据发送到指定的网络地址。
native.socketRead0 这是一个 native 方法,用于从底层网络接口读取数据。
libopenjdk.so.NET_Read 这是 libopenjdk.so 中的一个函数,调用底层的 recvfrom 方法,负责接收数据。
libopenjdk.so.recvfrom 这是一个底层系统调用函数,用于从网络接口接收数据包。
Hook实现
// 获取 libc.so 库中的 sendto 和 recvfrom 函数的指针
var sendtoPtr = Module.getExportByName("libc.so", "sendto");
var recvfromPtr = Module.getExportByName("libc.so", "recvfrom");
console.log("sendto:", sendtoPtr, ", recvfrom:", recvfromPtr);
 
// 拦截 sendto 函数
// sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
Interceptor.attach(sendtoPtr, {
    onEnter: function(args) {
        // 获取文件描述符 fd
        var fd = args[0];
        // 获取要发送的缓冲区指针 buff
        var buff = args[1];
        // 获取数据大小 size
        var size = args[2];
         
        // 获取套接字的相关信息
        var sockdata = getSocketData(fd.toInt32());
        console.log(sockdata);
         
        // 打印缓冲区的十六进制内容
        console.log(hexdump(buff, { length: size.toInt32() }));
    },
    onLeave: function(retval) {
        // 离开 sendto 函数时不做额外处理
    }
});
 
// 拦截 recvfrom 函数
// recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
Interceptor.attach(recvfromPtr, {
    onEnter: function(args) {
        // 获取文件描述符 fd
        this.fd = args[0];
        // 获取缓冲区指针 buff
        this.buff = args[1];
        // 获取数据大小 size
        this.size = args[2];
    },
    onLeave: function(retval) {
        // 获取套接字的相关信息
        var sockdata = getSocketData(this.fd.toInt32());
        console.log(sockdata);
         
        // 打印接收到的缓冲区的十六进制内容
        console.log(hexdump(this.buff, { length: this.size.toInt32() }));
    }
});
// 获取 libc.so 库中的 sendto 和 recvfrom 函数的指针
var sendtoPtr = Module.getExportByName("libc.so", "sendto");
var recvfromPtr = Module.getExportByName("libc.so", "recvfrom");
console.log("sendto:", sendtoPtr, ", recvfrom:", recvfromPtr);
 
// 拦截 sendto 函数
// sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
Interceptor.attach(sendtoPtr, {
    onEnter: function(args) {
        // 获取文件描述符 fd
        var fd = args[0];
        // 获取要发送的缓冲区指针 buff
        var buff = args[1];
        // 获取数据大小 size
        var size = args[2];
         
        // 获取套接字的相关信息
        var sockdata = getSocketData(fd.toInt32());
        console.log(sockdata);
         
        // 打印缓冲区的十六进制内容
        console.log(hexdump(buff, { length: size.toInt32() }));
    },
    onLeave: function(retval) {
        // 离开 sendto 函数时不做额外处理
    }
});
 
// 拦截 recvfrom 函数
// recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
Interceptor.attach(recvfromPtr, {
    onEnter: function(args) {
        // 获取文件描述符 fd
        this.fd = args[0];
        // 获取缓冲区指针 buff
        this.buff = args[1];
        // 获取数据大小 size
        this.size = args[2];
    },
    onLeave: function(retval) {

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-11-16 20:51 被正己编辑 ,原因:
收藏
免费 12
支持
分享
最新回复 (9)
雪    币: 2295
活跃值: (2992)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-11-17 21:09
0
雪    币: 4583
活跃值: (6836)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3

看大佬的文章,学了许多frida的技能。 感谢分享

最后于 2024-11-18 08:56 被huangjw编辑 ,原因:
2024-11-18 08:56
0
雪    币: 1426
活跃值: (3105)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-11-18 10:18
0
雪    币: 71
活跃值: (65)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2024-11-19 08:52
0
雪    币: 12
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享~
2024-11-20 15:31
0
雪    币: 166
活跃值: (96)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2024-11-21 15:17
0
雪    币: 1724
活跃值: (1524)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
大佬,现在dou音怎么抓? Xposed环境有方便的方法吗
2024-11-22 01:27
0
雪    币: 2670
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
正哥在这里也能看到你,哈哈
2024-11-25 07:49
0
雪    币: 4233
活跃值: (987)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
期待整点脱壳这一类的视频,现在外面的app都是带壳的,不脱能玩的就很少了
2024-11-26 10:02
0
游客
登录 | 注册 方可回帖
返回
//