返回列表 发新帖

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

  [复制链接]

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-11-18 19:15:44 | 显示全部楼层 | 阅读模式  来自 福建
本帖最后由 正己 于 2024-11-18 22:36 编辑






一、课程目标
1.了解hook抓包与混淆对抗
2.了解底层网络自吐
3.了解ebpf抓包
4.简单实战加解密协议
二、工具

1.教程Demo
2.r0capture&ecapture
3.Reqable
4.wireshark
三、课程内容
1.Hook抓包&&关键定位&&混淆对抗
Hook 抓包是一种截取应用程序数据包的方法,通过 Hook 应用或系统函数来获取数据流。在应用层 Hook 时,通过查找触发请求的函数来抓包,优点是不受防抓包手段影响,缺点是抓包数据不便于我们分析和筛选。
`常见安卓网络开发框架`

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

  5. Request request = new Request.Builder()
  6.     .url("https://www.52pojie.cn/")
  7.     .header("User-Agent", "OkHttp Example")
  8.     .build();

  9. Response response = client.newCall(request).execute();
  10. response.body().close();
复制代码

参考项目:
OkHttpLogger-Frida
`源码解析:`
定位OkHttpClient关键点
  1. java
  2. /**
  3. * 查找并配置OkHttpClient的Client和Builder类。
  4. * 该方法通过反射扫描指定类的字段和方法来确定其是否符合OkHttpClient的结构特征。
  5. * 如果找到符合的类,则会进一步配置和注入相关拦截器。
  6. *
  7. * @param classes   当前扫描的类
  8. * @param className 类名,用于查找和调试
  9. */
  10. private void findClientAndBuilderAndBuildAnd(Class classes, String className) {
  11.     try {
  12.         // 确认类是final且静态
  13.         if (Modifier.isFinal(classes.getModifiers())
  14.                 && Modifier.isStatic(classes.getModifiers())) {

  15.             int listCount = 0;         // 记录List类型字段的数量
  16.             int finalListCount = 0;    // 记录final修饰的List字段数量
  17.             int listInterfaceCount = 0;// 记录List中包含接口的字段数量
  18.             Field[] fields = classes.getDeclaredFields();
  19.             Field.setAccessible(fields, true); // 设置字段访问权限

  20.             for (Field field : fields) {
  21.                 String type = field.getType().getName();
  22.                 if (type.contains(List.class.getName())) {
  23.                     listCount++; // 判断字段是否为List类型

  24.                     // 检查List是否是接口类型
  25.                     Class genericClass = getGenericClass(field);
  26.                     if (null != genericClass && genericClass.isInterface()) {
  27.                         listInterfaceCount++;
  28.                     }
  29.                 }

  30.                 // 判断字段是否为final修饰的List类型
  31.                 if (type.contains(List.class.getName()) && Modifier.isFinal(field.getModifiers())) {
  32.                     finalListCount++;
  33.                 }
  34.             }

  35.             // 符合OkHttpClient特征的条件检查
  36.             if (listCount == 4 && finalListCount == 2 && listInterfaceCount == 2) {
  37.                 // 获取并确认OkHttpClient的包结构和父类
  38.                 Class OkHttpClientClazz = classes.getEnclosingClass();
  39.                 if (Cloneable.class.isAssignableFrom(OkHttpClientClazz)) {
  40.                     OkCompat.Cls_OkHttpClient = OkHttpClientClazz.getName();

  41.                     if (null != classes && null != classes.getPackage()) {
  42.                         Compat_PackageName = classes.getPackage().getName();
  43.                     }

  44.                     Class builderClazz = classes;

  45.                     // 查找并注入拦截器
  46.                     find_interceptor(builderClazz);

  47.                     // 查找OkHttpClient相关类
  48.                     findClientAbout(OkHttpClientClazz);

  49.                     findTag1 = true; // 标记找到目标
  50.                 }
  51.             }
  52.         }
  53.     } catch (Throwable th) {
  54.         // 捕获所有异常以防止中断流程,但不处理
  55.     }
  56. }

  57. /**
  58. * 查找并注入Interceptor拦截器到Builder类中。
  59. * 此方法会扫描Builder类的字段,找到符合拦截器的字段并进行配置。
  60. *
  61. * @param builderClazz 需要查找的Builder类
  62. */
  63. private void find_interceptor(Class builderClazz) {
  64.     // 检查包名是否符合条件
  65.     if (!checkPackage(builderClazz)) return;

  66.     Field[] declaredFields = builderClazz.getDeclaredFields();
  67.     Field.setAccessible(declaredFields, true); // 设置字段访问权限
  68.     int index = 0; // 用于计数找到的拦截器字段

  69.     for (Field field : declaredFields) {
  70.         // 检查字段是否为final修饰的List类型且包含接口
  71.         if (List.class.isAssignableFrom(field.getType()) && Modifier.isFinal(field.getModifiers())
  72.                 && getGenericClass(field).isInterface()) {
  73.             if (index == 0) {
  74.                 // 注入自定义Interceptor,提供给JS调用的回调
  75.                 findInterceptor(field);
  76.                 index++;
  77.             }
  78.         }
  79.     }
  80. }

复制代码

拦截器加载关键点
  1. java
  2. /**
  3. * hookRealCall - 拦截 OkHttp 的 RealCall 类的网络请求。
  4. * 该方法通过拦截 RealCall 类的 `enqueue`(异步请求)和 `execute`(同步请求)方法,
  5. * 实现对网络请求和响应的捕获和处理。
  6. *
  7. * @param {string} realCallClassName - OkHttp RealCall 类的完整类名。
  8. */
  9. function hookRealCall(realCallClassName) {
  10.     Java.perform(function () {
  11.         console.log(" ...........  hookRealCall  : " + realCallClassName)

  12.         // 获取 RealCall 类
  13.         var RealCall = Java.use(realCallClassName)

  14.         // 检查是否定义了 Cls_CallBack 类(用于异步请求拦截)
  15.         if ("" != Cls_CallBack) {
  16.             // 拦截 RealCall 类中的异步方法 enqueue
  17.             RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) {
  18.                 // 获取 callback 的类
  19.                 var realCallBack = Java.use(callback.$className)

  20.                 // 拦截 callback 中的 onResponse 方法,修改返回的响应数据
  21.                 realCallBack[M_CallBack_onResponse].overload(Cls_Call, Cls_Response).implementation = function(call, response) {
  22.                     // 使用自定义的 buildNewResponse 方法创建新的响应数据
  23.                     var newResponse = buildNewResponse(response)
  24.                     // 继续执行原始的 onResponse 方法,传入新的响应数据
  25.                     this[M_CallBack_onResponse](call, newResponse)
  26.                 }

  27.                 // 调用原始的 enqueue 方法,传入修改后的 callback
  28.                 this[M_Call_enqueue](callback)
  29.                 // 释放 callback 类引用
  30.                 realCallBack.$dispose
  31.             }
  32.         }

  33.         // 拦截 RealCall 类中的同步方法 execute
  34.         RealCall[M_Call_execute].overload().implementation = function () {
  35.             // 调用原始的 execute 方法,获取响应数据
  36.             var response = this[M_Call_execute]()
  37.             // 使用自定义的 buildNewResponse 方法创建新的响应数据
  38.             var newResponse = buildNewResponse(response)
  39.             // 返回新的响应数据
  40.             return newResponse;
  41.         }
  42.     })
  43. }

复制代码


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

  1. D:\Program Files\WORKON_HOME\frida16\frida-agent-example>frida -U wuaipojie -l okhttp_poker.js
  2.      ____
  3.     / _  |   Frida 16.1.3 - A world-class dynamic instrumentation toolkit
  4.    | (_| |
  5.     > _  |   Commands:
  6.    /_/ |_|       help      -> Displays the help system
  7.    . . . .       object?   -> Display information about 'object'
  8.    . . . .       exit/quit -> Exit
  9.    . . . .
  10.    . . . .   More info at [url]https://frida.re/docs/home/[/url]
  11.    . . . .
  12.    . . . .   Connected to Redmi K30 (id=30d9b4bf)
  13. Attaching...

  14. ------------------------- OkHttp Poker by SingleMan [V.20201130]------------------------------------
  15. API:
  16.    >>>  find()                                         检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数
  17.    >>>  switchLoader("okhttp3.OkHttpClient")           参数:静态分析到的okhttpclient类名
  18.    >>>  hold()                                         开启HOOK拦截
  19.    >>>  history()                                      打印可重新发送的请求
  20.    >>>  resend(index)                                  重新发送请求
  21. ----------------------------------------------------------------------------------------
  22. [Redmi K30::wuaipojie ]-> find()
  23. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  24. likelyClazzList size :352

  25. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  26. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  27. var Cls_Call = "okhttp3.Call";
  28. var Cls_CallBack = "okhttp3.Callback";
  29. var Cls_OkHttpClient = "okhttp3.OkHttpClient";
  30. var M_rsp$builder_build = "build";
  31. var M_rsp_newBuilder = "newBuilder";
  32. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  33. [Redmi K30::wuaipojie ]-> hold()
  34. [Redmi K30::wuaipojie ]->  ...........  hookRealCall  : okhttp3.RealCall

  35. ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  36. | URL: [url]http://192.168.124.21:5000/get_user_data[/url]
  37. |
  38. | Method: GET
  39. |
  40. | Request Headers: 0
  41. |     no headers
  42. |
  43. |--> END
  44. |
  45. | URL: [url]http://192.168.124.21:5000/get_user_data[/url]
  46. |
  47. | Status Code: 200 / OK
  48. |
  49. | Response Headers: 5
  50. |   ┌─Server: Werkzeug/2.3.3 Python/3.10.11
  51. |   ┌─Date: Sun, 27 Oct 2024 04:27:52 GMT
  52. |   ┌─Content-Type: application/json
  53. |   ┌─Content-Length: 104
  54. |   └─Connection: close
  55. |
  56. | Response Body:
  57. |   {"user_data":"{"user_id": "zj2595", "is_vip": true, "vip_level": "5", "coin_amount": 115}"}

  58. |
  59. |<-- END HTTP
  60. └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
复制代码

2.底层网络自吐&r0capture
问题:如果app不是用okhttp开发的呢?或者混淆定位不到?
[[原创]android抓包学习的整理和归纳](https://bbs.kanxue.com/thread-267940.htm)
r0capture开源地址
1.java 层 http 发包





`Hook实现`
  1. js
  2. // 使用 Java.use 方法获取 java.net.SocketOutputStream 类,并重写 socketWrite0 方法
  3. Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) {
  4.     // 调用原始的 socketWrite0 方法
  5.     var result = this.socketWrite0(fd, bytearry, offset, byteCount);
  6.    
  7.     // 创建一个消息对象用于存储数据
  8.     var message = {};
  9.     message["function"] = "HTTP_send"; // 标识为 HTTP 发送操作
  10.     message["ssl_session_id"] = ""; // SSL 会话 ID 为空

  11.     // 获取本地地址和端口
  12.     message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
  13.     message["src_port"] = parseInt(this.socket.value.getLocalPort().toString());

  14.     // 获取远程地址和端口
  15.     message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
  16.     message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());

  17.     // 获取调用栈信息
  18.     message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();

  19.     // 将要发送的数据拷贝到内存中
  20.     var ptr = Memory.alloc(byteCount);
  21.     for (var i = 0; i < byteCount; ++i)
  22.         Memory.writeS8(ptr.add(i), bytearry[offset + i]);

  23.     // 发送消息和数据
  24.     send(message, Memory.readByteArray(ptr, byteCount));
  25.    
  26.     // 返回原始方法的结果
  27.     return result;
  28. }

  29. // 使用 Java.use 方法获取 java.net.SocketInputStream 类,并重写 socketRead0 方法
  30. Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) {
  31.     // 调用原始的 socketRead0 方法
  32.     var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
  33.    
  34.     // 创建一个消息对象用于存储数据
  35.     var message = {};
  36.     message["function"] = "HTTP_recv"; // 标识为 HTTP 接收操作
  37.     message["ssl_session_id"] = ""; // SSL 会话 ID 为空

  38.     // 获取远程地址和端口(作为源地址)
  39.     message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop()));
  40.     message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop());

  41.     // 获取本地地址和端口(作为目标地址)
  42.     message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop()));
  43.     message["dst_port"] = parseInt(this.socket.value.getLocalPort());

  44.     // 获取调用栈信息
  45.     message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();

  46.     // 如果读取到的数据字节数大于 0,将数据拷贝到内存并发送
  47.     if (result > 0) {
  48.         var ptr = Memory.alloc(result);
  49.         for (var i = 0; i < result; ++i)
  50.             Memory.writeS8(ptr.add(i), bytearry[offset + i]);
  51.         send(message, Memory.readByteArray(ptr, result));
  52.     }

  53.     // 返回原始方法的结果
  54.     return result;
  55. }


复制代码

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

2.java 层 https 发包


`Hook实现`
  1. js
  2. // 拦截 SSLOutputStream 类的 write 方法
  3. Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
  4.     // 调用原始的 write 方法
  5.     var result = this.write(bytearry, int1, int2);
  6.     // 获取当前调用栈的字符串形式,存储 SSL 数据写入时的调用栈
  7.     SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
  8.     // 返回原始方法的结果
  9.     return result;
  10. }

  11. // 拦截 SSLInputStream 类的 read 方法
  12. Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
  13.     // 调用原始的 read 方法
  14.     var result = this.read(bytearry, int1, int2);
  15.     // 获取当前调用栈的字符串形式,存储 SSL 数据读取时的调用栈
  16.     SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
  17.     // 返回原始方法的结果
  18.     return result;
  19. }

复制代码

拦截了 `SSLOutputStream` 和 `SSLInputStream` 类的 `write` 和 `read` 方法,在进行数据读写时获取当前的调用栈信息


剩下内容看一楼



已有2人评分好评 金币 理由
生活 + 1 + 1 很给力!但是看不懂,感觉很深奥。.
苇草 + 1 + 1 赞一个!

查看全部评分 总评分:好评 +2  金币 +2 

回复

使用道具 举报

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-11-18 19:21:25 | 显示全部楼层  来自 福建
3.native 层 http 发包



`Hook实现`
  1. js
  2. // 获取 libc.so 库中的 sendto 和 recvfrom 函数的指针
  3. var sendtoPtr = Module.getExportByName("libc.so", "sendto");
  4. var recvfromPtr = Module.getExportByName("libc.so", "recvfrom");
  5. console.log("sendto:", sendtoPtr, ", recvfrom:", recvfromPtr);

  6. // 拦截 sendto 函数
  7. // sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
  8. Interceptor.attach(sendtoPtr, {
  9.     onEnter: function(args) {
  10.         // 获取文件描述符 fd
  11.         var fd = args[0];
  12.         // 获取要发送的缓冲区指针 buff
  13.         var buff = args[1];
  14.         // 获取数据大小 size
  15.         var size = args[2];
  16.         
  17.         // 获取套接字的相关信息
  18.         var sockdata = getSocketData(fd.toInt32());
  19.         console.log(sockdata);
  20.         
  21.         // 打印缓冲区的十六进制内容
  22.         console.log(hexdump(buff, { length: size.toInt32() }));
  23.     },
  24.     onLeave: function(retval) {
  25.         // 离开 sendto 函数时不做额外处理
  26.     }
  27. });

  28. // 拦截 recvfrom 函数
  29. // recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
  30. Interceptor.attach(recvfromPtr, {
  31.     onEnter: function(args) {
  32.         // 获取文件描述符 fd
  33.         this.fd = args[0];
  34.         // 获取缓冲区指针 buff
  35.         this.buff = args[1];
  36.         // 获取数据大小 size
  37.         this.size = args[2];
  38.     },
  39.     onLeave: function(retval) {
  40.         // 获取套接字的相关信息
  41.         var sockdata = getSocketData(this.fd.toInt32());
  42.         console.log(sockdata);
  43.         
  44.         // 打印接收到的缓冲区的十六进制内容
  45.         console.log(hexdump(this.buff, { length: this.size.toInt32() }));
  46.     }
  47. });


复制代码

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

`Hook实现`
  1. js
  2. // 获取 libc.so 库中的 write 和 read 函数的指针
  3. var writePtr = Module.getExportByName("libc.so", "write");
  4. var readPtr = Module.getExportByName("libc.so", "read");
  5. console.log("write:", writePtr, ", read:", readPtr);

  6. // 拦截 write 函数
  7. // write(int fd, const void *buf, size_t count)
  8. Interceptor.attach(writePtr, {
  9.     onEnter: function(args) {
  10.         // 获取文件描述符 fd
  11.         var fd = args[0];
  12.         // 获取写入的数据缓冲区指针 buff
  13.         var buff = args[1];
  14.         // 获取数据大小 size
  15.         var size = args[2];

  16.         // 获取套接字信息(假设 getSocketData 是自定义函数)
  17.         var sockdata = getSocketData(fd.toInt32());

  18.         // 如果套接字是 TCP 类型,打印相关数据
  19.         if (sockdata.indexOf("tcp") !== -1) {
  20.             console.log(sockdata);
  21.             console.log(hexdump(buff, { length: size.toInt32() }));
  22.         }
  23.     },
  24.     onLeave: function(retval) {
  25.         // 离开 write 函数时不做额外处理
  26.     }
  27. });

  28. // 拦截 read 函数
  29. // read(int fd, void *buf, size_t count)
  30. Interceptor.attach(readPtr, {
  31.     onEnter: function(args) {
  32.         // 获取文件描述符 fd
  33.         this.fd = args[0];
  34.         // 获取读取的缓冲区指针 buff
  35.         this.buff = args[1];
  36.         // 获取数据大小 size
  37.         this.size = args[2];
  38.     },
  39.     onLeave: function(retval) {
  40.         // 获取套接字信息
  41.         var sockdata = getSocketData(this.fd.toInt32());

  42.         // 如果套接字是 TCP 类型,打印相关数据
  43.         if (sockdata.indexOf("tcp") !== -1) {
  44.             console.log(sockdata);
  45.             console.log(hexdump(this.buff, { length: this.size.toInt32() }));
  46.         }
  47.     }
  48. });

  49. // 获取 libssl.so 中的 SSL_write、SSL_read 和 SSL_get_rfd 函数的指针
  50. var sslWritePtr = Module.getExportByName("libssl.so", "SSL_write");
  51. var sslReadPtr = Module.getExportByName("libssl.so", "SSL_read");
  52. console.log("sslWrite:", sslWritePtr, ", sslRead:", sslReadPtr);

  53. // 获取 SSL_get_rfd 函数的指针,用于从 SSL 结构体中获取文件描述符
  54. var sslGetFdPtr = Module.getExportByName("libssl.so", "SSL_get_rfd");
  55. // 使用 NativeFunction 创建对 SSL_get_rfd 函数的调用
  56. var sslGetFdFunc = new NativeFunction(sslGetFdPtr, 'int', ['pointer']);

  57. // 拦截 SSL_write 函数
  58. // int SSL_write(SSL *ssl, const void *buf, int num)
  59. Interceptor.attach(sslWritePtr, {
  60.     onEnter: function(args) {
  61.         // 获取 SSL 对象指针
  62.         var sslPtr = args[0];
  63.         // 获取要发送的缓冲区指针
  64.         var buff = args[1];
  65.         // 获取数据大小
  66.         var size = args[2];
  67.         
  68.         // 使用 SSL_get_rfd 获取文件描述符
  69.         var fd = sslGetFdFunc(sslPtr);
  70.         // 获取套接字的数据(假设 getSocketData 是自定义函数)
  71.         var sockdata = getSocketData(fd);
  72.         
  73.         // 打印套接字数据和发送数据的十六进制内容
  74.         console.log(sockdata);
  75.         console.log(hexdump(buff, { length: size.toInt32() }));
  76.     },
  77.     onLeave: function(retval) {
  78.         // 离开 SSL_write 函数时不做额外处理
  79.     }
  80. });

  81. // 拦截 SSL_read 函数
  82. // int SSL_read(SSL *ssl, void *buf, int num)
  83. Interceptor.attach(sslReadPtr, {
  84.     onEnter: function(args) {
  85.         // 获取 SSL 对象指针
  86.         this.sslPtr = args[0];
  87.         // 获取接收缓冲区指针
  88.         this.buff = args[1];
  89.         // 获取接收数据的大小
  90.         this.size = args[2];
  91.     },
  92.     onLeave: function(retval) {
  93.         // 使用 SSL_get_rfd 获取文件描述符
  94.         var fd = sslGetFdFunc(this.sslPtr);
  95.         // 获取套接字的数据
  96.         var sockdata = getSocketData(fd);
  97.         
  98.         // 打印套接字数据和接收到的十六进制数据
  99.         console.log(sockdata);
  100.         console.log(hexdump(this.buff, { length: this.size.toInt32() }));
  101.     }
  102. });

复制代码

`r0capture简介`
- 仅限安卓平台,测试安卓7-14 可用 ;
- 无视所有证书校验或绑定;
- 通杀TCP/IP四层模型中的应用层中的全部协议;
- 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本;
- 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等;
- 无视加固
局限:部分开发实力过强的大厂或框架,采用的是自身的SSL框架,比如WebView、部分融合App、小程序或Flutter,这部分目前暂未支持。

  1. python3 r0capture.py -U wuaipojie -v -p test.pcap
复制代码

wireshark下载地址
3.ebpf抓包实战&ecapture
1.什么是ebpf
what-is-ebpf
eBPF是一个运行在 Linux 内核里面的虚拟机组件,它可以在无需改变内核代码或者加载内核模块的情况下,安全而又高效地拓展内核的功能。


2.ebpf的功能之网络抓包



ecapture
官方案例
eCapture主要利用了eBPF和HOOK技术:
- **eBPF加载机制**:利用eBPF技术进行数据包的捕获和处理,eBPF程序是事件驱动的,当内核或应用程序通过某个挂钩点时运行。预定义的钩子包括系统调用、函数入口/出口、内核跟踪点、网络事件和其他几个;
- **HOOK机制**:使用eBPF uprobe相关函数进行用户态函数的HOOK,支持对不同编程语言实现的加密库进行HOOK,如OpenSSL、GnuTLS、NSS/NSPR。

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


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

  1. adb shell cat /proc/version
  2. 或者adb shell uname -a
复制代码

下载地址

  1. adb push ecapture /data/local/tmp/
  2. adb shell chmod 777 /data/local/tmp/ecapture
复制代码

`使用说明`

  1. NAME:
  2.         eCapture - 通过eBPF捕获SSL/TLS明文数据,无需安装CA证书。支持Linux/Android内核,适用于amd64/arm64架构。

  3. USAGE:
  4.         eCapture [flags]

  5. VERSION:
  6.         androidgki_arm64:v0.8.9:6.5.0-1025-azure

  7. COMMANDS:
  8.         bash    捕获bash命令的执行信息
  9.         gotls   捕获使用TLS/HTTPS加密的Golang程序的明文通信
  10.         help    获取有关任何命令的帮助信息
  11.         tls     用于捕获TLS/SSL明文内容,无需CA证书。支持OpenSSL 1.0.x/1.1.x/3.x或更新版本。

  12. DESCRIPTION:
  13.         eCapture(旁观者)是一个可以捕获如HTTPS和TLS等明文数据包的工具,且不需要安装CA证书。
  14.         它还可以捕获bash命令,适用于安全审计场景,比如mysqld数据库审计等(在Android中禁用)。
  15.         支持Linux(Android)系统,内核版本为X86_64 4.18或aarch64 5.5及更高版本。
  16.         项目仓库:[url]https://github.com/gojue/ecapture[/url]
  17.         官方主页:[url]https://ecapture.cc[/url]

  18.         使用方法:
  19.           ecapture tls -h
  20.           ecapture bash -h

  21.         Docker使用示例:
  22.         docker pull gojue/ecapture:latest
  23.         docker run --rm --privileged=true --net=host -v ${HOST_PATH}:${CONTAINER_PATH} gojue/ecapture -h

  24. NAME:
  25.         tls - 用于捕获TLS/SSL明文内容,无需CA证书。支持OpenSSL 1.0.x/1.1.x/3.x及更新版本。

  26. USAGE:
  27.         eCapture tls [flags]

  28. DESCRIPTION:
  29.         使用eBPF uprobe/TC捕获进程事件数据和网络数据。还支持pcap-NG格式。

  30.         示例:
  31.         ecapture tls -m [text|keylog|pcap] [flags] [pcap过滤表达式(用于pcap模式)]
  32.         ecapture tls -m pcap -i wlan0 -w save.pcapng host 192.168.1.1 and tcp port 443
  33.         ecapture tls -l save.log --pid=3423
  34.         ecapture tls --libssl=/lib/x86_64-linux-gnu/libssl.so.1.1
  35.         ecapture tls -m keylog --pcapfile save_3_0_5.pcapng --ssl_version="openssl 3.0.5" --libssl=/lib/x86_64-linux-gnu/libssl.so.3
  36.         ecapture tls -m pcap --pcapfile save_android.pcapng -i wlan0 --libssl=/apex/com.android.conscrypt/lib64/libssl.so --ssl_version="boringssl 1.1.1" tcp port 443

  37.         Docker使用示例:
  38.         docker pull gojue/ecapture
  39.         docker run --rm --privileged=true --net=host -v /etc:/etc -v /usr:/usr -v ${PWD}:/output gojue/ecapture tls -m pcap -i wlp3s0 --pcapfile=/output/ecapture.pcapng tcp port 443

  40. OPTIONS:
  41.       --cgroup_path="/sys/fs/cgroup"            设置cgroup路径,默认值:/sys/fs/cgroup。
  42.   -h, --help[=false]                            获取tls命令的帮助信息
  43.   -i, --ifname=""                               (TC Classifier) 要附加探针的网络接口名称
  44.   -k, --keylogfile="ecapture_openssl_key.og"    存储SSL/TLS密钥的文件,eCapture捕获加密通信中的密钥并将其保存到该文件
  45.       --libssl=""                               指定libssl.so文件路径,默认从curl中自动查找
  46.   -m, --model="text"                            捕获模型,可以是:text(明文内容),pcap/pcapng(原始数据包格式),key/keylog(SSL/TLS密钥)
  47.   -w, --pcapfile="save.pcapng"                  将原始数据包以pcapng格式写入文件
  48.       --ssl_version=""                          指定OpenSSL/BoringSSL版本,例如:--ssl_version="openssl 1.1.1g" 或 --ssl_version="boringssl 1.1.1"

  49. GLOBAL OPTIONS:
  50.   -b, --btf=0                           启用BTF模式(0:自动选择;1:核心模式;2:非核心模式)
  51.   -d, --debug[=false]                   启用调试日志
  52.       --eventaddr=""                    设置接收捕获事件的服务器地址。默认值与logaddr相同(例如:tcp://127.0.0.1:8090)
  53.       --hex[=false]                     以十六进制字符串打印字节数据
  54.       --listen="localhost:28256"        设置HTTP服务器的监听地址,默认值:127.0.0.1:28256
  55.   -l, --logaddr=""                      设置日志服务器的地址。例如:-l /tmp/ecapture.log 或 -l tcp://127.0.0.1:8080
  56.       --mapsize=1024                    设置每个CPU的eBPF映射大小(事件缓冲区)。默认值:1024 * PAGESIZE(单位:KB)
  57.   -p, --pid=0                           设置目标进程ID。如果为0,则目标为所有进程
  58.   -u, --uid=0                           设置目标用户ID。如果为0,则目标为所有用户

复制代码


  1. adb shell ps | findstr 应用包名(获取进程pid)
  2. ./ecapture tls -p pid -m text
复制代码
回复

使用道具 举报

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-11-18 19:40:21 | 显示全部楼层  来自 福建
本帖最后由 正己 于 2024-11-20 09:17 编辑

4.简单加解密协议实战
说一下这里服务端配置需要通过ipconfig获取到真实的ip地址替换,除此之外,还需要对教程demo里的dex进行修改,字符串搜搜192.,然后把对应接口的ip地址换成刚才获取到的ip地址
`服务端代码:`
  1. python
  2. import hashlib  
  3. import json  
  4. import base64  
  5. import time  
  6. from Crypto.Cipher import AES  
  7. from Crypto.Util.Padding import pad  
  8. from cryptography.hazmat.primitives import padding  
  9. from flask import Flask, jsonify, request  
  10.   
  11. app = Flask(__name__)  
  12.   
  13.   
  14. [b]加密函数  [/b]
  15. def aes_encrypt(data: str) -> str:  
  16.     key = b'1234567890abcdefwuaipojie0abcdef'  
  17.     iv = b'1234567wuaipojie'  [b]Initialization Vector  [/b]
  18.     cipher = AES.new(key, AES.MODE_CBC, iv)  
  19.     encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))  
  20.     return base64.b64encode(encrypted_data).decode('utf-8')  
  21.   
  22. [b]解密函数  [/b]
  23. def aes_decrypt(encrypted_data: str) -> dict:  
  24.     key = b'1234567890abcdefwuaipojie0abcdef'  
  25.     iv = b'1234567wuaipojie'  [b]Initialization Vector  [/b]
  26.     cipher = AES.new(key, AES.MODE_CBC, iv)  
  27.     encrypted_bytes = base64.b64decode(encrypted_data)  
  28.     decrypted_data = cipher.decrypt(encrypted_bytes)  
  29.     unpadder = padding.PKCS7(AES.block_size * 8).unpadder()  
  30.     decrypted_unpadded = unpadder.update(decrypted_data) + unpadder.finalize()  
  31.     decrypted_str = decrypted_unpadded.decode('utf-8')  
  32.     return json.loads(decrypted_str)  
  33.   
  34. [b]读取用户数据  [/b]
  35. with open('user_data.json', 'r') as file:  
  36.     user_data = json.load(file)  
  37.   
  38. [b]写入本地JSON文件  [/b]
  39. def write_json_file(file_path: str, data: dict):  
  40.     with open(file_path, 'w') as file:  
  41.         json.dump(data, file, indent=4)  
  42.   
  43. [b]生成签名函数  [/b]
  44. def generate_signature(user_id: str, coin: int, timestamp: int) -> str:  
  45.     message = f"{user_id}&{coin}&{timestamp}"  
  46.     hash_object = hashlib.md5(message.encode())  
  47.     return hash_object.hexdigest()  
  48.   
  49.   
  50. @app.route('/get_coin', methods=['POST'])  
  51. def get_coin():  
  52.     [b]获取加密的数据  [/b]
  53.     encrypted_data = request.json.get('user_data')  
  54.     if not encrypted_data:  
  55.         return jsonify({"error": "数据有误!"}), 400  
  56.     try:  
  57.         [b]解密数据  [/b]
  58.         decrypted_data = aes_decrypt(encrypted_data)  
  59.         [b]验证签名  [/b]
  60.         timestamp = int(decrypted_data.get('timestamp'))  
  61.         current_time = int(time.time()*1000)  
  62.         print(timestamp)  
  63.         print(abs(current_time - timestamp))  
  64.         if abs(current_time - timestamp) > 5000:  
  65.             return jsonify({"error": "请求过期!"}), 400  
  66.         sign = decrypted_data.get('sign')  
  67.         [b]计算签名  [/b]
  68.         expected_sign = generate_signature(decrypted_data["user_id"], 1, timestamp)  
  69.         if sign != expected_sign:  
  70.             return jsonify({"error": "签名验证失败!"}), 401  
  71.         [b]验证成功后,获取用户的金币数量  [/b]
  72.         user_id = decrypted_data.get('user_id')  
  73.         if user_id in user_data['user_id']:  
  74.             user_data['coin_amount'] += 1  [b]增加金币数量  [/b]
  75.             write_json_file('user_data.json', user_data)  [b]写入文件  [/b]
  76.             return jsonify({"投币成功,当前数量为:": user_data['coin_amount']})  
  77.         else:  
  78.             return jsonify({"error": "用户未找到!"}), 404  
  79.     except Exception as e:  
  80.         return jsonify({"error": f"处理请求时出错: {str(e)}"}), 500  
  81.   
  82. @app.route('/get_user_data', methods=['GET'])  
  83. def get_user_data():  
  84.     [b]将数据转换成字符串形式以便于加密  [/b]
  85.     data_str = json.dumps(user_data)  
  86.     return jsonify({"user_data": data_str})  
  87.   
  88. if __name__ == '__main__':  
  89.     app.run(host='192.168.73.82', port=5000)
复制代码


`协议实现:`
  1. python
  2. import json  
  3. import base64  
  4. import hashlib  
  5. import time  
  6.   
  7. from Crypto.Cipher import AES  
  8. from Crypto.Util.Padding import pad  
  9. from datetime import datetime  
  10. import requests  
  11.   
  12. [b]生成签名函数  [/b]
  13. def generate_signature(user_id: str, coin: int, timestamp: int) -> str:  
  14.     message = f"{user_id}&{coin}&{timestamp}"  
  15.     hash_object = hashlib.md5(message.encode())  
  16.     return hash_object.hexdigest()  
  17.   
  18. [b]加密函数  [/b]
  19. def aes_encrypt(data: str) -> str:  
  20.     key = b'1234567890abcdefwuaipojie0abcdef'  
  21.     iv = b'1234567wuaipojie'  [b]Initialization Vector  [/b]
  22.     cipher = AES.new(key, AES.MODE_CBC, iv)  
  23.     encrypted_data = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))  
  24.     return base64.b64encode(encrypted_data).decode('utf-8')  
  25.   
  26. [b]模拟用户数据  [/b]
  27. user_data = {  
  28.     "user_id": "zj2595",  
  29.     "timestamp": int(time.time()*1000),  [b]当前时间的时间戳  [/b]
  30.     "sign": "",  [b]这个稍后计算并赋值  [/b]
  31. }  
  32.   
  33. [b]计算签名  [/b]
  34. user_data["sign"] = generate_signature(user_data["user_id"], 1, user_data["timestamp"])  
  35.   
  36. [b]转换为JSON字符串  [/b]
  37. data_str = json.dumps(user_data)  
  38.   
  39. [b]加密数据  [/b]
  40. encrypted_data = aes_encrypt(data_str)  
  41.   
  42. [b]发送POST请求  [/b]
  43. try:  
  44.     response = requests.post(  
  45.         'http://192.168.73.82:5000/get_coin',  
  46.         json={"user_data": encrypted_data},  
  47.   
  48.     )  
  49.     if response.status_code == 200:  
  50.         print("投币成功")  
  51.         print("Response:", response.json())  
  52.     else:  
  53.         print(f"Request failed with status code: {response.status_code}")  
  54. except requests.exceptions.RequestException as e:  
  55.     print(f"请求出现异常: {e}")
复制代码

四、请作者喝杯咖啡


六、视频及课件地址


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

# 八、参考文档
[炒冷饭汇总抓包姿势-上](https://bbs.kanxue.com/thread-278142.htm)
[安卓 App 逆向课程之四 frida 注入 Okhttp 抓包中篇](https://cloud.tencent.com/developer/article/1663828)
回复

使用道具 举报

27

主题

318

回帖

1904

积分

高中生

Rank: 4

金币
440
好评
2
信誉
101
发表于 2024-11-18 19:17:00 来自手机  | 显示全部楼层  来自 安徽
看看隐藏
回复

使用道具 举报

134

主题

3460

回帖

1万

积分

博士生

无敌最俊朗

Rank: 7Rank: 7Rank: 7

金币
3010
好评
49
信誉
105
发表于 2024-11-18 19:19:48 来自手机  | 显示全部楼层  来自 四川
学习学习
回复

使用道具 举报

126

主题

3763

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
6579
好评
13
信誉
143

MT论坛新人MT论坛最佳新人考神MT论坛活跃会员

发表于 2024-11-18 19:20:17 来自手机  | 显示全部楼层  来自 四川
看看隐藏,解决温饱
回复

使用道具 举报

85

主题

7249

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
777
好评
96
信誉
147

考神MT论坛活跃会员MT论坛新人MT论坛最佳新人MT论坛帅哥MT论坛侠客挂机大佬

发表于 2024-11-18 19:20:49 来自手机  | 显示全部楼层  来自 新疆
学习学习
回复

使用道具 举报

10

主题

1978

回帖

6524

积分

硕士生

2024-06-21

Rank: 6Rank: 6

金币
2037
好评
62
信誉
140

考神MT论坛新人MT论坛帅哥

发表于 2024-11-18 19:22:41 来自手机  | 显示全部楼层  来自 江西
看看隐藏
回复

使用道具 举报

6

主题

1242

回帖

5939

积分

硕士生

看看

Rank: 6Rank: 6

金币
3198
好评
0
信誉
100
发表于 2024-11-18 19:23:37 来自手机  | 显示全部楼层  来自 江苏
看看隐藏
回复

使用道具 举报

10

主题

1646

回帖

7998

积分

硕士生

Rank: 6Rank: 6

金币
259
好评
10
信誉
120
发表于 2024-11-18 19:24:50 来自手机  | 显示全部楼层  来自 四川
支持
回复

使用道具 举报

2

主题

2291

回帖

6981

积分

硕士生

Rank: 6Rank: 6

金币
6154
好评
2
信誉
100

MT论坛新人考神

发表于 2024-11-18 19:25:12 来自手机  | 显示全部楼层  来自 浙江
感谢分享
回复

使用道具 举报

15

主题

2416

回帖

6503

积分

硕士生

Rank: 6Rank: 6

金币
3460
好评
0
信誉
84
QQ
发表于 2024-11-18 19:25:53 来自手机  | 显示全部楼层  来自 内蒙古
看看你的隐藏吧
回复

使用道具 举报

52

主题

4718

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
3649
好评
97
信誉
1072

考神MT论坛新人

发表于 2024-11-18 19:25:55 来自手机  | 显示全部楼层  来自 广西
谢谢分享
回复

使用道具 举报

152

主题

7921

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
11462
好评
10
信誉
104

MT论坛最佳新人考神MT论坛新人MT论坛帅哥

发表于 2024-11-18 19:30:08 来自手机  | 显示全部楼层  来自 云南
看看隐藏
回复

使用道具 举报

0

主题

5

回帖

97

积分

小学生

Rank: 2

金币
37
好评
0
信誉
100
发表于 2024-11-18 19:32:26 | 显示全部楼层  来自 浙江
看一下,哈哈,
回复

使用道具 举报

61

主题

3486

回帖

1万

积分

博士生

轻音永不毕业

Rank: 7Rank: 7Rank: 7

金币
3499
好评
2
信誉
100

MT论坛新人考神

发表于 2024-11-18 19:33:54 来自手机  | 显示全部楼层  来自 广西
学习学习
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表