@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ZSA233
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @zsa233/frida-analykit-agent
2
+
3
+ Runtime helpers for custom Frida agents used by `frida-analykit`.
4
+
5
+ This package is designed to be consumed by `frida-compile` projects generated through `frida-analykit gen dev`.
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@zsa233/frida-analykit-agent",
3
+ "version": "2.0.0",
4
+ "description": "Frida agent runtime for frida-analykit",
5
+ "keywords": [
6
+ "frida",
7
+ "reverse-engineering",
8
+ "instrumentation",
9
+ "typescript"
10
+ ],
11
+ "homepage": "https://github.com/ZSA233/frida-analykit#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/ZSA233/frida-analykit/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ZSA233/frida-analykit.git",
18
+ "directory": "packages/frida-analykit-agent"
19
+ },
20
+ "type": "module",
21
+ "files": [
22
+ "src/**/*",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "exports": {
27
+ ".": "./src/index.ts",
28
+ "./rpc": "./src/rpc.ts",
29
+ "./*": "./src/*.ts"
30
+ },
31
+ "scripts": {
32
+ "check": "tsc -p tsconfig.json --noEmit"
33
+ },
34
+ "dependencies": {
35
+ "frida-java-bridge": "^7.0.8"
36
+ },
37
+ "devDependencies": {
38
+ "@types/frida-gum": "^18.7.2",
39
+ "typescript": "^5.8.3"
40
+ }
41
+ }
@@ -0,0 +1,80 @@
1
+
2
+
3
+
4
+ export type NP = NativePointer
5
+
6
+
7
+ export interface EnvJvmti {
8
+ handle: NP
9
+ vm: NP,
10
+ vtable: NP
11
+ }
12
+
13
+
14
+
15
+ // frida-java-bridge/lib/android.js
16
+ export interface VMApi {
17
+ vm: NP
18
+
19
+ module: Module
20
+
21
+ flavor: 'art' | 'dalvik'
22
+
23
+ addLocalRefrence: null
24
+
25
+ find(name: string): NativePointer, // export => symbol => null
26
+
27
+ artRuntime: NativePointer
28
+
29
+ artClassLinker: {
30
+ address: NativePointer,
31
+ quickResolutionTrampoline: NativePointer,
32
+ quickImtConflictTrampoline: NativePointer,
33
+ quickGenericJniTrampoline: NativePointer,
34
+ quickToInterpreterBridgeTrampoline: NativePointer,
35
+ }
36
+
37
+ jvmti: EnvJvmti
38
+
39
+ $new(size: number): NativePointer
40
+ $delete(pointer: NativePointer): void
41
+
42
+ // jint JNI_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen, jsize* nVMs);
43
+ JNI_GetCreateJavaVMs(vmBuf: NP, bufLen: number, nVMs: NP): number
44
+
45
+ // jobject JavaVMExt::AddGlobalRef(Thread* self, ObjPtr<mirror::Object> obj)
46
+ ['art::JavaVMExt::AddGlobalRef']: (vm: NP, self: NP, obj: NP) => NP
47
+
48
+ // void ReaderWriterMutex::ExclusiveLock(Thread* self)
49
+ ['art::ReaderWriterMutex::ExclusiveLock']: (lock: NP, self: NP) => void
50
+
51
+ // IndirectRef IndirectReferenceTable::Add(IRTSegmentState previous_state, ObjPtr<mirror:: Object> obj, std::string * error_msg)
52
+ ['art::IndirectReferenceTable::Add']: (table: NP, previous_state: NP, obj: number, error_msg: NP) => NP
53
+
54
+ // ObjPtr<mirror::Object> JavaVMExt::DecodeGlobal(IndirectRef ref)
55
+ // thread: 7 > Android >= 6
56
+ ['art::JavaVMExt::DecodeGlobal']: (vm: NP, thread: NP, ref: NP) => NP
57
+
58
+ // ObjPtr<mirror::Object> Thread::DecodeJObject(jobject obj) const
59
+ ['art::Thread::DecodeJObject']: (thread: NP, obj: NP) => NP
60
+
61
+
62
+ // TODO:
63
+ }
64
+
65
+
66
+
67
+
68
+ declare global {
69
+ namespace Java {
70
+ const api: VMApi,
71
+ Env: {
72
+ handle: NativePointer
73
+ vm: Java.VM & {
74
+ handle: NativePointer
75
+ }
76
+ throwIfExceptionPending(): Error
77
+ }
78
+ }
79
+
80
+ }
package/src/bridges.ts ADDED
@@ -0,0 +1,18 @@
1
+ import JavaBridge from "frida-java-bridge";
2
+
3
+ type BridgeGlobals = typeof globalThis & {
4
+ Java?: typeof JavaBridge;
5
+ ObjC?: unknown;
6
+ Swift?: unknown;
7
+ __FRIDA_ANALYKIT_CONFIG__?: Record<string, unknown>;
8
+ };
9
+
10
+ const globals = globalThis as BridgeGlobals;
11
+
12
+ export const Java = globals.Java ?? JavaBridge;
13
+ export const ObjC = globals.ObjC;
14
+ export const Swift = globals.Swift;
15
+
16
+ if (!("Java" in globals)) {
17
+ globals.Java = Java;
18
+ }
@@ -0,0 +1,81 @@
1
+ #include <glib.h>
2
+ #include <gum/gummemory.h>
3
+ #include <gum/gumdefs.h>
4
+
5
+ #define PAGE_SIZE 0x1000
6
+ #define GET_ADDR_PAGE(x) ((uintptr_t)(x) & ~(PAGE_SIZE - 1))
7
+ #define ADRP_IMM_LEN_MASK 0x1fffff
8
+ #define ADRP_FIXED28_24_BITSET_MASK (0b10000 << 24)
9
+ #define ADRP_PAGE_INSTR_MASK 0x9fffffe0
10
+
11
+ typedef struct _MemoryScanRes MemoryScanRes;
12
+ typedef struct _ScanUserData ScanUserData;
13
+
14
+ static gboolean on_match_fuzzy_adrp(GumAddress address, gsize size, gpointer user_data);
15
+
16
+ struct _ScanUserData
17
+ {
18
+ gpointer target_address;
19
+ gint align_offset;
20
+ };
21
+
22
+
23
+ struct _MemoryScanRes
24
+ {
25
+ GArray *results;
26
+ ScanUserData *user_data;
27
+ };
28
+
29
+ void _dispose(const MemoryScanRes *res)
30
+ {
31
+ g_array_free(res->results, TRUE);
32
+ }
33
+
34
+ static gboolean on_match_fuzzy_adrp(GumAddress address, gsize size, gpointer user_data)
35
+ {
36
+ MemoryScanRes *scan_res = (MemoryScanRes *)user_data;
37
+ const guintptr target_page = (guintptr)GET_ADDR_PAGE(scan_res->user_data->target_address);
38
+ const guintptr align_offset = (guintptr)scan_res->user_data->align_offset;
39
+ const guintptr addr_val = (guintptr)address - align_offset;
40
+ const gpointer pc_addr = (gpointer)addr_val;
41
+
42
+ // 4字节指令对齐
43
+ if ((addr_val & (sizeof(guint32) - 1)) != 0)
44
+ return TRUE;
45
+
46
+ // 按pc页差进行匹配
47
+ const guintptr pc_page = (guintptr)GET_ADDR_PAGE(address);
48
+ const guint32 page_delta = (guint32)((target_page - pc_page) >> 12) & ADRP_IMM_LEN_MASK;
49
+ const guint32 immlo = page_delta & 0b11;
50
+ const guint32 immhi = page_delta >> 2;
51
+ const guint32 op = 0x1;
52
+ const guint32 adrp_sign =
53
+ (op << 31) |
54
+ (immlo << 29) |
55
+ ADRP_FIXED28_24_BITSET_MASK |
56
+ (immhi << 5);
57
+
58
+ if (((*(guint32 *)pc_addr) & ADRP_PAGE_INSTR_MASK) != (adrp_sign & ADRP_PAGE_INSTR_MASK))
59
+ return TRUE;
60
+
61
+ g_array_append_val(scan_res->results, pc_addr);
62
+ return TRUE;
63
+ }
64
+
65
+ gpointer scan(const GumAddress base_address,
66
+ const gsize size,
67
+ const gchar *pattern_str,
68
+ MemoryScanRes *const scan_res)
69
+ {
70
+ if (scan_res == NULL)
71
+ return NULL;
72
+
73
+ scan_res->results = g_array_new(FALSE, FALSE, sizeof(gpointer));
74
+
75
+ const GumMemoryRange range = {base_address, size};
76
+ const GumMatchPattern *pattern = gum_match_pattern_new_from_string(pattern_str);
77
+
78
+ gum_memory_scan(&range, pattern, on_match_fuzzy_adrp, scan_res);
79
+
80
+ return scan_res->results;
81
+ }
@@ -0,0 +1,118 @@
1
+
2
+ import { nativeFunctionOptions } from "../consts.js"
3
+ import { CMemoryScanRes } from "../utils/scan.js"
4
+
5
+
6
+ const CM = new CModule(`
7
+ #include <glib.h>
8
+ #include <gum/gummemory.h>
9
+ #include <gum/gumdefs.h>
10
+
11
+ #define PAGE_SIZE 0x1000
12
+ #define GET_ADDR_PAGE(x) ((uintptr_t)(x) & ~(PAGE_SIZE - 1))
13
+ #define ADRP_IMM_LEN_MASK 0x1fffff
14
+ #define ADRP_FIXED28_24_BITSET_MASK (0b10000 << 24)
15
+ #define ADRP_PAGE_INSTR_MASK 0x9fffffe0
16
+
17
+ typedef struct _MemoryScanRes MemoryScanRes;
18
+ typedef struct _ScanUserData ScanUserData;
19
+
20
+ static gboolean on_match_fuzzy_adrp(GumAddress address, gsize size, gpointer user_data);
21
+
22
+ struct _ScanUserData
23
+ {
24
+ gpointer target_address;
25
+ gint align_offset;
26
+ };
27
+
28
+
29
+ struct _MemoryScanRes
30
+ {
31
+ GArray *results;
32
+ ScanUserData *user_data;
33
+ };
34
+
35
+ void _dispose(const MemoryScanRes *res)
36
+ {
37
+ g_array_free(res->results, TRUE);
38
+ }
39
+
40
+ static gboolean on_match_fuzzy_adrp(GumAddress address, gsize size, gpointer user_data)
41
+ {
42
+ MemoryScanRes *scan_res = (MemoryScanRes *)user_data;
43
+ const guintptr target_page = (guintptr)GET_ADDR_PAGE(scan_res->user_data->target_address);
44
+ const guintptr align_offset = (guintptr)scan_res->user_data->align_offset;
45
+ const guintptr addr_val = (guintptr)address - align_offset;
46
+ const gpointer pc_addr = (gpointer)addr_val;
47
+
48
+ // 4字节指令对齐
49
+ if ((addr_val & (sizeof(guint32) - 1)) != 0)
50
+ return TRUE;
51
+
52
+ // 按pc页差进行匹配
53
+ const guintptr pc_page = (guintptr)GET_ADDR_PAGE(address);
54
+ const guint32 page_delta = (guint32)((target_page - pc_page) >> 12) & ADRP_IMM_LEN_MASK;
55
+ const guint32 immlo = page_delta & 0b11;
56
+ const guint32 immhi = page_delta >> 2;
57
+ const guint32 op = 0x1;
58
+ const guint32 adrp_sign =
59
+ (op << 31) |
60
+ (immlo << 29) |
61
+ ADRP_FIXED28_24_BITSET_MASK |
62
+ (immhi << 5);
63
+
64
+ if (((*(guint32 *)pc_addr) & ADRP_PAGE_INSTR_MASK) != (adrp_sign & ADRP_PAGE_INSTR_MASK))
65
+ return TRUE;
66
+
67
+ g_array_append_val(scan_res->results, pc_addr);
68
+ return TRUE;
69
+ }
70
+
71
+ gpointer scan(const GumAddress base_address,
72
+ const gsize size,
73
+ const gchar *pattern_str,
74
+ MemoryScanRes *const scan_res)
75
+ {
76
+ if (scan_res == NULL)
77
+ return NULL;
78
+
79
+ scan_res->results = g_array_new(FALSE, FALSE, sizeof(gpointer));
80
+
81
+ const GumMemoryRange range = {base_address, size};
82
+ const GumMatchPattern *pattern = gum_match_pattern_new_from_string(pattern_str);
83
+
84
+ gum_memory_scan(&range, pattern, on_match_fuzzy_adrp, scan_res);
85
+
86
+ return scan_res->results;
87
+ }
88
+
89
+ `)
90
+
91
+
92
+ export class ScanAdrpCMod {
93
+ static readonly cm: CModule = CM
94
+
95
+ static readonly $scan = new NativeFunction(this.cm.scan, 'pointer', ['pointer', 'size_t', 'pointer', 'pointer'], nativeFunctionOptions)
96
+
97
+ static scan(scanRange: { base: NativePointer, size: number }, pattern: string, targetAddr: NativePointer, alignOffset: number) {
98
+ let matcheResults: NativePointer[] = []
99
+
100
+ const userData = Memory.alloc(8 + 4)
101
+ userData.writePointer(targetAddr)
102
+ userData.add(8).writeU32(alignOffset)
103
+
104
+ const scanRes = new CMemoryScanRes(userData)
105
+ const { base, size } = scanRange
106
+ this.$scan(
107
+ base, size, Memory.allocUtf8String(pattern), scanRes.$handle,
108
+ )
109
+ if (scanRes.data.length > 0) {
110
+ matcheResults = scanRes.data.toArray().map(v => v.readPointer())
111
+ }
112
+ this.$dispose(scanRes.$handle)
113
+ return matcheResults
114
+ }
115
+
116
+ static readonly $dispose = new NativeFunction(this.cm._dispose, 'void', ['pointer'], nativeFunctionOptions)
117
+ }
118
+
package/src/config.ts ADDED
@@ -0,0 +1,56 @@
1
+ type InjectedConfig = {
2
+ OnRPC?: boolean
3
+ OutputDir?: string
4
+ LogLevel?: number
5
+ LogCollapse?: boolean
6
+ }
7
+
8
+ const injectedConfig = ((globalThis as typeof globalThis & {
9
+ __FRIDA_ANALYKIT_CONFIG__?: InjectedConfig
10
+ }).__FRIDA_ANALYKIT_CONFIG__) || {}
11
+
12
+ export function setGlobalProperties(keyValues: { [key: string]: any }): void {
13
+ for (let [k, v] of Object.entries(keyValues)) {
14
+ if (k in globalThis) {
15
+ throw new Error(`global property[${k}] exists already`)
16
+ }
17
+ ;(globalThis as typeof globalThis & { [key: string]: unknown })[k] = v
18
+ }
19
+ }
20
+
21
+
22
+ export const LogLevel = {
23
+ DEBUG: 0,
24
+ INFO: 1,
25
+ WARN: 2,
26
+ ERROR: 3,
27
+ _MUST_LOG: 9999999,
28
+ } as const
29
+
30
+ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]
31
+
32
+
33
+
34
+ declare global {
35
+ const LogLevel: {
36
+ DEBUG: number
37
+ INFO: number
38
+ WARN: number
39
+ ERROR: number
40
+ _MUST_LOG: number
41
+ }
42
+ }
43
+
44
+ export class Config {
45
+ static OnRPC: boolean = injectedConfig.OnRPC ?? false
46
+ static OutputDir?: string = injectedConfig.OutputDir
47
+ static LogLevel: number = injectedConfig.LogLevel ?? LogLevel.INFO
48
+ static LogCollapse: boolean = injectedConfig.LogCollapse ?? true
49
+ }
50
+
51
+
52
+
53
+ setGlobalProperties({
54
+ 'Config': Config,
55
+ 'LogLevel': LogLevel,
56
+ })
package/src/consts.ts ADDED
@@ -0,0 +1,31 @@
1
+
2
+
3
+
4
+
5
+ export const nativeFunctionOptions: NativeABI | NativeFunctionOptions = {
6
+ exceptions: 'propagate',
7
+ }
8
+
9
+
10
+
11
+
12
+ export enum SYM_INFO_BIND {
13
+ STB_LOCAL = 0x0,
14
+ STB_GLOBAL = 0x1,
15
+ STB_WEAK = 0x2,
16
+ STB_GNU_UNIQUE = 0x3,
17
+ }
18
+
19
+
20
+ export enum SYM_INFO_TYPE {
21
+ STT_NOTYPE = 0x0,
22
+ STT_OBJECT = 0x1,
23
+ STT_FUNC = 0x2,
24
+ STT_SECTION = 0x3,
25
+ STT_FILE = 0x4,
26
+ }
27
+
28
+ export enum SYM_SHNDX {
29
+ SHN_UNDEF = 0,
30
+ SHN_ABS = 0xfff1,
31
+ }
@@ -0,0 +1,61 @@
1
+ import { setGlobalProperties } from "../config.js"
2
+ import { NativePointerObject } from "../helper.js"
3
+
4
+
5
+
6
+ export class InstructionSequence extends NativePointerObject {
7
+ protected readonly entryInsn: Arm64Instruction
8
+ protected readonly insns: Arm64Instruction[] = []
9
+ protected eoi?: Arm64Instruction
10
+
11
+ constructor(entry: Arm64Instruction) {
12
+ const handle = entry.address
13
+ super(handle)
14
+ this.entryInsn = entry
15
+ this.insns = [entry]
16
+ }
17
+
18
+ static loadFromPointer<T extends InstructionSequence>(
19
+ this: new (insn: Arm64Instruction) => T,
20
+ handle: NativePointer
21
+ ): T {
22
+ const insn = Instruction.parse(handle) as Arm64Instruction
23
+ return new this(insn)
24
+ }
25
+
26
+ *[Symbol.iterator]() {
27
+ let insns = this.insns
28
+ let insn: Arm64Instruction = this.entryInsn
29
+ let inc = 0
30
+ const that = this
31
+
32
+ let value: Arm64Instruction | undefined
33
+
34
+ while (true) {
35
+ value = insns[inc]
36
+ if (value === undefined && that.eoi === undefined) {
37
+ try {
38
+ insn = Instruction.parse(insns[inc - 1].next) as Arm64Instruction
39
+ insns.push(insn)
40
+ } catch (error) {
41
+ that.eoi = insn
42
+ break
43
+ }
44
+ }
45
+ inc++
46
+ yield insn
47
+ }
48
+
49
+ }
50
+
51
+ clearCache() {
52
+ this.insns.length = 0
53
+ }
54
+
55
+
56
+ }
57
+
58
+
59
+ setGlobalProperties({
60
+ InstructionSequence,
61
+ })