From 5ca85b5ba3a8032cd303957c2801bc7347d744f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20S=C3=B8rb=C3=B8?= Date: Tue, 28 Jan 2025 21:19:27 +0100 Subject: [PATCH 1/2] rename --- index.js => index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename index.js => index.ts (100%) diff --git a/index.js b/index.ts similarity index 100% rename from index.js rename to index.ts From b214dc7713317d684175f0f5ef14fab85b1dabc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20S=C3=B8rb=C3=B8?= Date: Tue, 28 Jan 2025 23:42:22 +0100 Subject: [PATCH 2/2] Convert library to TypeScript --- .gitignore | 1 + index.ts | 161 +++++++++++++++++++++++++++++++--------------- package-lock.json | 25 ++++++- package.json | 17 ++++- tsconfig.json | 12 ++++ 5 files changed, 160 insertions(+), 56 deletions(-) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 0692df9..a0104ef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /example/node_modules /node_modules npm-debug.log +/dist \ No newline at end of file diff --git a/index.ts b/index.ts index 3ed2546..525455c 100644 --- a/index.ts +++ b/index.ts @@ -4,11 +4,64 @@ const IN_OUT = Symbol('in-out'); const pointerSize = Process.pointerSize; -export default function trace (spec) { +type Direction = typeof IN | typeof OUT | typeof IN_OUT; +type Predicate = (value: any) => boolean; + +interface Spec { + module?: string; + vtable: NativePointer; + functions: Func[]; + callbacks: { + onError: (error: Error) => void; + shouldSkip?: (context: InvocationContext) => boolean, + onEvent: (event: Event) => void, + onEnter?: (event: Event, args: InvocationArguments) => void, + onLeave?: (event: Event, retval: InvocationReturnValue) => void, + }; +} + +interface Type { + parse(rawValue: NativePointer, parameters: any[]): any; + read(ptr: NativePointer): any; +} + +interface Func { + name: string, + ret: Arg, + args: Arg[] +} + +interface Arg { + direction: Direction, + name: string, + type: { + parse: boolean + }, + condition: Condition, + requires: string[] +} + +interface Binding { + property: string + value: string +} + +type Parse = (value: any, params?: any) => void; + +// action, params +type Action = [(values:NativePointer[], event:Event, params:any) => void, any]; + +interface Condition { + value: string; + property?: string; + predicate?: Predicate +} + +export default function trace (spec: Spec) { const {module, vtable, functions} = spec; const {onError} = spec.callbacks; - const listeners = []; + const listeners : (InvocationListener | undefined)[] = []; const intercept = makeInterceptor(spec); if (module !== undefined) { @@ -85,16 +138,16 @@ trace.types = { cstring: cstring }; -function makeInterceptor (spec) { +function makeInterceptor (spec: Spec) { const {shouldSkip, onEvent, onEnter, onLeave, onError} = spec.callbacks; - return function (func, impl) { + return function (func: Func, impl: NativePointerValue) : InvocationListener | undefined { const name = func.name; - const inputActions = []; - const outputActions = []; + const inputActions: Action[] = []; + const outputActions: Action[] = []; if (!computeActions(func, inputActions, outputActions)) { - onError(new Error(`Oops. It seems ${module}!${name} has circular dependencies.`)); + onError(new Error(`Oops. It seems ${spec.module}!${name} has circular dependencies.`)); return; } @@ -108,7 +161,7 @@ function makeInterceptor (spec) { this._skip = true; return; } - const values = []; + const values: NativePointer[] = []; for (let i = 0; i !== numArgs; i++) { values.push(args[i]); } @@ -129,8 +182,8 @@ function makeInterceptor (spec) { if (this._skip === true) { return; } - const values = this.values; - const event = this.event; + const values: NativePointer[] = this.values; + const event: Event = this.event; values.push(retval); @@ -148,13 +201,13 @@ function makeInterceptor (spec) { }; } -function computeActions (func, inputActions, outputActions) { +function computeActions (func: Func, inputActions: Action[], outputActions: Action[]) { const args = func.args.slice(); if (func.ret !== null) { args.push(func.ret); } - const satisfied = new Set(); + const satisfied: Set = new Set(); let previousSatisfiedSize; do { @@ -192,7 +245,7 @@ function computeActions (func, inputActions, outputActions) { return !args.some(arg => !satisfied.has(arg.name)); } -function computeAction (arg, index) { +function computeAction (arg: Arg, index: number): Action { const {name, type, condition} = arg; const hasDependentType = Array.isArray(type); @@ -210,39 +263,39 @@ function computeAction (arg, index) { return [readValue, [index, name, type.parse]]; } -function readValue (values, event, params) { +function readValue (values: any[], event: Event, params: [number, string, Parse]) { const [index, name, parse] = params; event.set(name, parse(values[index])); } -function readValueConditionally (values, event, params) { +function readValueConditionally (values: any[], event: Event, params: [number, string, Parse, Condition]) { const [index, name, parse, condition] = params; - if (condition.predicate(event.get(condition.value))) { + if (condition.predicate!(event.get(condition.value))) { event.set(name, parse(values[index])); } } -function readValueWithDependentType (values, event, params) { +function readValueWithDependentType (values: any[], event: Event, params: [number, string, Parse, Binding]) { const [index, name, parse, binding] = params; - const typeParameters = {}; + const typeParameters: Record = {}; typeParameters[binding.property] = event.get(binding.value); event.set(name, parse(values[index], typeParameters)); } -function readValueWithDependentTypeConditionally (values, event, params) { +function readValueWithDependentTypeConditionally (values: any[], event: Event, params: [number, string, any, Binding, Condition]) { const [index, name, parse, binding, condition] = params; - if (condition.predicate(event.get(condition.value))) { - const typeParameters = {}; + if (condition.predicate!(event.get(condition.value))) { + const typeParameters: Record = {}; typeParameters[binding.property] = event.get(binding.value); event.set(name, parse(values[index], typeParameters)); } } -function func (name, ret, args) { +function func (name: string, ret: any, args: Arg[]): Func { return { name: name, ret: ret, @@ -250,19 +303,19 @@ function func (name, ret, args) { }; } -function argIn (name, type, condition) { +function argIn (name: string, type: Type, condition: Condition) { return arg(IN, name, type, condition); } -function argOut (name, type, condition) { +function argOut (name: string, type: Type, condition: Condition) { return arg(OUT, name, type, condition); } -function argInOut (name, type, condition) { +function argInOut (name: string, type: Type, condition: Condition) { return arg(IN_OUT, name, type, condition); } -function arg (direction, name, type, condition) { +function arg (direction: Direction, name: string, type: Type, condition: Condition) { condition = condition || null; return { @@ -274,29 +327,29 @@ function arg (direction, name, type, condition) { }; } -function retval (type, condition) { +function retval (type: Type, condition: Condition) { return argOut('result', type, condition); } -function padding (n) { +function padding (n:any) { return [n]; } -function bind (property, value) { +function bind (property: string, value: string): Condition { return { property: property, value: value }; } -function when (value, predicate) { +function when (value: string, predicate: Predicate): Condition { return { value: value, predicate: predicate }; } -function dependencies (direction, type, condition) { +function dependencies (direction: Direction, type: Type, condition: Condition) { const result = []; if (direction === OUT) { @@ -314,7 +367,7 @@ function dependencies (direction, type, condition) { return result; } -function bool () { +function bool (): Type { return { parse (rawValue) { return !!rawValue.toInt32(); @@ -325,7 +378,7 @@ function bool () { }; } -function byte () { +function byte (): Type { return { parse (rawValue) { return rawValue.toInt32() & 0xff; @@ -336,7 +389,7 @@ function byte () { }; } -function short () { +function short (): Type { return { parse (rawValue) { return rawValue.toInt32() & 0xffff; @@ -347,20 +400,20 @@ function short () { }; } -function int () { +function int (): Type { return { - parse (rawValue) { + parse (rawValue: NativePointer) { return rawValue.toInt32(); }, - read (ptr) { + read (ptr: NativePointer) { return ptr.readInt(); } }; } -function pointer (pointee) { +function pointer (pointee?: any): Type { return { - parse (rawValue, parameters) { + parse (rawValue: NativePointer, parameters: any[]) { if (pointee) { if (rawValue.isNull()) { return null; @@ -371,41 +424,41 @@ function pointer (pointee) { return rawValue; } }, - read (ptr) { + read (ptr: NativePointer) { return ptr.readPointer(); } }; } -function byteArray () { +function byteArray (): Type { return pointer({ - read (ptr, parameters) { + read (ptr: NativePointer, parameters: any[]) { return ptr.readByteArray(parameters.length); } }); } -function utf8 () { +function utf8 (): Type { return pointer({ - read (ptr, parameters) { + read (ptr: NativePointer, parameters: any[]) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readUtf8String(length); } }); } -function utf16 () { +function utf16 (): Type { return pointer({ - read (ptr, parameters) { + read (ptr: NativePointer, parameters: any[]) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readUtf16String(length); } }); } -function cstring () { +function cstring (): Type { return pointer({ - read (ptr, parameters) { + read (ptr: NativePointer, parameters: any[]) { const length = (parameters === undefined) ? -1 : parameters.length; return ptr.readCString(length); } @@ -413,7 +466,8 @@ function cstring () { } class Session { - constructor (listeners) { + _listeners: InvocationListener[]; + constructor (listeners: any[]) { this._listeners = listeners; } @@ -425,16 +479,19 @@ class Session { } class Event { - constructor (name) { + name: string; + args: Record; + result: any; + constructor (name: string) { this.name = name; this.args = {}; } - get (key) { + get (key: string) { return (key === 'result') ? this.result : this.args[key]; } - set (key, value) { + set (key: string, value: any) { if (key === 'result') { this.result = value; } else { diff --git a/package-lock.json b/package-lock.json index c401b4b..aa062e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,30 @@ "": { "name": "frida-trace", "version": "4.0.0", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "@types/frida-gum": "^18.7.2", + "typescript": "^5.1.3" + } + }, + "node_modules/@types/frida-gum": { + "version": "18.7.2", + "resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-18.7.2.tgz", + "integrity": "sha512-nOxTk8n45nQlFQhSSXcnePrzjw6k6JMJMESZ8HlHhUiIQqvGI4RYa69FYdUbGoBRnITn8srEq5vn0dkVwO/ezQ==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } } } } diff --git a/package.json b/package.json index 0fe9369..ac649cb 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,18 @@ "name": "frida-trace", "version": "4.0.0", "description": "Trace APIs declaratively", - "main": "index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", "type": "module", "files": [ - "/index.js" + "/dist/index.js", + "/dist/index.d.ts" ], + "scripts": { + "prepare": "npm run build", + "build": "tsc", + "watch": "tsc -w" + }, "repository": { "type": "git", "url": "git+https://github.com/nowsecure/frida-trace.git" @@ -15,5 +22,9 @@ "bugs": { "url": "https://github.com/nowsecure/frida-trace/issues" }, - "homepage": "https://github.com/nowsecure/frida-trace#readme" + "homepage": "https://github.com/nowsecure/frida-trace#readme", + "devDependencies": { + "typescript": "^5.1.3", + "@types/frida-gum": "^18.7.2" + } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..77ba55a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "lib": ["ES2020"], + "target": "ES2020", + "module": "Node16", + "moduleResolution": "node16", + "esModuleInterop": true, + "strict": true, + "declaration": true, + "outDir": "dist" + } + } \ No newline at end of file