-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.ts
149 lines (119 loc) · 4.11 KB
/
index.ts
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
import fs from 'fs'
import path from 'path'
import frida = require('frida')
import esbuild = require('esbuild')
import { createHostLogger } from './logger'
import { HostCompleteConfig } from './config'
export * from './config'
const logger = createHostLogger()
const esbuildLogger = createHostLogger("ESBuild")
let loadedScript: frida.Script | null = null
function watchAll(targets: MetaInputs, triggerRebuild: fs.WatchListener<string>) {
for (const file in targets) {
logger.log(`Watching ${path.join(process.cwd(), file)}...`)
fs.watch(path.join(process.cwd(), file), triggerRebuild)
}
}
async function launchRemoteScript(session: frida.Session, scriptFile: string, conf: HostCompleteConfig) {
const script = fs.readFileSync(scriptFile)
if (loadedScript && !loadedScript.isDestroyed) {
await loadedScript.unload()
}
loadedScript = await session.createScript(script.toString('utf-8'))
loadedScript.message.connect((message, buffer) => {
if (message.type === "error") {
if (conf.errorHandler) {
conf.errorHandler(message)
} else {
logger.error(`(${Date.now()}) [Remote] Error: ${message.description}\n${message.stack}`)
}
return
}
if (conf.messageHandler) {
conf.messageHandler(message, buffer)
return
}
logger.log(`(${Date.now()}) [Remote] ${message.payload}`)
})
await loadedScript.load()
}
type MetaInputs = {
[path: string]: {
bytes: number
imports: {
path: string
kind: esbuild.ImportKind
external?: boolean
original?: string
with?: Record<string, string>
}[]
format?: 'cjs' | 'esm'
with?: Record<string, string>
}
}
export async function buildBundle(ctx: esbuild.BuildContext): Promise<MetaInputs | null> {
try {
const result = await ctx.rebuild()
if (result.errors.length > 0) {
esbuildLogger.error(`Error while building remote script: ${result.errors}`)
return null
}
return result.metafile!.inputs ?? null
} catch (ex) {
esbuildLogger.error(`Error while building remote script: ${ex}`)
return null
}
}
export async function startHost(conf: HostCompleteConfig) {
esbuildLogger.log(`Creating script bundle...`)
const outfile = conf.output
const esbuildCtx = await esbuild.context({
entryPoints: [ conf.entryPoint ],
bundle: true,
minify: false,
sourcemap: true,
outfile,
format: "cjs",
metafile: true,
})
const sources = await buildBundle(esbuildCtx)
if (!sources) {
esbuildLogger.error(`Failed to build script bundle! Exiting...`)
process.exit(1)
}
const device = await conf.createDeviceConnection()
let session: frida.Session | null = null
const pref = conf.getAttachPreferenceInDetails()
for (let i = 0; i < pref.maxAttempts && !session; i++) {
try {
session = await conf.tryCreateSession()
} catch(ex) {
logger.log(`App not found, retrying in ${pref.delay / 1000}s...`)
await (new Promise(resolve => setTimeout(resolve, pref.delay)))
continue
}
}
if (!session) {
logger.error(`Failed to attach to target process after ${pref.maxAttempts} retries! Giving up...`)
return
}
await session.enableChildGating()
let rebuilding = false
watchAll(sources!, async (event, filename) => {
if (rebuilding) return
if (session.isDetached) {
logger.error(`Session detached! Exiting...`)
process.exit(0)
}
rebuilding = true
logger.clear()
logger.log(`Script modification detected! Rebuilding...`)
rebuilding &&= Boolean(await buildBundle(esbuildCtx))
logger.log(`Launching script...`)
await launchRemoteScript(session, outfile, conf)
rebuilding = false
})
logger.log(`Launching script...`)
await launchRemoteScript(session, outfile, conf)
await session.resume()
}