🛫 系列文章导航
- 【Frida】【Android】01_手把手教你环境搭建 https://blog.csdn.net/kinghzking/article/details/136986950
- 【Frida】【Android】02_JAVA层HOOK https://blog.csdn.net/kinghzking/article/details/137008446
- 【Frida】【Android】03_RPC https://blog.csdn.net/kinghzking/article/details/137050967
- 【Frida】【Android】04_Objection安装和使用 https://blog.csdn.net/kinghzking/article/details/137071768
- 【Frida】【Android】05_Objection实战 https://blog.csdn.net/kinghzking/article/details/137071826
- 【Frida】【Android】 06_夜神模拟器中间人抓包 https://blog.csdn.net/kinghzking/article/details/137162859
- 【Frida】【Android】 07_爬虫之网络通信库HttpURLConnection https://blog.csdn.net/kinghzking/article/details/137211973
- 【Frida】【Android】 08_爬虫之网络通信库okhttp3 https://blog.csdn.net/kinghzking/article/details/137227041
- 【Frida】【Android】 09_爬虫之Socket https://blog.csdn.net/kinghzking/article/details/137284648
- 【Frida】【Android】 工具篇:ZenTracer https://blog.csdn.net/kinghzking/article/details/137284648
▒ 目录 ▒
- 🛫 系列文章导航
- 🛫 导读
- 开发环境
- 1️⃣ Socket抓包原理
- 2️⃣ APP源码说明
- ezReq
- 按钮绑定及遇到问题解决
- 3️⃣ HTTP的Socket分析
- ZenTracer分析
- 编写代码:jhexdump
- 编写代码:hookSocket
- 验证效果
- 4️⃣ HTTPS的Socket分析
- ZenTracer分析
- 编写代码:
- 验证效果
- 5️⃣ 验证HTTPS请求流程
- objection测试
- 🛬 文章小结
- 📖 参考资料
🛫 导读
开发环境
版本号 | 描述 | |
---|---|---|
文章日期 | 2024-04-02 | |
操作系统 | Win11 - 22H2 | 22621.2715 |
node -v | v20.10.0 | |
npm -v | 10.2.3 | |
夜神模拟器 | 7.0.5.8 | |
Android | 9 | |
python | 3.9.9 | |
frida | 16.2.1 | |
frida-tools | 12.3.0 | |
objection | 1.11.0 | |
1️⃣ Socket抓包原理
前几篇所讨论的HTTP是应用层的协议,如下图所示。
HTTP数据从应用层发送出去后,依次经过传输层、网络层、链路层,在经过每一层时都会被包裹上头部数据,以保证在数据传输过程中的完整性,然后传输给接收方;接收方以相反的过程依次去除头部数据从而获取真实传输的HTTP数据。
因此,如果对应用进行抓包,那么不仅仅是应用层,在传输层、网络层等应用层往下的所有层级都可以获取传输的全部数据。这正是在传输层
进行Socket终极抓包的理论基础。
只要开发者使用了应用层框架,不可避免地会使用系统的Socket进行数据包的收发。
如果使用的是HTTP
协议,则直接使用Socket,此时数据如果没有任何代码层面的加解密,直接就是明文
,将内容dump下来即可进行分析;
如果使用的是HTTPS
协议,那么HTTP包还要“裹上”一层SSL
,最终通过SSL的接口进行收发,而SSL也会将加密后和解密前的数据通过Socket与服务器进行通信,如下图所示。
2️⃣ APP源码说明
本文示例中的APP可以从本篇绑定的资源中下载。主要功能是传递一个url,点击执行HTTP/HTTPS请求。
源码地址: https://gitcode.com/android8/AndroidFridaBeginnersBook
ezReq
该函数对HttpURLConnection进行封装,执行网络请求。具体含义参考《【Frida】【Android】 07_爬虫之网络通信库HttpURLConnection https://blog.csdn.net/kinghzking/article/details/137211973》。
public void ezReq(String urlStr) {
try {
URL url = new URL(urlStr);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("token","demo");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.connect(); // 开始连接
InputStream in = connection.getInputStream();
//if(in.available() > 0){
// 每次写入1024字节
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
StringBuffer sb = new StringBuffer();
while ((in.read(buffer)) != -1) {
sb.append(new String(buffer));
}
Log.d("demo", sb.toString());
connection.disconnect();
// }
} catch (IOException e) {
e.printStackTrace();
}
}
按钮绑定及遇到问题解决
NetworkOnMainThreadException
在Android 4.0以上,网络连接不能放在主线程上,否则报错android.os.NetworkOnMainThreadException。
所以,我们需要new Thread
创建一个线程将网络请求包进去。
Cleartext HTTP traffic to XXX not permitted
从Android 9.0(API级别28)
开始,默认情况下限制了明文流量的网络请求,对未加密流量不再信任,直接放弃请求,因此http的url均无法在webview中加载,https 不受影响。
小编使用的模拟器是Android 9,当执行http请求时就会报上面的问题。可以通过下面步骤解决:
- res 下新建 xml 目录,创建文件:network_security_config.xml ,内容如下:
- 在 AndroidManifest.xml 的 application 标签添加配置:
Button button = findViewById(R.id.buttonReq);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
(new Thread(new Runnable() {
@Override
public void run() {
//请求详情
TextView viewById = findViewById(R.id.textInputEditText);
String url = viewById.getText().toString();
Log.d("url = ", url);
ezReq(url);
}
})).start();
}
});
3️⃣ HTTP的Socket分析
ZenTracer分析
关于ZenTracer的使用,可以参考文章:《【Frida】【Android】 工具篇:ZenTracer https://blog.csdn.net/kinghzking/article/details/137284648》
- 我们设置好匹配内容为
Socket
(注意大小写),APP中设置url为http://www.baidu.com
,然后点击执行请求:
这时候在ZenTracer中,我们可以看到如下内容:
很明显,read、write函数就是我们想要的函数。
编写代码:jhexdump
java.net.SocketOutputStream的第一个参数是
[B
,表示字节数组,为了使输出的字节数组更加可视化,这里引用了Awakened的jhexdump()函数,如下面的代码清单所示:
function jhexdump(array, len) {
var ptr = Memory.alloc(len);
for (var i = 0; i < len; ++i) Memory.writeS8(ptr.add(i), array[i]);
//console.log(hexdump(ptr, { offset: off, length: len, header: false, ansi: false }));
console.log(
hexdump(ptr, {
offset: 0,
length: len,
header: false,
ansi: false,
})
);
}
这部分代码主要是将Java层的byte数组通Memory.writeS8()
函数存放至通过Memory.alloc()这个API手动开辟的内存区域中,再调用hexdump()这个API打印出相应字节的hexdump。
编写代码:hookSocket
代码实现对下面函数的hook
- java.net.SocketOutputStream.write(bytearray1, int1, int2)
- 第三个参数int2,表示数组的长度
- java.net.SocketInputStream.read(bytearray1, int1, int2)
- 返回值表示读取的长度
- 对于过长的封包,会分多次读取。
function hookSocket() {
Java.perform(function () {
// java.net.SocketOutputStream.write
// java.net.SocketOutputStream.socketWrite
Java.use("java.net.SocketOutputStream").socketWrite.overload(
"[B",
"int",
"int"
).implementation = function (bytearray1, int1, int2) {
var result = this.socketWrite(bytearray1, int1, int2);
console.log(
"socketWrite result,bytearray1,int1,int2=>",
result,
bytearray1,
int1,
int2
);
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
jhexdump(bytearray1, int2);
return result;
};
// java.net.SocketInputStream.read
// java.net.SocketInputStream.socketRead0
Java.use("java.net.SocketInputStream").read.overload(
"[B",
"int",
"int"
).implementation = function (bytearray1, int1, int2) {
var result = this.read(bytearray1, int1, int2);
console.log(
"read result,bytearray1,int1,int2=>",
result,
bytearray1,
int1,
int2
);
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
//console.log('contents: => ', ByteString.of(bytearray1).hex())
jhexdump(bytearray1, result);
return result;
};
});
}
验证效果
请求封包,write打印结果:
响应封包,read打印结果:
4️⃣ HTTPS的Socket分析
ZenTracer分析
分析之前我们将APP中的URL由http://www.baidu.com改成
https://www.baidu.com,再次点击执行请求按钮。
ZenTracer将打印如下内容,可见com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write
就是我们需要的写函数
相对的,我们可以在ZenTracer的控制台中搜素read
,可以找到读取的函数com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read
编写代码:
代码实现与HTTP的类似,不做过多解释,代码如下:
function hookSSLSocketAndroid() {
Java.perform(function () {
// com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write
Java.use(
"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream"
).write.overload("[B", "int", "int").implementation = function (
bytearray1,
int1,
int2
) {
var result = this.write(bytearray1, int1, int2);
console.log(
"write result,bytearray1,int1,int2=>",
result,
bytearray1,
int1,
int2
);
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log("contents: => ", ByteString.of(bytearray1).hex());
return result;
};
// com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read
Java.use(
"com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream"
).read.overload("[B", "int", "int").implementation = function (
bytearray1,
int1,
int2
) {
var result = this.read(bytearray1, int1, int2);
console.log(
"read result,bytearray1,int1,int2=>",
result,
bytearray1,
int1,
int2
);
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
//console.log('contents: => ', ByteString.of(bytearray1).hex())
jhexdump(bytearray1, result);
return result;
};
});
}
验证效果
打印结果与HTTP类似,不再贴图了。
5️⃣ 验证HTTPS请求流程
至此,Socket的Hook代码已经写完,反过来,我们通过hook相关函数,可以定位到APP的核心代码,下面我们以HTTPS为例,定位APP调用函数。
objection测试
- objection连接APP:
objection -g com.roysue.httpurlconnectiondemo explore
- hook关键函数、打印堆栈
android hooking watch class_method com.android.org.conscrypt.ConscryptFileDescrip torSocket$SSLOutputStream.write --dump-args --dump-return --dump-backtrace
- 点击请求,查看打印结果
如下图,我们可以看出,请求从run
函数开始,调用ezReq
,然后执行一系列函数,最终发送出去的。
🛬 文章小结
使用ZenTracer过程中,会出现崩溃的情况,这时候可以:
多试几次
;- 或者根据已有的输出结果,调整删选条件再次尝试。
- 或者通过objection的
-c
命令调整输入结果进行尝试。
关于Socket请求,有很多实现方式,大厂可能会自实现网络请求,避开Socket调用。也有的直接调用so层实现网络请求。
📖 参考资料
- frida 常见问题和报错https://crifan.github.io/reverse_debug_frida/website/summary_note/common_issue/
- objection地址:https://github.com/sensepost/objection
ps: 文章中内容仅用于技术交流,请勿用于违规违法行为。