forked from httptoolkit/frida-interception-and-unpinning
-
Notifications
You must be signed in to change notification settings - Fork 4
/
config.js
199 lines (164 loc) · 6.94 KB
/
config.js
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
/**************************************************************************************************
*
* This file defines various config parameters, used later within the other scripts.
*
* In all cases, you'll want to set CERT_PEM and likely PROXY_HOST and PROXY_PORT.
*
* Source available at https://github.com/httptoolkit/frida-interception-and-unpinning/
* SPDX-License-Identifier: AGPL-3.0-or-later
* SPDX-FileCopyrightText: Tim Perry <[email protected]>
*
*************************************************************************************************/
// Put your CA certificate data here in PEM format:
const CERT_PEM = `-----BEGIN CERTIFICATE-----
[!! Put your CA certificate data here, in PEM format !!]
-----END CERTIFICATE-----`;
// Put your intercepting proxy's address here:
const PROXY_HOST = '127.0.0.1';
const PROXY_PORT = 8000;
// If you like, set to to true to enable extra logging:
const DEBUG_MODE = false;
// If you find issues with non-HTTP traffic being captured (due to the
// native connect hook script) you can add ports here to exempt traffic
// on that port from being redirected. Note that this will only affect
// traffic captured by the raw connection hook - for apps using the
// system HTTP proxy settings, traffic on these ports will still be
// sent via the proxy and intercepted despite this setting.
const IGNORED_NON_HTTP_PORTS = [];
// ----------------------------------------------------------------------------
// You don't need to modify any of the below, it just checks and applies some
// of the configuration that you've entered above.
// ----------------------------------------------------------------------------
if (DEBUG_MODE) {
// Add logging just for clean output & to separate reloads:
console.log('\n*** Starting scripts ***');
if (Java.available) {
Java.perform(() => {
setTimeout(() => console.log('*** Scripts completed ***\n'), 5);
// (We assume that nothing else will take more than 5ms, but app startup
// probably will, so this should separate script & runtime logs)
});
} else {
setTimeout(() => console.log('*** Scripts completed ***\n'), 5);
// (We assume that nothing else will take more than 5ms, but app startup
// probably will, so this should separate script & runtime logs)
}
} else {
console.log(''); // Add just a single newline, for minimal clarity
}
// Check the certificate (without literally including the instruction phrasing
// here, as that can be confusing for some users):
if (CERT_PEM.match(/\[!!.* CA certificate data .* !!\]/)) {
throw new Error('No certificate was provided' +
'\n\n' +
'You need to set CERT_PEM in the Frida config script ' +
'to the contents of your CA certificate.'
);
}
// ----------------------------------------------------------------------------
// Don't modify any of the below unless you know what you're doing!
// This section defines various utilities & calculates some constants which may
// be used by later scripts elsewhere in this project.
// ----------------------------------------------------------------------------
// As web atob & Node.js Buffer aren't available, we need to reimplement base64 decoding
// in pure JS. This is a quick rough implementation without much error handling etc!
// Base64 character set (plus padding character =) and lookup:
const BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const BASE64_LOOKUP = new Uint8Array(123);
for (let i = 0; i < BASE64_CHARS.length; i++) {
BASE64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i;
}
/**
* Take a base64 string, and return the raw bytes
* @param {string} input
* @returns Uint8Array
*/
function decodeBase64(input) {
// Calculate the length of the output buffer based on padding:
let outputLength = Math.floor((input.length * 3) / 4);
if (input[input.length - 1] === '=') outputLength--;
if (input[input.length - 2] === '=') outputLength--;
const output = new Uint8Array(outputLength);
let outputPos = 0;
// Process each 4-character block:
for (let i = 0; i < input.length; i += 4) {
const a = BASE64_LOOKUP[input.charCodeAt(i)];
const b = BASE64_LOOKUP[input.charCodeAt(i + 1)];
const c = BASE64_LOOKUP[input.charCodeAt(i + 2)];
const d = BASE64_LOOKUP[input.charCodeAt(i + 3)];
// Assemble into 3 bytes:
const chunk = (a << 18) | (b << 12) | (c << 6) | d;
// Add each byte to the output buffer, unless it's padding:
output[outputPos++] = (chunk >> 16) & 0xff;
if (input.charCodeAt(i + 2) !== 61) output[outputPos++] = (chunk >> 8) & 0xff;
if (input.charCodeAt(i + 3) !== 61) output[outputPos++] = chunk & 0xff;
}
return output;
}
/**
* Take a single-certificate PEM string, and return the raw DER bytes
* @param {string} input
* @returns Uint8Array
*/
function pemToDer(input) {
const pemLines = input.split('\n');
if (
pemLines[0] !== '-----BEGIN CERTIFICATE-----' ||
pemLines[pemLines.length- 1] !== '-----END CERTIFICATE-----'
) {
throw new Error(
'Your certificate should be in PEM format, starting & ending ' +
'with a BEGIN CERTIFICATE & END CERTIFICATE header/footer'
);
}
const base64Data = pemLines.slice(1, -1).map(l => l.trim()).join('');
if ([...base64Data].some(c => !BASE64_CHARS.includes(c))) {
throw new Error(
'Your certificate should be in PEM format, containing only ' +
'base64 data between a BEGIN & END CERTIFICATE header/footer'
);
}
return decodeBase64(base64Data);
}
const CERT_DER = pemToDer(CERT_PEM);
function waitForModule(moduleName, callback) {
if (Array.isArray(moduleName)) {
moduleName.forEach(module => waitForModule(module, callback));
}
try {
Module.ensureInitialized(moduleName);
callback(moduleName);
return;
} catch (e) {
try {
Module.load(moduleName);
callback(moduleName);
return;
} catch (e) {}
}
MODULE_LOAD_CALLBACKS[moduleName] = callback;
}
const getModuleName = (nameOrPath) => {
const endOfPath = nameOrPath.lastIndexOf('/');
return nameOrPath.slice(endOfPath + 1);
};
const MODULE_LOAD_CALLBACKS = {};
new ApiResolver('module').enumerateMatches('exports:linker*!*dlopen*').forEach((dlopen) => {
Interceptor.attach(dlopen.address, {
onEnter(args) {
const moduleArg = args[0].readCString();
if (moduleArg) {
this.moduleName = getModuleName(moduleArg);
}
},
onLeave() {
if (!this.moduleName) return;
Object.keys(MODULE_LOAD_CALLBACKS).forEach((key) => {
if (this.moduleName === key) {
MODULE_LOAD_CALLBACKS[key](this.moduleName);
delete MODULE_LOAD_CALLBACKS[key];
}
});
}
});
});