Frida Hook Android APP笔记(三)

在Github上发现了一个有用的资料:

  • 一个系统性讲解Frida用法和配置方法的Frida系列文章,在笔记(二)中的两篇Frida Java Hook详解也是出自这里。

还是用之前的app实验,这次尝试分析Android自带的HttpURLConnection

用法:

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
// GET
try {
String url = "https://www.baidu.com/";
URL url = new URL(url);
//得到connection对象。
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//设置请求方式
connection.setRequestMethod("GET");
//连接
connection.connect();
//得到响应码
int responseCode = connection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
//得到响应流
InputStream inputStream = connection.getInputStream();
//将响应流转换成字符串
String result = is2String(inputStream);//将流转换为字符串。
}

} catch (Exception e) {
e.printStackTrace();
}
// POST
try {
URL url = new URL(getUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.connect();

String body = "userName=zhangsan&password=123456";
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(), "UTF-8"));
writer.write(body);
writer.close();

int responseCode = connection.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
InputStream inputStream = connection.getInputStream();
String result = is2String(inputStream);//将流转换为字符串。
}

} catch (Exception e) {
e.printStackTrace();
}

可以看到关键点在HttpURLConnection类,请求的属性和输入输出流都在这个类的对象中配置。

如果直接hook HttpURLConnection类的方法,会发现没有任何东西。

1
2
3
4
5
6
var httpUrlConnection = Java.use('java.net.HttpURLConnection');
httpUrlConnection.getInputStream.implementation = function(){
var result = this.getInputStream();
console.log("java.net.HttpURLConnection -> " + readInputStream(result));
return result;
};

打开源码,发现HttpURLConnection为抽象类,在源码中搜索extends HttpURLConnection,找到其实现类:com.android.okhttp.internal.huc.HttpURLConnectionImpl

(搜索源码:https://cs.android.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var HttpURLConnectionImpl = Java.use('com.android.okhttp.internal.huc.HttpURLConnectionImpl');
var connect_http = HttpURLConnectionImpl.connect;
connect_http.overload().implementation = function () {
console.log('Hooked HttpURLConnectionImpl->connect()');
connect_http.call(this);
};
HttpURLConnectionImpl.setRequestProperty.implementation = function(name,value){
console.log("HttpURLConnectionImpl.setRequestProperty => ",name,": ",value);
return this.setRequestProperty(name,value);
}
HttpURLConnectionImpl.setRequestMethod.implementation = function(type){
console.log("HttpURLConnectionImpl.setRequestMethod : ",type);
return this.setRequestMethod(type);
}
HttpURLConnectionImpl.responseSourceHeader.implementation = function(response){
var result = this.responseSourceHeader(response);
console.log("HttpURLConnectionImpl.responseSourceHeader : " + result);
return result;
};

结果如图:


https请求的实现在com.android.okhttp.internal.huc.HttpsURLConnectionImpl, hook逻辑与HttpURLConnectionImpl相同,把类替换掉就可以了。这样以来代码中出现了大量重复,看下能不能将https请求和http请求的hook合并。

分析源码我们可以发现,HttpsURLConnectionImpl继承了抽象类DelegatingHttpsURLConnection,而DelegatingHttpsURLConnection继承自HttpsURLConnection,再往下,HttpsURLConnection又继承自HttpURLConnection

打印下调用栈,看https请求的具体实现是否使用了com.android.okhttp.internal.huc.HttpURLConnectionImpl的相关接口。

打印调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function printStack() {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\r\n");
console.log("=============================Stack strat=======================");
console.log(replaceStr);
console.log("=============================Stack end=======================\r\n");
Exception.$dispose();
}
});
}

1
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

如图,可以看到HttpURLConnectionImpl.setRequestProperty()方法最终调用了HttpURLConnectionImpl.setRequestProperty

可以直接简化掉一半代码。


上文中hook到的都是请求的相关配置,通信的数据是由InputStreamOutputStream数据流传递。connection.getOutputStream()向服务端发送数据,connection.getInputStream()从服务端接收数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var getInputStream_http = HttpURLConnectionImpl.getInputStream;
var inputStream_http = null;
getInputStream_http.overload().implementation = function () {
var returnStream = getInputStream_http.call(this);
inputStream_http = returnStream;
var inputStr = readInputStream(returnStream);
console.log("HttpURLConnectionImpl->getInputStream(): " + inputStr);
return returnStream;
};

var getOutputStream_http = HttpURLConnectionImpl.getOutputStream;
getOutputStream_http.overload().implementation = function ()
var returnStream = getOutputStream_http.call(this);
return returnStream;
};

由于var returnStream = getInputStream_http.call(this);得到的只是一个InputStream数据流对象,需要解析InputStream为String:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function readInputStream(inputStream){
var str = '';
if(inputStream == null){
console.log("inputStream is null");
return str;
}
try{
var inputStreamReader = Java.use('java.io.InputStreamReader').$new(inputStream);
var bufferedReader = Java.use('java.io.BufferedReader').$new(inputStreamReader);
var response = Java.use('java.lang.StringBuffer').$new();
var line = null;
while((line = bufferedReader.readLine()) != null){
response.append(line);
}
str = response;
}catch(error){
console.error( "inputstream error: " + error);
return null;
}
return str;
}

结果如下图:

部分数据还存在乱码问题,后面需要想办法解决这个问题。