-
-
[原创]《安卓逆向这档事》第二十二课、抓包学得好,牢饭吃得饱(下)
-
发表于: 1天前 1945
-
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 中的 socketWrite0
和 socketRead0
方法,在数据发送和接收时收集相关信息并发送给指定的接收方,以便进行监控或调试
Hook实现
拦截了 SSLOutputStream
和 SSLInputStream
类的 write
和 read
方法,在进行数据读写时获取当前的调用栈信息
拦截 sendto
和 recvfrom
函数,捕获发送和接收的数据包。onEnter
钩子函数用于在函数调用前处理参数,获取文件描述符和缓冲区地址,调用 hexdump
打印缓冲区内容以便查看实际发送或接收的数据
Hook实现
r0capture简介
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期)