Android逆向17-抓包进阶 来自吾爱破解-正己
https://www.52pojie.cn/thread-1701353-1-1.html
Hook抓包&&关键定位&&混淆对抗 Hook 抓包是一种截取应用程序数据包的方法,通过 Hook 应用或系统函数来获取数据流。在应用层 Hook 时,通过查找触发请求的函数来抓包,优点是不受防抓包手段影响,缺点是抓包数据不便于我们分析和筛选。
常见安卓网络开发框架
【译】OkHttp3 拦截器(Interceptor)
拦截器是 OkHttp 提供的对 Http 请求和响应进行统一处理的强大机制,它可以实现网络监听、请求以及响应重写、请求失败充实等功能。 OkHttp 中的 Interceptor 就是典型的责任链的实现,它可以设置任意数量的 Intercepter 来对网络请求及其响应做任何中间处理,比如设置缓存,Https证书认证,统一对请求加密/防篡改社会,打印log,过滤请求等等。 OkHttp 中的拦截器分为 Application Interceptor(应用拦截器) 和 NetWork Interceptor(网络拦截器)两种
Network Interceptor(网络拦截器) 通过调用 OkHttpClient.Builder 的 addNetworkInterceptor() 方法来注册网络拦截器
1 2 3 4 5 6 7 8 9 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();
参考项目:
OkHttpLogger-Frida
源码解析:
定位OkHttpClient关键点
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 private void findClientAndBuilderAndBuildAnd (Class classes, String className) { try { if (Modifier.isFinal(classes.getModifiers()) && Modifier.isStatic(classes.getModifiers())) { int listCount = 0 ; int finalListCount = 0 ; int listInterfaceCount = 0 ; Field[] fields = classes.getDeclaredFields(); Field.setAccessible(fields, true ); for (Field field : fields) { String type = field.getType().getName(); if (type.contains(List.class.getName())) { listCount++; Class genericClass = getGenericClass(field); if (null != genericClass && genericClass.isInterface()) { listInterfaceCount++; } } if (type.contains(List.class.getName()) && Modifier.isFinal(field.getModifiers())) { finalListCount++; } } if (listCount == 4 && finalListCount == 2 && listInterfaceCount == 2 ) { 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); findClientAbout(OkHttpClientClazz); findTag1 = true ; } } } } catch (Throwable th) { } } 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) { if (List.class.isAssignableFrom(field.getType()) && Modifier.isFinal(field.getModifiers()) && getGenericClass(field).isInterface()) { if (index == 0 ) { findInterceptor(field); index++; } } } }
拦截器加载关键点
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 function hookRealCall (realCallClassName) { Java.perform(function () { console.log(" ........... hookRealCall : " + realCallClassName) var RealCall = Java.use(realCallClassName) if ("" != Cls_CallBack) { RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) { var realCallBack = Java.use(callback.$className) realCallBack[M_CallBack_onResponse].overload(Cls_Call, Cls_Response).implementation = function(call, response) { var newResponse = buildNewResponse(response) this [M_CallBack_onResponse](call, newResponse) } this [M_Call_enqueue](callback) realCallBack.$dispose } } RealCall[M_Call_execute].overload().implementation = function () { var response = this [M_Call_execute]() var newResponse = buildNewResponse(response) return newResponse; } }) }
使用操作:
1.将 okhttpfind.dex
拷贝到 /data/local/tmp/
目录下(顺带设置一下777权限)
2.执行命令启动frida -U wuaipojie -l okhttp_poker.js
可追加 -o [output filepath]
保存到文件
3.执行find()和hold()方法看看效果
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 56 57 58 59 60 61 62 63 64 65 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 └────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
底层网络自吐&r0capture 问题:如果app不是用okhttp开发的呢?或者混淆定位不到?
原创]android抓包学习的整理和归纳
r0capture开源地址
java 层 http 发包
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 56 57 58 59 60 61 62 63 64 65 66 Java .use ("java.net.SocketOutputStream" ).socketWrite0 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' ).implementation = function (fd, bytearry, offset, byteCount ) { var result = this .socketWrite0 (fd, bytearry, offset, byteCount); var message = {}; message["function" ] = "HTTP_send" ; message["ssl_session_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 .overload ('java.io.FileDescriptor' , '[B' , 'int' , 'int' , 'int' ).implementation = function (fd, bytearry, offset, byteCount, timeout ) { var result = this .socketRead0 (fd, bytearry, offset, byteCount, timeout); var message = {}; message["function" ] = "HTTP_recv" ; message["ssl_session_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 (); 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 中的 socketWrite0
和 socketRead0
方法,在数据发送和接收时收集相关信息并发送给指定的接收方,以便进行监控或调试
java 层 https 发包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Java .use ("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream" ).write .overload ('[B' , 'int' , 'int' ).implementation = function (bytearry, int1, int2 ) { var result = this .write (bytearry, int1, int2); SSLstackwrite = Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new()).toString (); return result; } Java .use ("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream" ).read .overload ('[B' , 'int' , 'int' ).implementation = function (bytearry, int1, int2 ) { var result = this .read (bytearry, int1, int2); SSLstackread = Java .use ("android.util.Log" ).getStackTraceString (Java .use ("java.lang.Throwable" ).$new()).toString (); return result; }
拦截了 SSLOutputStream
和 SSLInputStream
类的 write
和 read
方法,在进行数据读写时获取当前的调用栈信息
native 层 http 发包
函数名称
描述
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
这是一个底层系统调用函数,用于从网络接口接收数据包。
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 var sendtoPtr = Module .getExportByName ("libc.so" , "sendto" );var recvfromPtr = Module .getExportByName ("libc.so" , "recvfrom" );console .log ("sendto:" , sendtoPtr, ", recvfrom:" , recvfromPtr);Interceptor .attach (sendtoPtr, { onEnter : function (args ) { var fd = args[0 ]; var buff = args[1 ]; var size = args[2 ]; var sockdata = getSocketData (fd.toInt32 ()); console .log (sockdata); console .log (hexdump (buff, { length : size.toInt32 () })); }, onLeave : function (retval ) { } }); Interceptor .attach (recvfromPtr, { onEnter : function (args ) { this .fd = args[0 ]; this .buff = args[1 ]; 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 () })); } });
拦截 sendto
和 recvfrom
函数,捕获发送和接收的数据包。onEnter
钩子函数用于在函数调用前处理参数,获取文件描述符和缓冲区地址,调用 hexdump
打印缓冲区内容以便查看实际发送或接收的数据
native 层 https 发包
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 var writePtr = Module .getExportByName ("libc.so" , "write" );var readPtr = Module .getExportByName ("libc.so" , "read" );console .log ("write:" , writePtr, ", read:" , readPtr);Interceptor .attach (writePtr, { onEnter : function (args ) { var fd = args[0 ]; var buff = args[1 ]; var size = args[2 ]; var sockdata = getSocketData (fd.toInt32 ()); if (sockdata.indexOf ("tcp" ) !== -1 ) { console .log (sockdata); console .log (hexdump (buff, { length : size.toInt32 () })); } }, onLeave : function (retval ) { } }); Interceptor .attach (readPtr, { onEnter : function (args ) { this .fd = args[0 ]; this .buff = args[1 ]; this .size = args[2 ]; }, onLeave : function (retval ) { var sockdata = getSocketData (this .fd .toInt32 ()); if (sockdata.indexOf ("tcp" ) !== -1 ) { console .log (sockdata); console .log (hexdump (this .buff , { length : this .size .toInt32 () })); } } }); var sslWritePtr = Module .getExportByName ("libssl.so" , "SSL_write" );var sslReadPtr = Module .getExportByName ("libssl.so" , "SSL_read" );console .log ("sslWrite:" , sslWritePtr, ", sslRead:" , sslReadPtr);var sslGetFdPtr = Module .getExportByName ("libssl.so" , "SSL_get_rfd" );var sslGetFdFunc = new NativeFunction (sslGetFdPtr, 'int' , ['pointer' ]);Interceptor .attach (sslWritePtr, { onEnter : function (args ) { var sslPtr = args[0 ]; var buff = args[1 ]; var size = args[2 ]; var fd = sslGetFdFunc (sslPtr); var sockdata = getSocketData (fd); console .log (sockdata); console .log (hexdump (buff, { length : size.toInt32 () })); }, onLeave : function (retval ) { } }); Interceptor .attach (sslReadPtr, { onEnter : function (args ) { this .sslPtr = args[0 ]; this .buff = args[1 ]; this .size = args[2 ]; }, onLeave : function (retval ) { var fd = sslGetFdFunc (this .sslPtr ); var sockdata = getSocketData (fd); console .log (sockdata); console .log (hexdump (this .buff , { length : this .size .toInt32 () })); } });
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
ebpf抓包实战&ecapture 什么是ebpf
what-is-ebpf
eBPF是一个运行在 Linux 内核里面的虚拟机组件,它可以在无需改变内核代码或者加载内核模块的情况下,安全而又高效地拓展内核的功能。
ebpf的功能之网络抓包
功能
描述
优势
系统调用监控
使用 eBPF 脚本监控应用程序的系统调用,帮助分析应用行为。
- 不需要修改目标程序 - 不易被应用程序检测 - 性能开销低
应用程序插桩
通过 kprobe/uprobe/tracepoints/USDT 对应用程序进行动态插桩,用于监视或修改程序状态。
- 高度便携 - 无需重新编译应用程序 - 支持内核和用户空间
性能问题分析
利用 eBPF 监控内核关键路径,识别性能瓶颈。
- 直接在内核层面工作,减少干扰 - 开销低,准确性高 - 易于实施,已有工具支持
网络抓包
在内核网络层面上使用 eBPF 实现高效的数据包捕获,包括 HTTPS 流量。
- 无需设置代{过}{滤}理或使用其他中间件 - 支持加密流量的捕获(理论上) - 更加安全可靠
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 2 adb shell cat /proc/version 或者adb shell uname -a
下载地址
1 2 adb push ecapture /data/local/tmp/ adb shell chmod 777 /data/local/tmp/ecapture
使用说明
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 NAME: eCapture - 通过eBPF捕获SSL/TLS明文数据,无需安装CA证书。支持Linux/Android内核,适用于amd64/arm64架构。 USAGE: eCapture [flags] VERSION: androidgki_arm64:v0.8.9:6.5.0-1025-azure COMMANDS: bash 捕获bash命令的执行信息 gotls 捕获使用TLS/HTTPS加密的Golang程序的明文通信 help 获取有关任何命令的帮助信息 tls 用于捕获TLS/SSL明文内容,无需CA证书。支持OpenSSL 1.0.x/1.1.x/3.x或更新版本。 DESCRIPTION: eCapture(旁观者)是一个可以捕获如HTTPS和TLS等明文数据包的工具,且不需要安装CA证书。 它还可以捕获bash命令,适用于安全审计场景,比如mysqld数据库审计等(在Android中禁用)。 支持Linux(Android)系统,内核版本为X86_64 4.18或aarch64 5.5及更高版本。 项目仓库:https://github.com/gojue/ecapture 官方主页:https://ecapture.cc 使用方法: ecapture tls -h ecapture bash -h Docker使用示例: docker pull gojue/ecapture:latest docker run --rm --privileged=true --net=host -v ${HOST_PATH}:${CONTAINER_PATH} gojue/ecapture -h NAME: tls - 用于捕获TLS/SSL明文内容,无需CA证书。支持OpenSSL 1.0.x/1.1.x/3.x及更新版本。 USAGE: eCapture tls [flags] DESCRIPTION: 使用eBPF uprobe/TC捕获进程事件数据和网络数据。还支持pcap-NG格式。 示例: ecapture tls -m [text|keylog|pcap] [flags] [pcap过滤表达式(用于pcap模式)] ecapture tls -m pcap -i wlan0 -w save.pcapng host 192.168.1.1 and tcp port 443 ecapture tls -l save.log --pid=3423 ecapture tls --libssl=/lib/x86_64-linux-gnu/libssl.so.1.1 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 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 Docker使用示例: docker pull gojue/ecapture 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 OPTIONS: --cgroup_path="/sys/fs/cgroup" 设置cgroup路径,默认值:/sys/fs/cgroup。 -h, --help[=false] 获取tls命令的帮助信息 -i, --ifname="" (TC Classifier) 要附加探针的网络接口名称 -k, --keylogfile="ecapture_openssl_key.og" 存储SSL/TLS密钥的文件,eCapture捕获加密通信中的密钥并将其保存到该文件 --libssl="" 指定libssl.so文件路径,默认从curl中自动查找 -m, --model="text" 捕获模型,可以是:text(明文内容),pcap/pcapng(原始数据包格式),key/keylog(SSL/TLS密钥) -w, --pcapfile="save.pcapng" 将原始数据包以pcapng格式写入文件 --ssl_version="" 指定OpenSSL/BoringSSL版本,例如:--ssl_version="openssl 1.1.1g" 或 --ssl_version="boringssl 1.1.1" GLOBAL OPTIONS: -b, --btf=0 启用BTF模式(0:自动选择;1:核心模式;2:非核心模式) -d, --debug[=false] 启用调试日志 --eventaddr="" 设置接收捕获事件的服务器地址。默认值与logaddr相同(例如:tcp://127.0.0.1:8090) --hex[=false] 以十六进制字符串打印字节数据 --listen="localhost:28256" 设置HTTP服务器的监听地址,默认值:127.0.0.1:28256 -l, --logaddr="" 设置日志服务器的地址。例如:-l /tmp/ecapture.log 或 -l tcp://127.0.0.1:8080 --mapsize=1024 设置每个CPU的eBPF映射大小(事件缓冲区)。默认值:1024 * PAGESIZE(单位:KB) -p, --pid=0 设置目标进程ID。如果为0,则目标为所有进程 -u, --uid=0 设置目标用户ID。如果为0,则目标为所有用户
1 2 3 adb shell ps | findstr 应用包名(获取进程pid) ./ecapture tls -p pid -m text ./ecapture tls -p 2600 -m text
简单加解密协议实战 说一下这里服务端配置需要通过ipconfig获取到真实的ip地址替换,除此之外,还需要对教程demo里的dex进行修改,字符串搜搜192.,然后把对应接口的ip地址换成刚才获取到的ip地址
服务端代码:
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import hashlib import json import base64 import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad from cryptography.hazmat.primitives import padding from flask import Flask, jsonify, request app = Flask(__name__) def aes_encrypt (data: str ) -> str : key = b'1234567890abcdefwuaipojie0abcdef' iv = b'1234567wuaipojie' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = cipher.encrypt(pad(data.encode('utf-8' ), AES.block_size)) return base64.b64encode(encrypted_data).decode('utf-8' ) def aes_decrypt (encrypted_data: str ) -> dict : key = b'1234567890abcdefwuaipojie0abcdef' iv = b'1234567wuaipojie' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_bytes = base64.b64decode(encrypted_data) decrypted_data = cipher.decrypt(encrypted_bytes) unpadder = padding.PKCS7(AES.block_size * 8 ).unpadder() decrypted_unpadded = unpadder.update(decrypted_data) + unpadder.finalize() decrypted_str = decrypted_unpadded.decode('utf-8' ) return json.loads(decrypted_str) with open ('user_data.json' , 'r' ) as file: user_data = json.load(file) def write_json_file (file_path: str , data: dict ): with open (file_path, 'w' ) as file: json.dump(data, file, indent=4 ) def generate_signature (user_id: str , coin: int , timestamp: int ) -> str : message = f"{user_id} &{coin} &{timestamp} " hash_object = hashlib.md5(message.encode()) return hash_object.hexdigest() @app.route('/get_coin' , methods=['POST' ] ) def get_coin (): encrypted_data = request.json.get('user_data' ) if not encrypted_data: return jsonify({"error" : "数据有误!" }), 400 try : decrypted_data = aes_decrypt(encrypted_data) timestamp = int (decrypted_data.get('timestamp' )) current_time = int (time.time()*1000 ) print (timestamp) print (abs (current_time - timestamp)) if abs (current_time - timestamp) > 5000 : return jsonify({"error" : "请求过期!" }), 400 sign = decrypted_data.get('sign' ) expected_sign = generate_signature(decrypted_data["user_id" ], 1 , timestamp) if sign != expected_sign: return jsonify({"error" : "签名验证失败!" }), 401 user_id = decrypted_data.get('user_id' ) if user_id in user_data['user_id' ]: user_data['coin_amount' ] += 1 write_json_file('user_data.json' , user_data) return jsonify({"投币成功,当前数量为:" : user_data['coin_amount' ]}) else : return jsonify({"error" : "用户未找到!" }), 404 except Exception as e: return jsonify({"error" : f"处理请求时出错: {str (e)} " }), 500 @app.route('/get_user_data' , methods=['GET' ] ) def get_user_data (): data_str = json.dumps(user_data) return jsonify({"user_data" : data_str}) if __name__ == '__main__' : app.run(host='192.168.73.82' , port=5000 )
协议实现:
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 import json import base64 import hashlib import time from Crypto.Cipher import AES from Crypto.Util.Padding import pad from datetime import datetime import requests def generate_signature (user_id: str , coin: int , timestamp: int ) -> str : message = f"{user_id} &{coin} &{timestamp} " hash_object = hashlib.md5(message.encode()) return hash_object.hexdigest() def aes_encrypt (data: str ) -> str : key = b'1234567890abcdefwuaipojie0abcdef' iv = b'1234567wuaipojie' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted_data = cipher.encrypt(pad(data.encode('utf-8' ), AES.block_size)) return base64.b64encode(encrypted_data).decode('utf-8' ) user_data = { "user_id" : "zj2595" , "timestamp" : int (time.time()*1000 ), "sign" : "" , } user_data["sign" ] = generate_signature(user_data["user_id" ], 1 , user_data["timestamp" ]) data_str = json.dumps(user_data) encrypted_data = aes_encrypt(data_str) try : response = requests.post( 'http://192.168.73.82:5000/get_coin' , json={"user_data" : encrypted_data}, ) if response.status_code == 200 : print ("投币成功" ) print ("Response:" , response.json()) else : print (f"Request failed with status code: {response.status_code} " ) except requests.exceptions.RequestException as e: print (f"请求出现异常: {e} " )