@zsa233/frida-analykit-agent 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/net/ssl.ts ADDED
@@ -0,0 +1,360 @@
1
+
2
+ import { Config, setGlobalProperties } from "../config.js"
3
+ import { RPCMsgType } from "../message.js"
4
+ import { arrayBuffer2Hex, unwrapArgs } from "../utils/utils.js"
5
+ import { ssl_st_structOf, SSL3_RANDOM_SIZE } from "./struct.js"
6
+ import { help, NativePointerObject, ProgressNotify } from "../helper.js"
7
+ import { Libssl } from "../lib/libssl.js"
8
+ import { FuncHelper } from "../func.js"
9
+ import { ElfModuleX } from "../elf/module.js"
10
+ import { AdrlXref } from "../elf/xref.js"
11
+ import { TextEncoder } from "../utils/text_endec.js"
12
+ import { Subroutine } from "../elf/verifier.js"
13
+
14
+
15
+ type SSL3_STATE = {
16
+ read_sequence: ArrayBuffer
17
+ cwrite_sequence: ArrayBuffer
18
+ server_random: ArrayBuffer
19
+ client_random: ArrayBuffer
20
+ }
21
+
22
+
23
+ interface SSLField {
24
+ method: NativePointer
25
+ config: NativePointer
26
+ version: number
27
+ max_send_fragment: number
28
+ rbio: NativePointer
29
+ wbio: NativePointer
30
+ do_handshake: NativePointer
31
+ s3: SSL3_STATE
32
+ d1: NativePointer
33
+ msg_callback: NativePointer
34
+ msg_callback_arg: NativePointer
35
+ initial_timeout_duration_ms: number
36
+ session: NativePointer
37
+ info_callback: NativePointer
38
+ ctx: NativePointer
39
+ session_ctx: NativePointer
40
+ ex_data: NativePointer
41
+ }
42
+
43
+
44
+ interface SSL extends SSLField { }
45
+
46
+ const HEX_TABLE = '0123456789abcdef'
47
+
48
+
49
+
50
+ export type SSLSecretLog = {
51
+ label: string
52
+ client_random: string
53
+ secret: string
54
+ }
55
+
56
+ class SSL extends NativePointerObject {
57
+ constructor(handle: NativePointer) {
58
+ super(handle)
59
+ for (let [field, offseter] of Object.entries(Process.pointerSize === 8 ? ssl_st_structOf.B64 : {})) {
60
+ Object.defineProperty(this, field, {
61
+ value: offseter(this.$handle),
62
+ writable: false,
63
+ enumerable: true,
64
+ })
65
+ }
66
+ }
67
+
68
+
69
+ static cbb_hex(bytes: ArrayBuffer, in_len: number): string {
70
+ const bs = new Uint8Array(bytes)
71
+ const hex_list: string[] = []
72
+ for (let i = 0; i < in_len; i++) {
73
+ hex_list.push(HEX_TABLE[bs[i] >> 4] + HEX_TABLE[bs[i] & 0xf])
74
+ }
75
+ return hex_list.join('')
76
+ }
77
+
78
+ secretlog(label: string, secret: ArrayBuffer, secret_len: number): SSLSecretLog {
79
+ return {
80
+ label,
81
+ client_random: SSL.cbb_hex(this.s3.client_random, SSL3_RANDOM_SIZE),
82
+ secret: SSL.cbb_hex(secret, secret_len),
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+
89
+
90
+ function sendSSLSecret(tag: string, data: { label: string, client_random: string, secret: string }){
91
+ if (Config.OnRPC) {
92
+ help.$send({
93
+ type: RPCMsgType.SSL_SECRET,
94
+ data: {
95
+ tag: tag,
96
+ ...data
97
+ }
98
+ })
99
+ } else {
100
+ const file = help.getLogfile(tag, 'a')
101
+ file.writeLine(`${data.label} ${data.client_random} ${data.secret}`)
102
+ file.flush()
103
+ }
104
+ }
105
+
106
+
107
+
108
+
109
+
110
+
111
+ export class BoringSSL {
112
+ private mod: ElfModuleX | Module
113
+ constructor(mod: ElfModuleX | Module) {
114
+ this.mod = mod
115
+ }
116
+
117
+
118
+ static loadFromModule(mod: ElfModuleX | Module){
119
+ return new BoringSSL(mod)
120
+ }
121
+
122
+ scanKeylogFunc(
123
+ fullScan: boolean = false,
124
+ verifier: (p: NativePointer) => NativePointer | null = this.verifyKeylogFunc,
125
+ ): NativePointer[] {
126
+ const prog = new ProgressNotify('BoringSSL::scanKeylogFunc')
127
+
128
+ const mod = this.mod
129
+ const tryGetSegment = (name: string) => {
130
+ if (mod instanceof ElfModuleX) {
131
+ return mod.getSegment(name)
132
+ }
133
+ return null
134
+ }
135
+ const targetString = 'CLIENT_RANDOM'
136
+ const enc = new TextEncoder()
137
+ const strBuff = enc.encode(targetString).buffer as ArrayBuffer
138
+ const scanPattern = arrayBuffer2Hex(strBuff) + " 00"
139
+ const targetScanRange = fullScan ? mod : (tryGetSegment('.rodata') || mod)
140
+
141
+ prog.notify({
142
+ intro: `开始扫描特征[${targetString}]`,
143
+ modx_name: mod.name,
144
+ modx_base: mod.base,
145
+ modx_size: mod.size,
146
+
147
+ scan_base: (targetScanRange ? targetScanRange : mod).base,
148
+ scan_size: (targetScanRange ? targetScanRange : mod).size,
149
+ scan_string: targetString,
150
+ scan_pattern: scanPattern,
151
+
152
+ next: 'help.scanMemory'
153
+ })
154
+
155
+ // 特征string 扫描
156
+ const stringTargets = help.scanMemory(
157
+ targetScanRange ? targetScanRange : mod,
158
+ scanPattern,
159
+ { limit: 0x10000 }
160
+ )
161
+
162
+ prog.notify({
163
+ intro: `捕获目标数[${stringTargets.length}]`,
164
+
165
+ targets: stringTargets.map(v => v.address),
166
+ })
167
+ if (!stringTargets.length) {
168
+ return []
169
+ }
170
+
171
+ prog.notify({
172
+ targets: stringTargets.map(v => v.address),
173
+ })
174
+
175
+ const guessBLs = []
176
+
177
+ for (const target of stringTargets) {
178
+ const adrlScanRange = fullScan ? mod : (tryGetSegment('.text') || mod)
179
+ prog.notify({
180
+ target: target.address,
181
+ scan_base: adrlScanRange.base,
182
+ scan_size: adrlScanRange.size,
183
+
184
+ next: 'AdrlXref::scanAdrl'
185
+ })
186
+
187
+ // adrl 目标引用扫描
188
+ const xref = new AdrlXref(target.address)
189
+ const adrlResults = xref.scanAdrl(adrlScanRange)
190
+
191
+ prog.notify({
192
+ intro: `特征[${target.address.sub(mod.base)}]引用关联数[${adrlResults.length}]`,
193
+
194
+ results: adrlResults,
195
+ })
196
+
197
+ for(const adrl of adrlResults) {
198
+ const bls = []
199
+ // bl 函数调用(只考虑第一次bl
200
+ const bl = adrl.scanBL()
201
+ if(bl) {
202
+ guessBLs.push(bl)
203
+ bls.push(bl)
204
+ }
205
+ prog.notify({
206
+ adrl: adrl,
207
+ bls: bls,
208
+ })
209
+ }
210
+ }
211
+
212
+ const guessFuncs = []
213
+ for(const target of guessBLs) {
214
+ const funcPtr = ptr(target.insn.operands[0].value.toString())
215
+ guessFuncs.push(funcPtr)
216
+ prog.notify({
217
+ intro: `从[${target.src.$handle.sub(mod.base)}]猜测目标函数[${funcPtr.sub(mod.base)}]`,
218
+
219
+ bl: target,
220
+ func_ptr: funcPtr,
221
+ })
222
+ }
223
+
224
+ // 进一步验证目标函数
225
+ return guessFuncs.reduce<NativePointer[]>((acc, v) => {
226
+ const p = verifier(v)
227
+ if(p) {
228
+ acc.push(p)
229
+ }
230
+ return acc
231
+ }, [])
232
+ }
233
+
234
+ verifyKeylogFunc(p: NativePointer): NativePointer | null {
235
+ const subroutine = Subroutine.loadFromPointer(p)
236
+ // thunk/tail call
237
+ const verifyResult = subroutine.scoreThunk()
238
+ if (verifyResult.score > 50 && verifyResult.eoi) {
239
+ const bInstr = verifyResult.eoi
240
+ switch (bInstr.mnemonic) {
241
+ case 'b':
242
+ const target = bInstr.operands[0]
243
+ return ptr(target.value.toString())
244
+ case 'br':
245
+ // TODO:
246
+ break
247
+ }
248
+ }
249
+ return p
250
+ }
251
+
252
+
253
+ }
254
+
255
+
256
+
257
+ class SSLTools extends NativePointerObject {
258
+ private static _libssl_hook: boolean = false
259
+
260
+ static newConsumer(tag: string = 'sslkey.log'): SSLSecretCallbackConsumer {
261
+ return new SSLSecretCallbackConsumer(tag)
262
+ }
263
+
264
+ static attachLibsslKeylogFunc(tag: string = 'sslkey.log'){
265
+ if (this._libssl_hook) {
266
+ return true
267
+ }
268
+ const handle = Libssl.SSL_new.$handle
269
+ if (!handle || handle.isNull()) {
270
+ return false
271
+ }
272
+ this._libssl_hook = true
273
+ Interceptor.attach(handle,{
274
+ onEnter(args) {
275
+ const [ctx] = unwrapArgs(args, 1)
276
+ this.ssl_ctx = ctx
277
+ },
278
+ onLeave(retval){
279
+ const ctx = this.ssl_ctx
280
+ Libssl.SSL_CTX_set_keylog_callback(ctx, FuncHelper.SSL_CTX_keylog_callback(
281
+ Libssl.SSL_CTX_get_keylog_callback(ctx),
282
+ (impl: NativeFunction<NativePointer, Array<NativePointer>>, ssl: NativePointer, line: NativePointer) => {
283
+ const str = line.readCString()
284
+ if (str !== null) {
285
+ const sep_list = str.split(' ')
286
+ if(sep_list.length !== 3) {
287
+ printErr(`[attachLogSecret] error to parse secret_log[${str}]`)
288
+ }else{
289
+ const [label, client_random, secret] = sep_list
290
+ sendSSLSecret(tag, { label, client_random, secret })
291
+ }
292
+ }
293
+ if(!impl.isNull()) {
294
+ impl(ssl, line)
295
+ }
296
+ }
297
+ ))
298
+ }
299
+ })
300
+ return true
301
+ }
302
+
303
+ static attachBoringsslKeylogFunc(options: { mod?: ElfModuleX | Module, libname?: string }){
304
+ let { mod, libname } = options
305
+ if(!mod && !libname) {
306
+ throw new Error(`[attachBoringssl] mod和libname必须要指定一个`)
307
+ }
308
+ const prog = new ProgressNotify('SSLTools::attachBoringsslKeylogFunc')
309
+ if(!mod) {
310
+ mod = Process.getModuleByName(libname!)
311
+ }
312
+ const bor = new BoringSSL(mod)
313
+ const guessList = bor.scanKeylogFunc()
314
+ if(guessList.length != 1) {
315
+ throw new Error(`[attachBoringssl] 扫到的目标不存在或多个[${guessList.length}], 不执行attach。`)
316
+ }
317
+ Interceptor.attach(guessList[0], SSLTools.newConsumer('sslkey.log').Handler())
318
+ prog.log(mod!.name, `ssl_log_secret: ${guessList[0].sub(mod!.base)}`)
319
+ }
320
+
321
+
322
+ }
323
+
324
+
325
+ export class SSLSecretCallbackConsumer {
326
+ private tag: string
327
+
328
+ constructor(tag: string) {
329
+ this.tag = tag
330
+ }
331
+
332
+ Handler(): ScriptInvocationListenerCallbacks {
333
+ const that = this
334
+ return {
335
+ onEnter(args: InvocationArguments): void {
336
+ const [ssl, label, secret, secret_len] = unwrapArgs(args, 4)
337
+ const handle = new SSL(ssl)
338
+ const len = secret_len.toUInt32()
339
+ const data = handle.secretlog(label.readCString(), secret.readByteArray(len), len)
340
+ sendSSLSecret(that.tag, data)
341
+ },
342
+ onLeave(retval: InvocationReturnValue): void {
343
+
344
+ }
345
+ }
346
+ }
347
+
348
+ }
349
+
350
+
351
+ export { SSLTools }
352
+
353
+
354
+
355
+
356
+
357
+ setGlobalProperties({
358
+ 'SSLTools': SSLTools,
359
+ 'BoringSSL': BoringSSL,
360
+ })
@@ -0,0 +1,51 @@
1
+
2
+
3
+ import {
4
+ readByteArray, binaryReadPointer,
5
+ binaryPointer, binaryReadPointerStruct,
6
+ binaryReadU8, binaryReadU16,
7
+ binaryReadU32, binaryReadS32,
8
+ binaryReadU64, binaryReadS64,
9
+ } from "../utils/utils.js"
10
+
11
+ export const SSL3_RANDOM_SIZE = 32
12
+
13
+ export const bssl_SSL3_STATE_structOf = {
14
+ B64: {
15
+ read_sequence: readByteArray(0, 8),
16
+ cwrite_sequence: readByteArray(8, 8),
17
+ server_random: readByteArray(16, SSL3_RANDOM_SIZE),
18
+ client_random: readByteArray(16 + SSL3_RANDOM_SIZE, SSL3_RANDOM_SIZE),
19
+ // ...
20
+ },
21
+ // B32
22
+ B32: {}
23
+ }
24
+
25
+
26
+ export const ssl_st_structOf = {
27
+ B64: {
28
+ method: binaryReadPointer(0),
29
+ config: binaryReadPointer(8),
30
+ version: binaryReadU16(16),
31
+ max_send_fragment: binaryReadU16(18),
32
+ rbio: binaryReadPointer(24),
33
+ wbio: binaryReadPointer(32),
34
+ do_handshake: binaryReadPointer(40),
35
+ s3: binaryReadPointerStruct(48, bssl_SSL3_STATE_structOf),
36
+ d1: binaryReadPointer(56),
37
+ msg_callback: binaryReadPointer(64),
38
+ msg_callback_arg: binaryReadPointer(72),
39
+ initial_timeout_duration_ms: binaryReadU32(80),
40
+ session: binaryReadPointer(88),
41
+ info_callback: binaryReadPointer(96),
42
+ ctx: binaryReadPointer(104),
43
+ session_ctx: binaryReadPointer(112),
44
+ ex_data: binaryReadPointer(120),
45
+ // ...
46
+ },
47
+ // B32
48
+ B32: {}
49
+ }
50
+
51
+
File without changes
package/src/process.ts ADDED
@@ -0,0 +1,137 @@
1
+ import { help } from "./helper.js";
2
+
3
+
4
+ // frida-gum/index.d.ts
5
+ interface RangeDetails {
6
+ /**
7
+ * Base address.
8
+ */
9
+ base: NativePointer;
10
+
11
+ /**
12
+ * Size in bytes.
13
+ */
14
+ size: number;
15
+
16
+ /**
17
+ * Protection.
18
+ */
19
+ protection: PageProtection;
20
+
21
+ /**
22
+ * File mapping details, if available.
23
+ */
24
+ file?: FileMapping | undefined;
25
+
26
+ }
27
+
28
+
29
+ export class ProcMapItem {
30
+ start_page: NativePointer
31
+ end_page: NativePointer
32
+ prots: string
33
+ offset: number
34
+ main_dev: string
35
+ slave_dev: string
36
+ inode: number
37
+ pathname: string
38
+
39
+ constructor(
40
+ start_page: string,
41
+ end_page: string,
42
+ prots: string,
43
+ offset: string,
44
+ main_dev: string,
45
+ slave_dev: string,
46
+ inode: string,
47
+ pathname: string,
48
+ ) {
49
+ this.start_page = ptr(parseInt(start_page, 16))
50
+ this.end_page = ptr(parseInt(end_page, 16))
51
+ this.prots = prots
52
+ this.offset = parseInt(offset, 16)
53
+ this.main_dev = main_dev
54
+ this.slave_dev = slave_dev
55
+ this.inode = parseInt(inode)
56
+ this.pathname = pathname.trim()
57
+ }
58
+
59
+ toString(): string {
60
+ return `${this.start_page.toString(16)}-${this.end_page.toString(16)} ${this.prots} ${this.offset.toString(16)} ${this.main_dev}:${this.slave_dev} ${this.inode} ${this.pathname}`
61
+ }
62
+ }
63
+
64
+ const REGEXP_PROC_MAPS_LINE = /^([a-fA-F0-9]+)-([a-fA-F0-9]+)\s+([rwx\-p]+)\s+(\w+)\s+(\w+):(\d+)\s+(\d+)\s+(.*)$/
65
+
66
+ export class ProcMap {
67
+ public text: string
68
+ public items: ProcMapItem[]
69
+
70
+ constructor(text: string) {
71
+ this.text = text
72
+ this.items = []
73
+ const lines = text.split('\n')
74
+ for (const line of lines) {
75
+ if (!line.trim()) continue
76
+ const result = ProcMap.parseLine(line)
77
+ if (!result || result.length !== 8) continue
78
+ const item = new ProcMapItem(
79
+ ...result as [string, string, string, string, string, string, string, string]
80
+ )
81
+ this.items.push(item)
82
+ }
83
+ }
84
+
85
+ static parseLine(line: string): string[] | null {
86
+ const m = REGEXP_PROC_MAPS_LINE.exec(line)
87
+ if (!m) return null
88
+ return m.slice(1)
89
+ }
90
+
91
+ find(start_addr: NativePointer, end_addr: NativePointer): ProcMapItem[] {
92
+ return this.items.filter(item => {
93
+ return !(item.start_page >= end_addr || item.end_page <= start_addr)
94
+ })
95
+ }
96
+ }
97
+
98
+
99
+
100
+ class Proc {
101
+ private static _mapCache: RangeDetails[] = []
102
+
103
+
104
+ static findMapCache(addr: NativePointer): RangeDetails | null {
105
+ const result = this._mapCache.find((v, i) => {
106
+ return addr >= v.base && addr < v.base.add(v.size)
107
+ })
108
+ if (result) {
109
+ return result || null
110
+ }
111
+ const range = Process.findRangeByAddress(addr)
112
+ if(!range) {
113
+ return null
114
+ }
115
+ let hitIndex = -1
116
+ const hit = this._mapCache.find((v, i) => {
117
+ const ok = v.base == range.base
118
+ if (ok) hitIndex = i
119
+ return ok
120
+ })
121
+ if (hitIndex !== -1) {
122
+ this._mapCache[hitIndex] = range
123
+ }else{
124
+ this._mapCache.push(range)
125
+ }
126
+ return range
127
+ }
128
+
129
+
130
+ static loadProcMap(pid: number | string = 'self'): ProcMap{
131
+ return new ProcMap(help.readProcMaps(pid))
132
+ }
133
+
134
+ }
135
+
136
+
137
+ export { Proc as proc }