frida初探

First Post:

Last Update:

前情提要

该搞安卓app了

环境:某转回收的700块大米10pro
已通过magisk root


目标apk:

环境搭建

frida-server:

因为看到是某梆企业壳,直接就上魔改frida了
https://github.com/hzzheyang/strongR-frida-android/releases/download/16.5.6/hluda-server-16.5.6-android-arm64.gz
下载解压得到frida-server
用mt管理器扔进/data/local/tmp/目录

注:不需要自己编译,找牢版本直接用

frida-client:

on windows

1
pip3 install frida==16.5.6 frida-tools==13.0.0

注:frida版本必须与frida-server版本一致,frida-tools版本较高即可。无需与python版本对应。

shamiko:

某梆有root检测,该模块用来隐藏root
https://magiskcn.com/shamiko-install.html
跟着教程走即可

脱壳网站

https://56.al/

信息收集

mt管理器

使用mt管理器查壳

appinfo

使用appinfo查壳+敏感信息收集(此处未安装成功无法进行收集)

脱壳+jadx

https://56.al/
脱壳。下载后得到class1.dex和class2.dex,如果jadx打不开,可以将.dex改为.jar进行反编译。此处class2.dex无法打开所以改为.jar

抓包

先在电脑开热点:

瞅一眼电脑ip
然后在手机上设置代理

再在bp上配置好监听。yakit这一步在mitm上同样做(只是yakit的mitm只监听一个端口)

访问bp的监听服务下载证书。yakit在mitm处可以下载.pem格式的证书

在设置中的ca证书安装中安装证书就能抓到包了。yakit的证书是.pem格式,用在线网站转换成.der格式安装即可。

注:某些app的流量不走代理的话需要hook到发包方法才能获取到数据包。
https://www.lddgo.net/encrypt/cert-format-converter

hook

frida启动

将frida-server放到/data/local/tmp/目录,然后用adb shell启动

1
2
3
4
adb shell
su
cd /data/local/tmp/
./frida-server

在另一终端中:

1
2
3
4
5
frida-ps -Ua
PID Name Identifier
----- ------- ----------------------------------
23746 apk_name com.example.app

com.example.app即为frida注入的目标

过检测

过某梆参考以下文章,应该不是最新版的
https://blog.csdn.net/2503_90751938/article/details/146265892?spm=1001.2101.3001.6650.6&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6-146265892-blog-145873893.235%5Ev43%5Epc_blog_bottom_relevance_base7&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-6-146265892-blog-145873893.235%5Ev43%5Epc_blog_bottom_relevance_base7

最终代码(实际上该app无okhttp3模块)

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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
//frida -U -f com.example.app -l hook.js 
function hook_dlopen(so_name) {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(so_name) !== -1) {
this.match = true;
}
}
},
onLeave: function (retval) {
if (this.match) {
console.log(so_name, "加载成功");
var base = Module.findBaseAddress("libDexHelper.so");
patch_func_nop(base.add(322008));
patch_func_nop(base.add(308756));
patch_func_nop(base.add(350196));
patch_func_nop(base.add(372648));
//===============> libDexHelper.so 0x7548e639d8 322008 4e9d8
//===============> libDexHelper.so 0x7548e60614 308756 4b614
//===============> libDexHelper.so 0x7548e6a7f4 350196 557f4
//===============> libDexHelper.so 0x7548e6ffa8 372648 5afa8


}
}
});
}

function patch_func_nop(addr) {
Memory.patchCode(addr, 8, function (code) {
code.writeByteArray([0xE0, 0x03, 0x00, 0xAA]);
code.writeByteArray([0xC0, 0x03, 0x5F, 0xD6]);
console.log("patch code at " + addr);
});
}

hook_dlopen("libDexHelper.so");

function hajimi() {
var clone = Module.findExportByName('libc.so', 'clone');
Interceptor.attach(clone, {
onEnter: function (args) {
if (args[3] != 0) {
var addr = args[3].add(96).readPointer();
var so_name = Process.findModuleByAddress(addr).name;
var so_base = Module.getBaseAddress(so_name);
var offset = (addr - so_base);
console.log("===============>", so_name, addr, offset, offset.toString(16));
}
},
onLeave: function (retval) {}
});
}

hajimi(); // 这一部分不需要每次都执行,只在需要时调用

/*
* Android okhttp3 Traffic Interceptor
* Author: Vinay Kumar Rasala (Xplo8E)
* Organization: XYSec Labs (Appknox)
* Description: Intercepts network traffic using Frida for specified target hosts, logging API calls and WebView URL loads.
* Supported Libraries: okhttp3
*/

setImmediate(function() {
console.log("[*] Waiting for Traffic");
console.warn("[*] Please Check Target Hosts section in the code, if u dont see requests");

Java.perform(function() {
var okhttp3 = Java.use('okhttp3.OkHttpClient');
var webViewClient = Java.use('android.webkit.WebViewClient');

// ANSI escape code for red color
var redColor = '\u001b[31m';
// ANSI escape code for green color
var greenColor = '\u001b[32m';
// ANSI escape code to reset color
var resetColor = '\u001b[0m';

var targetHosts = [];
// Replace with your target hosts to set your scope
// eg: targetHosts = ["google.com", "frida.re", "github.com"]
// leave empty (eg: []) to print all requests


// Intercept API calls
var originalNewCall = okhttp3.newCall.overload('okhttp3.Request');
originalNewCall.implementation = function(request) {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
// Get the request's URL and extract the host
var requestUrl = request.url().toString();
var urlParts = requestUrl.split("/");
var extractedHost = urlParts[2]; // Assumes the host is at index 2
console.log("request url: ", requestUrl)
console.log("request parts: ", urlParts)
console.log("extractedHost: ", extractedHost)


if (targetHosts.includes(extractedHost) || targetHosts.length === 0) {

var requestEndpoint = requestUrl.replace(/^(https?:\/\/[^\/]+)(\/.*)$/, '$2');
// Construct and print request headers
var requestHeaders = request.headers();
console.log(redColor + "[API Call]" + resetColor);
console.log(" ");
console.log(greenColor + request.method() + " " + requestEndpoint);
// Add the Host header with the extracted host
requestHeaders = requestHeaders.newBuilder()
.add("Host", extractedHost)
.build();
// console.log(greenColor + "Request Headers:");
var requestHeaderNames = requestHeaders.names();
var requestHeaderNamesArray = requestHeaderNames.toArray();

for (var i = 0; i < requestHeaderNamesArray.length; i++) {
var headerName = requestHeaderNamesArray[i];
var headerValue = requestHeaders.get(headerName);
headerValue = decodeURIComponent(headerValue); // Decode header value
console.log(greenColor + headerName + ": " + headerValue + resetColor);
}
console.log(" ");
console.log(" ");


console.log(greenColor + requestBodyToString(request.body()) + resetColor);
console.log(redColor + "============================" + resetColor);

var newRequest = request.newBuilder().headers(requestHeaders).build();
var response = this.newCall(newRequest).execute();

// Construct and print response headers
console.log(redColor + "[API Response]" + " - [" + requestEndpoint + "]" + resetColor);
console.log(" ");
console.log(greenColor + response.code() + " " + response.message() + resetColor);
var responseHeaders = response.headers();
var responseHeaderNames = responseHeaders.names();
var responseHeaderNamesArray = responseHeaderNames.toArray();
for (var i = 0; i < responseHeaderNamesArray.length; i++) {
var responseHeaderName = responseHeaderNamesArray[i];
var responseHeaderValue = responseHeaders.get(responseHeaderName);
console.log(greenColor + responseHeaderName + ": " + responseHeaderValue + resetColor);
}
console.log(" ");

// console.log(greenColor + response.message());
var responseBody = response.body();
if (responseBody !== null) {
if (response.isSuccessful()) {
console.log(greenColor + responseBody.string() + resetColor + resetColor);
} else {
console.log(redColor + "Error: Response not successful" + resetColor);
}
} else {
console.log(greenColor + "Error: Empty response body" + resetColor);
}
console.log(redColor + "============================" + resetColor);
return this.newCall(request);

} else {
// Return a new Call instance for Frida to continue instrumenting
return this.newCall(request);
}
};

// Intercept WebView URL loads
var shouldOverrideUrlLoading = webViewClient.shouldOverrideUrlLoading.overload('android.webkit.WebView', 'java.lang.String');
shouldOverrideUrlLoading.implementation = function(view, url) {
console.log(redColor + "[WebView URL]: " + url + resetColor);
return shouldOverrideUrlLoading.call(this, view, url);
};
get_data();
});
});

function requestBodyToString(requestBody) {
if (requestBody === null) {
return '';
}

var buffer = Java.use('okio.Buffer').$new();
requestBody.writeTo(buffer);
return buffer.readUtf8();
}

const Color = {
Red: '\x1b[31m',
Green: '\x1b[32m',
Yellow: '\x1b[33m',
Blue: '\x1b[34m',
Reset: '\x1b[0m'
};

function colorLog(msg, opts) {
const c = opts?.c || Color.Reset;
console.log(c + msg + Color.Reset);
}

Java.perform(function() {
colorLog('\n----------DEEPLINK DATA MONITOR (GETDATA ENHANCED)------------', {c: Color.Yellow});

var Intent = Java.use('android.content.Intent');

// 1. Hook 涉及 Uri 的构造函数
if (Intent.$init) {
// 构造函数:Intent(String action, Uri uri)
Intent.$init.overload('java.lang.String', 'android.net.Uri').implementation = function(action, uri) {
const retval = this.$init(action, uri);
if (uri) {
colorLog('\n[+] Deeplink 初始化 (构造函数)', {c: Color.Green});
colorLog('------Intent 信息------', {c: Color.Blue});
console.log('[+] Action: ' + action);
console.log('[+] Deeplink Data: ' + uri.toString());
colorLog('-----------------------', {c: Color.Blue});
}
return retval;
};
}

// 2. Hook 设置 Data 的方法
Intent.setData.overload('android.net.Uri').implementation = function(uri) {
const retval = this.setData(uri);
if (uri) {
colorLog('\n[+] Deeplink 数据设置 (setData)', {c: Color.Red});
colorLog('------Intent 信息------', {c: Color.Blue});
console.log('[+] Action: ' + this.getAction());
console.log('[+] Deeplink Data: ' + uri.toString());
console.log('[+] Flags: ' + this.getFlags());
colorLog('-----------------------', {c: Color.Blue});
}
return retval;
};

// 3. Hook 设置带类型 Data 的方法
Intent.setDataAndType.overload('android.net.Uri', 'java.lang.String').implementation = function(uri, type) {
const retval = this.setDataAndType(uri, type);
if (uri) {
colorLog('\n[+] 带类型 Deeplink 数据设置 (setDataAndType)', {c: Color.Yellow});
colorLog('------Intent 信息------', {c: Color.Blue});
console.log('[+] Action: ' + this.getAction());
console.log('[+] Deeplink Data: ' + uri.toString());
console.log('[+] MIME Type: ' + type);
colorLog('-----------------------', {c: Color.Blue});
}
return retval;
};

// 4. 核心:Hook getData() 方法(获取 Uri 对象)
Intent.getData.implementation = function() {
const uri = this.getData(); // 调用原方法获取结果
if (uri) {
colorLog('\n[!] Deeplink 数据被获取 (getData)', {c: Color.Green});
colorLog('------获取到的 Uri 信息------', {c: Color.Blue});
console.log('[+] Uri 完整路径: ' + uri.toString());
console.log('[+] Scheme: ' + uri.getScheme()); // 协议(如 http、https、自定义 scheme)
console.log('[+] Host: ' + uri.getHost()); // 主机名
console.log('[+] Path: ' + uri.getPath()); // 路径
console.log('[+] Query: ' + uri.getQuery()); // 查询参数
colorLog('----------------------------', {c: Color.Blue});
}
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return uri; // 返回原结果,不影响正常逻辑
};

// 5. 核心:Hook getDataString() 方法(获取 Uri 字符串)
Intent.getDataString.implementation = function() {
const dataStr = this.getDataString(); // 调用原方法获取结果
if (dataStr) {
colorLog('\n[!] Deeplink 字符串被获取 (getDataString)', {c: Color.Red});
colorLog('------获取到的字符串信息------', {c: Color.Blue});
console.log('[+] Data String: ' + dataStr);
colorLog('------------------------------', {c: Color.Blue});
}
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
return dataStr; // 返回原结果,不影响正常逻辑
};
});

观察到能够正常执行frida命令,成功绕过frida-server检测