├── commonjs ├── package.json ├── code-root.cjs.d.ts ├── code-root.cjs └── code-root.spec.ts ├── tests ├── fixtures │ ├── typings.d.ts │ ├── simple-class.ts │ ├── sidecar-dump.metadata.smoke-testing.json.fixture │ ├── smoke-testing.ts │ └── sidecar-metadata.fixture.ts ├── library-sidecar.spec.ts ├── decorator-execute-order.spec.ts └── integration.spec.ts ├── src ├── decorators │ ├── name │ │ ├── mod.ts │ │ ├── name.spec.ts │ │ └── name.ts │ ├── param-type │ │ ├── constants.ts │ │ ├── mod.ts │ │ ├── metadata-param-type.spec.ts │ │ ├── metadata-param-type.ts │ │ ├── guard-param-type.spec.ts │ │ ├── param-type.ts │ │ ├── guard-param-type.ts │ │ └── param-type.spec.ts │ ├── sidecar │ │ ├── constants.ts │ │ ├── mod.ts │ │ ├── metadata-sidecar.spec.ts │ │ ├── guard-metadata-sidecar.spec.ts │ │ ├── guard-metadata-sidecar.ts │ │ ├── metadata-sidecar.ts │ │ ├── sidecar.ts │ │ ├── target.ts │ │ ├── target.spec.ts │ │ ├── sidecar.spec.ts │ │ └── build-sidecar-metadata.spec.ts │ ├── ret-type │ │ ├── constants.ts │ │ ├── mod.ts │ │ ├── metadata-ret-type.spec.ts │ │ ├── metadata-ret-type.ts │ │ ├── ret-type.ts │ │ ├── guard-ret-type.ts │ │ ├── guard-ret-type.spec.ts │ │ └── ret-type.spec.ts │ ├── hook │ │ ├── mod.ts │ │ ├── hook.spec.ts │ │ └── hook.ts │ ├── call │ │ ├── mod.ts │ │ ├── constants.ts │ │ ├── metadata-call.spec.ts │ │ ├── metadata-call.ts │ │ ├── call.ts │ │ ├── call.spec.ts │ │ ├── update-rpc-descriptor.spec.ts │ │ └── update-rpc-descriptor.ts │ └── mod.ts ├── version.ts ├── cjs.ts ├── cli │ ├── mod.ts │ ├── extract-class-names.ts │ ├── source.ts │ ├── metadata.ts │ ├── vm.ts │ ├── extract-class-names.spec.ts │ ├── metadata-handler.ts │ ├── source-handler.ts │ └── source-handler.spec.ts ├── wrappers │ ├── log-level.ts │ ├── native-function-name-list.ts │ ├── native-ret-type.ts │ ├── native-param-types.ts │ ├── js-args.ts │ ├── name-helpers.ts │ ├── native-args.ts │ ├── js-ret.spec.ts │ ├── native-ret-type.spec.ts │ ├── module-name.ts │ ├── native-args.spec.ts │ ├── native-param-types.spec.ts │ ├── mod.ts │ ├── module-name.spec.ts │ ├── declare-js-args.ts │ ├── js-ret.ts │ └── name-helper.spec.ts ├── sidecar-body │ ├── mod.ts │ ├── operations.ts │ ├── constants.ts │ ├── sidecar-body.spec.ts │ ├── payload-schemas.ts │ ├── sidecar-emitter.ts │ └── operations.spec.ts ├── cjs.spec.ts ├── config.ts ├── version.spec.ts ├── misc.spec.ts ├── ret.spec.ts ├── ret.ts ├── agent │ ├── templates │ │ ├── rpc-exports.mustache │ │ ├── native-functions-agent.mustache │ │ ├── libs │ │ │ ├── payload.cjs.d.ts │ │ │ ├── log.cjs.d.ts │ │ │ ├── log.spec.ts │ │ │ ├── payload.cjs │ │ │ └── payload.spec.ts │ │ ├── interceptors.mustache │ │ ├── native-functions.mustache │ │ ├── native-functions.spec.ts │ │ ├── interceptors.spec.ts │ │ ├── interceptors-address.mustache │ │ ├── native-functions-address.mustache │ │ ├── agent.spec.ts │ │ ├── interceptors-export.mustache │ │ ├── native-functions-export.mustache │ │ ├── interceptors-agent.mustache │ │ ├── rpc-exports.spec.ts │ │ └── agent.mustache │ ├── partial-lookup.ts │ ├── partial-lookup.spec.ts │ ├── build-agent-source.ts │ └── build-agent-source.spec.ts ├── misc.ts ├── frida-agent.d.ts ├── frida.ts ├── function-target.spec.ts ├── mod.ts ├── frida.spec.ts ├── type-guard.spec.ts └── function-target.ts ├── docs └── images │ ├── adapter.png │ └── sidecar.webp ├── examples ├── chatbox │ ├── chatbox-linux │ ├── chatbox-darwin │ ├── Makefile │ └── chatbox.c ├── dynamic-library │ ├── libfactorial-x64.dll │ ├── libfactorial-x64.so │ ├── libfactorial.dylib │ ├── main.c │ ├── factorial.c │ ├── README.md │ ├── main.ts │ └── factorial-sidecar.ts ├── archive │ ├── raw │ │ ├── frida.ts │ │ ├── Makefile │ │ ├── script-destroyed-handler.ts │ │ ├── clean.ts │ │ ├── load-agent-source.ts │ │ ├── messaging.c │ │ ├── script-message-handler.ts │ │ ├── agent.ts │ │ └── sidecar.ts │ ├── raw-events │ │ ├── frida.ts │ │ ├── schema.ts │ │ ├── Makefile │ │ ├── load-agent-source.ts │ │ ├── sidecar.ts │ │ ├── messaging.c │ │ └── agent.ts │ ├── win32 │ │ └── agent-message-box.ts │ └── decorator │ │ └── reflect-metadata.ts ├── README.md ├── chatbox-sidecar.ts ├── chatbox-sidecar-pro │ ├── chatbox-sidecar-pro.ts │ └── sidecar-config.ts ├── chatbox-sidecar-agent │ ├── chatbox-sidecar-agent.ts │ ├── sidecar-config.ts │ └── init-agent-script.js └── main.ts ├── tsconfig.cjs.json ├── .markdownlint.json ├── scripts ├── package-publish-config-tag.sh ├── generate-version.sh ├── npm-pack-testing.sh └── post-install.cjs ├── tsconfig.json ├── .eslintrc.cjs ├── .editorconfig ├── bin └── sidecar-dump.ts ├── .gitignore ├── .vscode └── settings.json └── .github └── workflows └── npm.yml /commonjs/package.json: -------------------------------------------------------------------------------- 1 | { "type": "commonjs" } 2 | -------------------------------------------------------------------------------- /tests/fixtures/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'es-main' 2 | -------------------------------------------------------------------------------- /src/decorators/name/mod.ts: -------------------------------------------------------------------------------- 1 | export { Name } from './name.js' 2 | -------------------------------------------------------------------------------- /commonjs/code-root.cjs.d.ts: -------------------------------------------------------------------------------- 1 | export declare const codeRoot: string 2 | -------------------------------------------------------------------------------- /docs/images/adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/docs/images/adapter.png -------------------------------------------------------------------------------- /docs/images/sidecar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/docs/images/sidecar.webp -------------------------------------------------------------------------------- /examples/chatbox/chatbox-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/examples/chatbox/chatbox-linux -------------------------------------------------------------------------------- /examples/chatbox/chatbox-darwin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/examples/chatbox/chatbox-darwin -------------------------------------------------------------------------------- /src/decorators/param-type/constants.ts: -------------------------------------------------------------------------------- 1 | const PARAM_TYPE_SYMBOL = Symbol('parameterType') 2 | 3 | export { PARAM_TYPE_SYMBOL } 4 | -------------------------------------------------------------------------------- /src/decorators/sidecar/constants.ts: -------------------------------------------------------------------------------- 1 | const SIDECAR_SYMBOL = Symbol('sidecarSymbol') 2 | 3 | export { 4 | SIDECAR_SYMBOL, 5 | } 6 | -------------------------------------------------------------------------------- /examples/dynamic-library/libfactorial-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/examples/dynamic-library/libfactorial-x64.dll -------------------------------------------------------------------------------- /examples/dynamic-library/libfactorial-x64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/examples/dynamic-library/libfactorial-x64.so -------------------------------------------------------------------------------- /examples/dynamic-library/libfactorial.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huan/sidecar/HEAD/examples/dynamic-library/libfactorial.dylib -------------------------------------------------------------------------------- /src/decorators/ret-type/constants.ts: -------------------------------------------------------------------------------- 1 | const RET_TYPE_SYMBOL = Symbol('methodRetType') 2 | 3 | export { 4 | RET_TYPE_SYMBOL, 5 | } 6 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto generated from scripts/generate-version.sh 3 | */ 4 | export const VERSION: string = '0.0.0' 5 | -------------------------------------------------------------------------------- /src/cjs.ts: -------------------------------------------------------------------------------- 1 | import codeRootPkg from '../commonjs/code-root.cjs' 2 | const codeRoot = codeRootPkg['codeRoot'] 3 | 4 | export { 5 | codeRoot, 6 | } 7 | -------------------------------------------------------------------------------- /src/cli/mod.ts: -------------------------------------------------------------------------------- 1 | import { metadata } from './metadata.js' 2 | import { source } from './source.js' 3 | 4 | export { 5 | metadata, 6 | source, 7 | } 8 | -------------------------------------------------------------------------------- /src/wrappers/log-level.ts: -------------------------------------------------------------------------------- 1 | import { log } from '../config.js' 2 | 3 | function logLevel () { 4 | return log.level() 5 | } 6 | 7 | export { logLevel } 8 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "dist/cjs", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/simple-class.ts: -------------------------------------------------------------------------------- 1 | function decorator (...args: any[]): any { void args } 2 | 3 | @decorator 4 | class Test { 5 | n?: number 6 | } 7 | 8 | export { Test } 9 | -------------------------------------------------------------------------------- /src/decorators/hook/mod.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Hook, 3 | getMetadataHook, 4 | } from './hook.js' 5 | 6 | export { 7 | Hook, 8 | getMetadataHook, 9 | } 10 | -------------------------------------------------------------------------------- /commonjs/code-root.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const codeRoot = path.join( 4 | __dirname, 5 | '..', 6 | ) 7 | 8 | module.exports = { 9 | codeRoot, 10 | } 11 | -------------------------------------------------------------------------------- /examples/archive/raw/frida.ts: -------------------------------------------------------------------------------- 1 | export * from 'frida' 2 | export type { 3 | ScriptMessageHandler, 4 | ScriptDestroyedHandler, 5 | } from 'frida/dist/script' 6 | -------------------------------------------------------------------------------- /examples/archive/raw-events/frida.ts: -------------------------------------------------------------------------------- 1 | export * from 'frida' 2 | export type { 3 | ScriptMessageHandler, 4 | ScriptDestroyedHandler, 5 | } from 'frida/dist/script' 6 | -------------------------------------------------------------------------------- /src/decorators/call/mod.ts: -------------------------------------------------------------------------------- 1 | import { Call } from './call.js' 2 | import { getMetadataCall } from './metadata-call.js' 3 | 4 | export { 5 | Call, 6 | getMetadataCall, 7 | } 8 | -------------------------------------------------------------------------------- /examples/archive/raw-events/schema.ts: -------------------------------------------------------------------------------- 1 | export interface SidecarFridaPayload { 2 | method: string, 3 | args: { 4 | [k: string]: null | string | number 5 | }, 6 | data?: null | Buffer, 7 | } 8 | -------------------------------------------------------------------------------- /src/decorators/call/constants.ts: -------------------------------------------------------------------------------- 1 | const CALL_SYMBOL = Symbol('callTarget') 2 | const DEBUG_CALL_RET_ERROR = Symbol('callRetError') 3 | 4 | export { 5 | CALL_SYMBOL, 6 | DEBUG_CALL_RET_ERROR, 7 | } 8 | -------------------------------------------------------------------------------- /src/decorators/ret-type/mod.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataRetType } from './metadata-ret-type.js' 2 | import { RetType } from './ret-type.js' 3 | 4 | export { 5 | getMetadataRetType, 6 | RetType, 7 | } 8 | -------------------------------------------------------------------------------- /src/decorators/param-type/mod.ts: -------------------------------------------------------------------------------- 1 | import { getMetadataParamType } from './metadata-param-type.js' 2 | import { ParamType } from './param-type.js' 3 | 4 | export { 5 | getMetadataParamType, 6 | ParamType, 7 | } 8 | -------------------------------------------------------------------------------- /examples/archive/raw/Makefile: -------------------------------------------------------------------------------- 1 | all: messaging.c 2 | gcc -o messaging messaging.c 3 | 4 | clean: 5 | rm -f messaging 6 | 7 | .PHONY: messaging 8 | messaging: 9 | ./messaging 10 | 11 | sidecar: 12 | ts-node sidecar.ts 13 | -------------------------------------------------------------------------------- /examples/archive/raw-events/Makefile: -------------------------------------------------------------------------------- 1 | all: messaging.c 2 | gcc -o messaging messaging.c 3 | 4 | clean: 5 | rm -f messaging 6 | 7 | .PHONY: messaging 8 | messaging: 9 | ./messaging 10 | 11 | sidecar: 12 | ts-node sidecar.ts 13 | -------------------------------------------------------------------------------- /src/sidecar-body/mod.ts: -------------------------------------------------------------------------------- 1 | import { SidecarBody } from './sidecar-body.js' 2 | import { 3 | attach, 4 | detach, 5 | } from './operations.js' 6 | 7 | export { 8 | attach, 9 | detach, 10 | SidecarBody, 11 | } 12 | -------------------------------------------------------------------------------- /commonjs/code-root.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S ts-node --project tsconfig.cjs.json 2 | 3 | import { test } from 'tstest' 4 | 5 | import { codeRoot } from './code-root.cjs' 6 | 7 | test('CJS: codeRoot()', async t => { 8 | t.ok(codeRoot, 'should exist codeRoot') 9 | }) 10 | -------------------------------------------------------------------------------- /src/cjs.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | 5 | import { 6 | codeRoot, 7 | } from './cjs.js' 8 | 9 | test('ESM: codeRoot', async t => { 10 | t.ok(codeRoot, 'should exists "codeRoot"') 11 | }) 12 | -------------------------------------------------------------------------------- /examples/archive/raw/script-destroyed-handler.ts: -------------------------------------------------------------------------------- 1 | import type { ScriptDestroyedHandler } from './frida.js' 2 | import { log } from 'brolog' 3 | 4 | const scriptDestroyedHandler: ScriptDestroyedHandler = () => { 5 | log.verbose('Sidecar', 'scriptDestroyedHandler()') 6 | } 7 | 8 | export { scriptDestroyedHandler } 9 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Run the sidecar example with the following commands: 2 | 3 | ```sh 4 | # Clone the git repo to local 5 | git clone git@github.com:huan/sidecar.git 6 | 7 | # Enter the repo folder 8 | cd sidecar 9 | 10 | # Install dependencies 11 | npm install 12 | 13 | # run the examples/demo.ts 14 | npm start 15 | ``` 16 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import 'reflect-metadata' 3 | 4 | import { log } from 'brolog' 5 | import { wrapAsyncError } from 'gerror' 6 | 7 | const wrapAsync = wrapAsyncError(e => log.error('Sidecar', 'wrapAsyncError: %s\n%s', e.message, e.stack)) 8 | 9 | export { 10 | log, 11 | wrapAsync, 12 | } 13 | -------------------------------------------------------------------------------- /src/version.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { VERSION } from './version.js' 5 | 6 | test('Make sure the VERSION is fresh in source code', async t => { 7 | t.equal(VERSION, '0.0.0', 'version should be 0.0.0 in source code, only updated before publish to NPM') 8 | }) 9 | -------------------------------------------------------------------------------- /src/wrappers/native-function-name-list.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadata } from '../decorators/sidecar/mod.js' 2 | 3 | function nativeFunctionNameList (this: SidecarMetadata) { 4 | return this.nativeFunctionList 5 | .map(x => Object.values(x)) 6 | .flat() 7 | .map(x => x.name) 8 | } 9 | 10 | export { nativeFunctionNameList } 11 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "no-trailing-punctuation": { 4 | "punctuation": ".,;:!" 5 | }, 6 | "MD013": false, 7 | "MD033": { 8 | "allowed_elements": ["dl", "dt", "dd", "code", "var", "cite"] 9 | }, 10 | "first-line-h1": false, 11 | "no-hard-tabs": true, 12 | "no-trailing-spaces": { 13 | "br_spaces": 2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/archive/raw/clean.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Session, 3 | Script, 4 | } from './frida.js' 5 | import { log } from 'brolog' 6 | 7 | function clean ( 8 | session: Session, 9 | script: Script, 10 | ): void { 11 | log.verbose('Sidecar', 'clean()') 12 | 13 | script.unload().catch(console.error) 14 | session.detach().catch(console.error) 15 | } 16 | 17 | export { clean } 18 | -------------------------------------------------------------------------------- /src/wrappers/native-ret-type.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 2 | 3 | function nativeRetType (this: SidecarMetadataFunctionDescription) { 4 | // console.log('this.retType', this.retType) 5 | if (!this.retType || this.retType.length <= 0) { 6 | return "'void'" 7 | } 8 | return `'${this.retType[0]}'` 9 | } 10 | 11 | export { nativeRetType } 12 | -------------------------------------------------------------------------------- /scripts/package-publish-config-tag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | VERSION=$(npx pkg-jq -r .version) 5 | 6 | if npx --package @chatie/semver semver-is-prod $VERSION; then 7 | npx pkg-jq -i '.publishConfig.tag="latest"' 8 | echo "production release: publicConfig.tag set to latest." 9 | else 10 | npx pkg-jq -i '.publishConfig.tag="next"' 11 | echo 'development release: publicConfig.tag set to next.' 12 | fi 13 | 14 | -------------------------------------------------------------------------------- /scripts/generate-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | SRC_VERSION_TS_FILE='src/version.ts' 5 | 6 | [ -f ${SRC_VERSION_TS_FILE} ] || { 7 | echo ${SRC_VERSION_TS_FILE}" not found" 8 | exit 1 9 | } 10 | 11 | VERSION=$(npx pkg-jq -r .version) 12 | 13 | cat <<_SRC_ > ${SRC_VERSION_TS_FILE} 14 | /** 15 | * This file was auto generated from scripts/generate-version.sh 16 | */ 17 | export const VERSION: string = '${VERSION}' 18 | _SRC_ 19 | -------------------------------------------------------------------------------- /src/misc.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | isInstance, 6 | } from './misc.js' 7 | 8 | test('isInstance()', async t => { 9 | class Test {} 10 | const test = new Test() 11 | 12 | t.notOk(isInstance(Test), 'should identify static Class is not an instance') 13 | t.ok(isInstance(test), 'should identify class instance to be an instance') 14 | }) 15 | -------------------------------------------------------------------------------- /src/ret.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | RET_SYMBOL, 6 | Ret, 7 | } from './ret.js' 8 | 9 | test('Ret()', async t => { 10 | const p = Ret() 11 | t.equal(p.constructor.name, 'Promise', 'should get a promise from Ret()') 12 | 13 | const r = await p 14 | t.equal(r, RET_SYMBOL, 'should return RET_SYMBOL by calling Ret()') 15 | }) 16 | -------------------------------------------------------------------------------- /src/ret.ts: -------------------------------------------------------------------------------- 1 | const RET_SYMBOL = Symbol('PLACE_HOLDER_FOR_RETURN') 2 | 3 | function Ret (...args: any[]): Promise { 4 | void args // nop. Just for making TypeScript happy 5 | /** 6 | * You can safely ignore the following return value: RET 7 | * Because it will be replaced by the docorator 8 | * with the real return value. 9 | */ 10 | return Promise.resolve(RET_SYMBOL) 11 | } 12 | 13 | export { 14 | RET_SYMBOL, 15 | Ret, 16 | } 17 | -------------------------------------------------------------------------------- /src/decorators/sidecar/mod.ts: -------------------------------------------------------------------------------- 1 | import { Sidecar } from './sidecar.js' 2 | import type { 3 | SidecarMetadata, 4 | SidecarMetadataFunctionDescription, 5 | SidecarMetadataFunctionTypeDescription, 6 | } from './metadata-sidecar.js' 7 | 8 | export type { 9 | SidecarMetadata, 10 | SidecarMetadataFunctionDescription, 11 | SidecarMetadataFunctionTypeDescription, 12 | } 13 | 14 | export { 15 | Sidecar, 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@chatie/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist/esm", 5 | "emitDecoratorMetadata": true, 6 | "lib": [ 7 | "esnext", 8 | ], 9 | }, 10 | "exclude": [ 11 | "node_modules/", 12 | "dist/", 13 | "tests/fixtures/", 14 | ], 15 | "include": [ 16 | "bin/*.ts", 17 | "examples/**/*.ts", 18 | "scripts/**/*.ts", 19 | "src/**/*.ts", 20 | "tests/**/*.spec.ts", 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const rules = { 2 | 'no-console': ['error', { allow: ['log', 'info', 'warn', 'error'] }], 3 | 'multiline-ternary': 0, 4 | 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 5 | } 6 | const globals = { 7 | Interceptor: true, 8 | Memory: true, 9 | NativeFunction: true, 10 | ptr: true, 11 | rpc: true, 12 | send: true, 13 | recv: true, 14 | } 15 | 16 | module.exports = { 17 | extends: '@chatie', 18 | rules, 19 | globals, 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | indent_style = space 15 | trim_trailing_whitespace = false 16 | 17 | # 4 tab indentation 18 | [Makefile] 19 | indent_style = tab 20 | indent_size = 4 21 | 22 | [*.py] 23 | indent_size = 4 24 | -------------------------------------------------------------------------------- /src/agent/templates/rpc-exports.mustache: -------------------------------------------------------------------------------- 1 | /********************************************************* 2 | * File: "rpc-exports.mustache" 3 | * ---------------------------- 4 | * RPC Exports List: automaticated generated by Sidecar 5 | * 6 | * Author: Huan 7 | * https://github.com/huan/sidecar 8 | *********************************************************/ 9 | rpc.exports = { 10 | ...rpc.exports, 11 | {{# nativeFunctionNameList }} 12 | {{ . }}: __sidecar__{{ . }}_Function_wrapper, 13 | {{/ nativeFunctionNameList}} 14 | } 15 | -------------------------------------------------------------------------------- /src/wrappers/native-param-types.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 2 | 3 | function nativeParamTypes ( 4 | this: SidecarMetadataFunctionDescription, 5 | ): string { 6 | /** 7 | * There's no any parameters 8 | */ 9 | if (/* !this.paramTypeList || */ this.paramTypeList.length === 0) { 10 | return '[]' 11 | } 12 | return '[ ' 13 | + this.paramTypeList 14 | .map(paramType => `'${paramType[0]}'`) 15 | .join(', ') 16 | + ' ]' 17 | } 18 | 19 | export { nativeParamTypes } 20 | -------------------------------------------------------------------------------- /src/agent/partial-lookup.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * partial.mustache -> templates/ 3 | */ 4 | import fs from 'fs' 5 | import path from 'path' 6 | 7 | import { codeRoot } from '../cjs.js' 8 | 9 | function partialLookup (partial: string) { 10 | const file = path.join( 11 | codeRoot, 12 | 'src', 13 | 'agent', 14 | 'templates', 15 | partial, 16 | ) 17 | if (!file) { 18 | throw new Error(`partial name "${partial}" not found from path "${file}"`) 19 | } 20 | 21 | return fs.readFileSync(file).toString() 22 | } 23 | 24 | export { partialLookup } 25 | -------------------------------------------------------------------------------- /examples/archive/raw/load-agent-source.ts: -------------------------------------------------------------------------------- 1 | const { makeCompiler } = require('frida-compile') 2 | 3 | /** 4 | * See: https://github.com/frida/frida-compile/blob/ca091615186f83c0f2326f07fbfd4eac86056fd7/index.js#L31-L40 5 | */ 6 | async function loadAgentSource (): Promise { 7 | const compile = makeCompiler( 8 | require.resolve('./agent.ts'), 9 | {}, 10 | {}, 11 | ) 12 | const result = await compile() as { bundle: Buffer } 13 | const agentSource = result.bundle.toString() 14 | 15 | return agentSource 16 | } 17 | 18 | export { loadAgentSource } 19 | -------------------------------------------------------------------------------- /examples/archive/raw-events/load-agent-source.ts: -------------------------------------------------------------------------------- 1 | const { makeCompiler } = require('frida-compile') 2 | 3 | /** 4 | * See: https://github.com/frida/frida-compile/blob/ca091615186f83c0f2326f07fbfd4eac86056fd7/index.js#L31-L40 5 | */ 6 | async function loadAgentSource (): Promise { 7 | const compile = makeCompiler( 8 | require.resolve('./agent.ts'), 9 | {}, 10 | {}, 11 | ) 12 | const result = await compile() as { bundle: Buffer } 13 | const agentSource = result.bundle.toString() 14 | 15 | return agentSource 16 | } 17 | 18 | export { loadAgentSource } 19 | -------------------------------------------------------------------------------- /src/wrappers/js-args.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 2 | 3 | function jsArgs ( 4 | this: SidecarMetadataFunctionDescription, 5 | ): string { 6 | const typeList = this.paramTypeList 7 | // if (!typeList) { 8 | // throw new Error('no .paramTypeList found in SidecarMetadataFunctionDescription!') 9 | // } 10 | 11 | const wrappedArgNameList = typeList 12 | .map((_, i) => i) 13 | .map(i => `${this.name}_JsArg_${i}`) 14 | 15 | return '[ ' + wrappedArgNameList.join(', ') + ' ]' 16 | } 17 | 18 | export { jsArgs } 19 | -------------------------------------------------------------------------------- /src/agent/templates/native-functions-agent.mustache: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | * File: "native-function-agent.mustache" 3 | * 4 | * Native Function: {{ name }} 5 | * - funcName: {{ target.funcName }} 6 | * - Parameters: {{{ nativeParamTypes }}} 7 | * - Ret: {{ retType }} 8 | ********************************************/ 9 | 10 | const __sidecar__{{ name }}_Function_wrapper = function (...args) { 11 | log.verbose( 12 | 'SidecarAgent', 13 | '{{ name }}(%s)', 14 | args.join(', '), 15 | ) 16 | return {{ target.funcName }}(...args) 17 | } 18 | -------------------------------------------------------------------------------- /src/agent/templates/libs/payload.cjs.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SidecarPayloadHook, 3 | SidecarPayloadLog, 4 | } from '../../../sidecar-body/payload-schemas.js' 5 | 6 | /* eslint camelcase: 0 */ 7 | declare module './payload.cjs' { 8 | 9 | export declare function __sidecar__payloadHook ( 10 | method: string, 11 | args: any[], 12 | ): SidecarPayloadHook 13 | 14 | export declare function __sidecar__payloadLog ( 15 | level: 'error' | 'warn' | 'info' | 'verbose' | 'silly', 16 | prefix: string, 17 | message: string, 18 | ): SidecarPayloadLog 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/decorators/mod.ts: -------------------------------------------------------------------------------- 1 | import { Call } from './call/mod.js' 2 | import { Hook } from './hook/mod.js' 3 | import { Name } from './name/mod.js' 4 | import { ParamType } from './param-type/mod.js' 5 | import { RetType } from './ret-type/mod.js' 6 | import { 7 | Sidecar, 8 | SidecarMetadata, 9 | SidecarMetadataFunctionDescription, 10 | } from './sidecar/mod.js' 11 | 12 | export type { 13 | SidecarMetadata, 14 | SidecarMetadataFunctionDescription, 15 | } 16 | export { 17 | Call, 18 | Hook, 19 | Name, 20 | ParamType, 21 | RetType, 22 | Sidecar, 23 | } 24 | -------------------------------------------------------------------------------- /src/sidecar-body/operations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ATTACH_SYMBOL, 3 | DETACH_SYMBOL, 4 | INIT_SYMBOL, 5 | } from './constants.js' 6 | import type { SidecarBody } from './sidecar-body.js' 7 | 8 | function attach ( 9 | sidecar: SidecarBody, 10 | ): Promise { 11 | return sidecar[ATTACH_SYMBOL]() 12 | } 13 | 14 | function detach ( 15 | sidecar: SidecarBody, 16 | ): Promise { 17 | return sidecar[DETACH_SYMBOL]() 18 | } 19 | 20 | function init ( 21 | sidecar: SidecarBody, 22 | ): Promise { 23 | return sidecar[INIT_SYMBOL]() 24 | } 25 | 26 | export { 27 | attach, 28 | detach, 29 | init, 30 | } 31 | -------------------------------------------------------------------------------- /src/wrappers/name-helpers.ts: -------------------------------------------------------------------------------- 1 | const jsArgName = ( 2 | method: string, 3 | argIdx: number, 4 | ) => `${method}_JsArg_${argIdx}` 5 | 6 | const nativeArgName = ( 7 | method: string, 8 | argIdx: number, 9 | ) => `${method}_NativeArg_${argIdx}` 10 | 11 | const argName = (idx: number) => `args[${idx}]` 12 | 13 | const bufName = ( 14 | method : string, 15 | argIdx : number, 16 | typeIdx? : number, 17 | ) => [ 18 | `${method}_Memory_${argIdx}`, 19 | typeof typeIdx === 'undefined' 20 | ? '' 21 | : `_${typeIdx}`, 22 | ].join('') 23 | 24 | export { 25 | argName, 26 | bufName, 27 | jsArgName, 28 | nativeArgName, 29 | } 30 | -------------------------------------------------------------------------------- /src/agent/partial-lookup.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import fs from 'fs' 5 | import path from 'path' 6 | 7 | import { 8 | partialLookup, 9 | } from './partial-lookup.js' 10 | import { codeRoot } from '../cjs.js' 11 | 12 | test('partialLookup()', async t => { 13 | const EXPECTED_STR = fs.readFileSync(path.join( 14 | codeRoot, 15 | 'src', 16 | 'agent', 17 | 'templates/libs/log.cjs', 18 | ), 'utf-8') 19 | const source = await partialLookup('libs/log.cjs') 20 | t.equal(source, EXPECTED_STR, 'should get right partial file content') 21 | }) 22 | -------------------------------------------------------------------------------- /src/sidecar-body/constants.ts: -------------------------------------------------------------------------------- 1 | const INIT_SYMBOL = Symbol('init') 2 | const ATTACH_SYMBOL = Symbol('attatch') 3 | const DETACH_SYMBOL = Symbol('detach') 4 | 5 | const SCRIPT_DESTROYED_HANDLER_SYMBOL = Symbol('scriptDestroyedHandler') 6 | const SCRIPT_MESSAGRE_HANDLER_SYMBOL = Symbol('scriptMessageHandler') 7 | 8 | const LOG_EVENT_HANDLER = Symbol('logEventHandler') 9 | const HOOK_EVENT_HANDLER = Symbol('hookEventHandler') 10 | 11 | export { 12 | INIT_SYMBOL, 13 | ATTACH_SYMBOL, 14 | DETACH_SYMBOL, 15 | 16 | SCRIPT_DESTROYED_HANDLER_SYMBOL, 17 | SCRIPT_MESSAGRE_HANDLER_SYMBOL, 18 | 19 | LOG_EVENT_HANDLER, 20 | HOOK_EVENT_HANDLER, 21 | } 22 | -------------------------------------------------------------------------------- /examples/archive/raw-events/sidecar.ts: -------------------------------------------------------------------------------- 1 | import { log } from 'brolog' 2 | 3 | import { MessagingSidecar } from './messaging-sidecar.js' 4 | 5 | log.level('verbose') 6 | 7 | async function main () { 8 | const sidecar = new MessagingSidecar() 9 | 10 | process.on('SIGINT', () => sidecar.stop().catch(console.error)) 11 | process.on('SIGTERM', () => sidecar.stop().catch(console.error)) 12 | 13 | await sidecar.init() 14 | await sidecar.start() 15 | 16 | sidecar.on('hook', payload => { 17 | console.log('MessagingSidecar event[hook]:', payload) 18 | }) 19 | 20 | await sidecar.mo('hello from MessagingSidecar!') 21 | } 22 | 23 | main() 24 | .catch(console.error) 25 | -------------------------------------------------------------------------------- /examples/chatbox/Makefile: -------------------------------------------------------------------------------- 1 | # Credit: https://stackoverflow.com/a/14777895/1123955 2 | ifeq ($(OS),Windows_NT) 3 | detected_OS := win32 4 | else 5 | detected_OS := $(shell sh -c 'uname 2>/dev/null | tr "[:upper:]" "[:lower:]"') 6 | endif 7 | 8 | all: build chatbox 9 | 10 | # https://stackoverflow.com/a/10305055/1123955 11 | build: 12 | -@make $(detected_OS) 13 | 14 | linux: chatbox.c 15 | gcc -o chatbox-$(detected_OS) chatbox.c 16 | 17 | darwin: chatbox.c 18 | gcc -o chatbox-$(detected_OS) chatbox.c 19 | 20 | win32: chatbox.c 21 | cl.exe /Fe:chatbox-$(detected_OS) chatbox.c 22 | 23 | clean: 24 | rm -f chatbox-$(detected_OS) 25 | 26 | chatbox: 27 | ./chatbox-$(detected_OS) 28 | -------------------------------------------------------------------------------- /src/misc.ts: -------------------------------------------------------------------------------- 1 | // import ref from 'ref' 2 | 3 | /** 4 | * Huan(202106): 5 | > class Test {} 6 | undefined 7 | > typeof Test 8 | 'function' 9 | > const t = new Test() 10 | undefined 11 | > typeof t 12 | 'object' 13 | */ 14 | function isInstance (target: any): boolean { 15 | switch (typeof target) { 16 | case 'function': // Class 17 | if (target.name) { 18 | return false 19 | } 20 | break 21 | case 'object': // instance 22 | if (!target.name) { 23 | return true 24 | } 25 | break 26 | } 27 | throw new Error('FIXME: Unknown state for target.') 28 | } 29 | 30 | export { 31 | isInstance, 32 | } 33 | -------------------------------------------------------------------------------- /src/frida-agent.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import type { SidecarPayloadHook } from './sidecar-body/payload-schemas.js' 3 | // import type { NativePointer } from '@types/frida-gum' 4 | 5 | /** 6 | * `args` at here is a Array, which is the arguments of the hooked function 7 | * It will be transformed to a Object internally 8 | */ 9 | declare const __sidecar__payloadHook: (method: string, args: any[]) => SidecarPayloadHook 10 | 11 | /** 12 | * declared in templates/agent.mustache 13 | */ 14 | // Huan(202109): `NativePointer` can not be found by eslint??? (But vscode can recognize it as well) 15 | // eslint-disable-next-line 16 | declare const __sidecar__moduleBaseAddress: NativePointer 17 | -------------------------------------------------------------------------------- /src/agent/templates/interceptors.mustache: -------------------------------------------------------------------------------- 1 | /********************************************************* 2 | * File: "templates/interceptors.mustache" 3 | * 4 | * Interceptors List: automaticated generated by Sidecar 5 | * template file: "interceptors.mustache" 6 | * 7 | * Author: Huan 8 | * https://github.com/huan/sidecar 9 | *********************************************************/ 10 | {{# interceptorList }} 11 | 12 | {{# address }} 13 | {{> interceptors-address.mustache }} 14 | {{/ address }} 15 | 16 | {{# agent }} 17 | {{> interceptors-agent.mustache }} 18 | {{/ agent}} 19 | 20 | {{# export }} 21 | {{> interceptors-export.mustache }} 22 | {{/ export}} 23 | 24 | {{/ interceptorList}} 25 | -------------------------------------------------------------------------------- /src/decorators/sidecar/metadata-sidecar.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { getSidecarMetadataFixture } from '../../../tests/fixtures/sidecar-metadata.fixture.js' 5 | 6 | import { 7 | getMetadataSidecar, 8 | updateMetadataSidecar, 9 | } from './metadata-sidecar.js' 10 | 11 | test('update & get view metadata', async t => { 12 | const VALUE = getSidecarMetadataFixture() 13 | const TARGET = {} 14 | 15 | updateMetadataSidecar( 16 | TARGET, 17 | VALUE, 18 | ) 19 | 20 | const data = getMetadataSidecar( 21 | TARGET, 22 | ) 23 | 24 | t.same(data, VALUE, 'should get the view data the same as we set(update)') 25 | }) 26 | -------------------------------------------------------------------------------- /src/agent/templates/native-functions.mustache: -------------------------------------------------------------------------------- 1 | /************************************************************ 2 | * File: "native-functions.mustache" 3 | * --------------------------------- 4 | * Native Function List: automaticated generated by Sidecar 5 | * 6 | * Author: Huan 7 | * https://github.com/huan/sidecar 8 | ************************************************************/ 9 | {{# nativeFunctionList }} 10 | 11 | {{# address }} 12 | {{> native-functions-address.mustache }} 13 | {{/ address}} 14 | 15 | {{# agent }} 16 | {{> native-functions-agent.mustache }} 17 | {{/ agent}} 18 | 19 | {{# export }} 20 | {{> native-functions-export.mustache }} 21 | {{/ export}} 22 | 23 | {{/ nativeFunctionList}} 24 | -------------------------------------------------------------------------------- /src/cli/extract-class-names.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import type { URL } from 'url' 3 | 4 | async function extractClassNameList ( 5 | url: URL, 6 | ): Promise { 7 | const content = await fs.readFile(url, 'utf-8') 8 | return extractClassNameListFromSource(content) 9 | } 10 | 11 | async function extractClassNameListFromSource ( 12 | source: string, 13 | ): Promise { 14 | /** 15 | * Extract the @Sidecar decorated classes 16 | */ 17 | const REGEXP = /@Sidecar\s*\(.*?\)\s*(?:export)?\s*class\s+([A-Za-z0-9\-_]+)\s+/sg 18 | 19 | return Array.from( 20 | source.matchAll(REGEXP), 21 | ).map(m => m[1]!) 22 | } 23 | 24 | export { 25 | extractClassNameListFromSource, 26 | extractClassNameList, 27 | } 28 | -------------------------------------------------------------------------------- /src/decorators/call/metadata-call.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | getMetadataCall, 6 | updateMetadataCall, 7 | } from './metadata-call.js' 8 | 9 | test('update & get call target metadata', async t => { 10 | const PROPERTY_KEY = 'key' 11 | const TARGET = { 12 | [PROPERTY_KEY]: () => {}, 13 | } 14 | const CALL_TARGET = 0x42 15 | 16 | updateMetadataCall( 17 | TARGET, 18 | PROPERTY_KEY, 19 | CALL_TARGET, 20 | ) 21 | 22 | const data = getMetadataCall( 23 | TARGET, 24 | PROPERTY_KEY, 25 | ) 26 | 27 | t.same(data, CALL_TARGET, 'should get the call target data the same as we set(update') 28 | }) 29 | -------------------------------------------------------------------------------- /src/wrappers/native-args.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 2 | import { nativeArgName } from './name-helpers.js' 3 | 4 | function nativeArgs ( 5 | this: SidecarMetadataFunctionDescription, 6 | ): string { 7 | const name = this.name 8 | const paramTypeList = this.paramTypeList 9 | 10 | /** 11 | * There's no any parameters needed 12 | */ 13 | if (!Array.isArray(paramTypeList)) { 14 | return '' 15 | } 16 | 17 | const nativeArgNameList = [] 18 | 19 | for (let i = 0; i < paramTypeList.length; i++) { 20 | nativeArgNameList.push( 21 | nativeArgName(name, i), 22 | ) 23 | } 24 | 25 | return '[ ' + nativeArgNameList.join(', ') + ' ]' 26 | } 27 | 28 | export { nativeArgs } 29 | -------------------------------------------------------------------------------- /examples/archive/raw/messaging.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char buf[100] = {0}; 5 | int counter = 0; 6 | 7 | char* message (const char* type) { 8 | sprintf(buf, "Messaging: %s message#%d", type, counter); 9 | return buf; 10 | } 11 | 12 | void mo (char* content) { 13 | printf("> %s\n", content); 14 | } 15 | 16 | void mt (char* content) { 17 | printf("<< %s\n", content); 18 | } 19 | 20 | int main() { 21 | printf("mo() is at %p\n", mo); 22 | printf("mt() is at %p\n", mt); 23 | 24 | mo("Messaging demo started."); 25 | 26 | while(++counter) { 27 | mt(message("Receive")); 28 | 29 | if (counter % 3 == 0) { 30 | mo(message("Send")); 31 | } 32 | 33 | sleep(3); 34 | } 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/archive/raw-events/messaging.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | char buf[100] = {0}; 5 | int counter = 0; 6 | 7 | char* message (const char* type) { 8 | sprintf(buf, "Messaging: %s message#%d", type, counter); 9 | return buf; 10 | } 11 | 12 | void mo (char* content) { 13 | printf("> %s\n", content); 14 | } 15 | 16 | void mt (char* content) { 17 | printf("<< %s\n", content); 18 | } 19 | 20 | int main() { 21 | printf("mo() is at %p\n", mo); 22 | printf("mt() is at %p\n", mt); 23 | 24 | mo("Messaging demo started."); 25 | 26 | while(++counter) { 27 | mt(message("Receive")); 28 | 29 | if (counter % 3 == 0) { 30 | mo(message("Send")); 31 | } 32 | 33 | sleep(3); 34 | } 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /examples/dynamic-library/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main (int argc, char** argv) { 7 | void *handle; 8 | uint64_t (*factorial)(int); 9 | char *error; 10 | 11 | handle = dlopen("libfactorial.dylib", RTLD_LAZY); 12 | if (!handle) { 13 | fprintf(stderr, "%s\n", dlerror()); 14 | exit(EXIT_FAILURE); 15 | } 16 | 17 | dlerror(); /* Clear any existing error */ 18 | 19 | factorial = (uint64_t (*)(int)) dlsym(handle, "factorial"); 20 | 21 | error = dlerror(); 22 | if (error != NULL) { 23 | fprintf(stderr, "%s\n", error); 24 | exit(EXIT_FAILURE); 25 | } 26 | 27 | printf("%llu\n", (*factorial)(3)); 28 | dlclose(handle); 29 | exit(EXIT_SUCCESS); 30 | } 31 | -------------------------------------------------------------------------------- /src/sidecar-body/sidecar-body.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | SidecarBody, 6 | } from './sidecar-body.js' 7 | 8 | /** 9 | * Huan(202106): 10 | * Should we support the multi-instance of Sidecar, 11 | * or NOT? 12 | */ 13 | test.skip('SidecarBody enforce singleton', async t => { 14 | 15 | class SidecarTest extends SidecarBody {} 16 | 17 | const s1 = new SidecarTest() 18 | const s2 = new SidecarTest() 19 | 20 | t.equal(s1, s2, 'should be the same instance of SidecarBody') 21 | }) 22 | 23 | test('Class intance constructor should be the Class Function', async t => { 24 | class Test {} 25 | const test = new Test() 26 | 27 | t.ok(test.constructor === Test, 'should be equal') 28 | }) 29 | -------------------------------------------------------------------------------- /examples/dynamic-library/factorial.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Credit: https://github.com/node-ffi/node-ffi/tree/master/example/factorial 3 | * 4 | * To compile `libfactorial.dll` on Windows (http://stackoverflow.com/a/2220213): 5 | * 6 | * ```sh 7 | * cl.exe /D_USRDLL /D_WINDLL factorial.c /link /DLL /OUT:libfactorial.dll 8 | * ``` 9 | * 10 | * To run the example: 11 | * 12 | * ```bash 13 | * $ node factorial.js 35 14 | * Your output: 6399018521010896896 15 | * ``` 16 | * 17 | */ 18 | #include 19 | 20 | #if defined(WIN32) || defined(_WIN32) 21 | #define EXPORT __declspec(dllexport) 22 | #else 23 | #define EXPORT 24 | #endif 25 | 26 | EXPORT uint64_t factorial(int max) { 27 | int i = max; 28 | uint64_t result = 1; 29 | 30 | while (i >= 2) { 31 | result *= i--; 32 | } 33 | 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /src/wrappers/js-ret.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { getSidecarMetadataFixture } from '../../tests/fixtures/sidecar-metadata.fixture.js' 5 | import { jsRet } from './js-ret.js' 6 | 7 | test('jsRet()', async t => { 8 | const SIDECAR_VIEW = getSidecarMetadataFixture() 9 | 10 | const nativeFunctionList = SIDECAR_VIEW.nativeFunctionList.map(x => Object.values(x)).flat() 11 | 12 | const EXPECTED_RET_LIST = [ 13 | 'ret.readPointer().readInt()', 14 | 'ret.readPointer().readUtf8String()', 15 | 'ret', 16 | 'undefined /* void */', 17 | 'ret', 18 | ] 19 | 20 | const result = nativeFunctionList 21 | .map(x => jsRet.call(x)) 22 | 23 | // console.log(result) 24 | t.same(result, EXPECTED_RET_LIST, 'should wrap the ret correct') 25 | }) 26 | -------------------------------------------------------------------------------- /src/decorators/param-type/metadata-param-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { TypeChain } from '../../frida.js' 4 | 5 | import { 6 | getMetadataParamType, 7 | updateMetadataParamType, 8 | } from './metadata-param-type.js' 9 | 10 | test('update & get parame type metadata', async t => { 11 | const PROPERTY_KEY = 'key' 12 | const TARGET = { 13 | [PROPERTY_KEY]: () => {}, 14 | } 15 | const VALUE = [['pointer', 'Utf8String']] as TypeChain[] 16 | 17 | updateMetadataParamType( 18 | TARGET, 19 | PROPERTY_KEY, 20 | 0, 21 | VALUE[0]!, 22 | ) 23 | 24 | const data = getMetadataParamType( 25 | TARGET, 26 | PROPERTY_KEY, 27 | ) 28 | 29 | t.same(data, VALUE, 'should get the parameter type data the same as we set(update)') 30 | }) 31 | -------------------------------------------------------------------------------- /src/decorators/ret-type/metadata-ret-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { 4 | NativeType, 5 | PointerType, 6 | } from '../../frida.js' 7 | 8 | import { 9 | getMetadataRetType, 10 | updateMetadataRetType, 11 | } from './metadata-ret-type.js' 12 | 13 | test('update & get ret type metadata', async t => { 14 | const PROPERTY_KEY = 'key' 15 | const TARGET = { 16 | [PROPERTY_KEY]: () => {}, 17 | } 18 | const VALUE = ['pointer', 'Utf8String'] as [NativeType, ...PointerType[]] 19 | 20 | updateMetadataRetType( 21 | TARGET, 22 | PROPERTY_KEY, 23 | VALUE, 24 | ) 25 | 26 | const data = getMetadataRetType( 27 | TARGET, 28 | PROPERTY_KEY, 29 | ) 30 | 31 | t.same(data, VALUE, 'should get the ret type data the same as we set(update)') 32 | }) 33 | -------------------------------------------------------------------------------- /src/agent/build-agent-source.ts: -------------------------------------------------------------------------------- 1 | import Mustache from 'mustache' 2 | 3 | import type { SidecarMetadata } from '../decorators/sidecar/metadata-sidecar.js' 4 | 5 | import { wrapView } from '../wrappers/mod.js' 6 | 7 | import { log } from '../config.js' 8 | 9 | import { partialLookup } from './partial-lookup.js' 10 | 11 | const AGENT_MUSTACHE = 'agent.mustache' 12 | 13 | async function buildAgentSource (metadata: SidecarMetadata) { 14 | log.verbose('Sidecar', 'buildAgentSource("%s...")', JSON.stringify(metadata).substr(0, 20)) 15 | // log.silly('Sidecar', 'buildAgentSource(%s)', JSON.stringify(metadata)) 16 | 17 | const agentMustache = partialLookup(AGENT_MUSTACHE) 18 | const view = wrapView(metadata) 19 | 20 | const source = await Mustache.render( 21 | agentMustache, 22 | view, 23 | partialLookup, 24 | ) 25 | 26 | return source 27 | } 28 | 29 | export { buildAgentSource } 30 | -------------------------------------------------------------------------------- /src/wrappers/native-ret-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { getSidecarMetadataFixture } from '../../tests/fixtures/sidecar-metadata.fixture.js' 5 | 6 | import { 7 | nativeRetType, 8 | } from './native-ret-type.js' 9 | 10 | test('nativeRetType()', async t => { 11 | 12 | const fixture = getSidecarMetadataFixture() 13 | 14 | // console.log(JSON.stringify(fixture.nativeFunctionList, null, 2)) 15 | const result = fixture.nativeFunctionList 16 | .map(x => Object.values(x)) 17 | .flat() 18 | .map(x => nativeRetType.call(x)) 19 | 20 | // console.log(result) 21 | const EXPECTED_RESULT = [ 22 | "'pointer'", 23 | "'pointer'", 24 | "'pointer'", 25 | "'void'", 26 | "'void'", 27 | ] 28 | t.same(result, EXPECTED_RESULT, 'should list the native ret type correctly.') 29 | }) 30 | -------------------------------------------------------------------------------- /src/wrappers/module-name.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import type { SidecarMetadata } from '../decorators/mod.js' 3 | import { 4 | isSidecarTargetProcess, 5 | isSidecarTargetSpawn, 6 | } from '../decorators/sidecar/target.js' 7 | 8 | function moduleName ( 9 | this: SidecarMetadata, 10 | ) { 11 | const targetObj = this.sidecarTarget 12 | if (!targetObj) { 13 | throw new Error('no target found in SidecarMetadata') 14 | } 15 | 16 | if (isSidecarTargetProcess(targetObj)) { 17 | return typeof targetObj.target === 'number' 18 | ? targetObj.target 19 | : path.win32.basename(targetObj.target) 20 | } else if (isSidecarTargetSpawn(targetObj)) { 21 | // See: https://nodejs.org/api/path.html 22 | return path.win32.basename(targetObj.target[0]) 23 | } else { 24 | throw new Error('unknown target obj: ' + JSON.stringify(targetObj)) 25 | } 26 | 27 | } 28 | 29 | export { moduleName } 30 | -------------------------------------------------------------------------------- /src/wrappers/native-args.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { getSidecarMetadataFixture } from '../../tests/fixtures/sidecar-metadata.fixture.js' 5 | 6 | import { 7 | nativeArgs, 8 | } from './native-args.js' 9 | 10 | test('nativeArgs()', async t => { 11 | 12 | const fixture = getSidecarMetadataFixture() 13 | 14 | // console.log(fixture.nativeFunctionList.length) 15 | const result = fixture.nativeFunctionList 16 | .map(x => Object.values(x)) 17 | .flat() 18 | .map(x => nativeArgs.call(x!)) 19 | 20 | const EXPECTED_RESULT = [ 21 | '[ anotherCall_NativeArg_0, anotherCall_NativeArg_1 ]', 22 | '[ testMethod_NativeArg_0, testMethod_NativeArg_1 ]', 23 | '[ pointerMethod_NativeArg_0 ]', 24 | '[ ]', 25 | '[ ]', 26 | ] 27 | t.same(result, EXPECTED_RESULT, 'should list the native arg names correctly.') 28 | }) 29 | -------------------------------------------------------------------------------- /src/wrappers/native-param-types.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { getSidecarMetadataFixture } from '../../tests/fixtures/sidecar-metadata.fixture.js' 5 | 6 | import { 7 | nativeParamTypes, 8 | } from './native-param-types.js' 9 | 10 | test('nativeParamTypes()', async t => { 11 | 12 | const fixture = getSidecarMetadataFixture() 13 | 14 | // console.log(JSON.stringify(fixture.nativeFunctionList, null, 2)) 15 | const result = fixture.nativeFunctionList 16 | .map(x => Object.values(x)) 17 | .flat() 18 | .map(x => nativeParamTypes.call(x)) 19 | 20 | // console.log(result) 21 | const EXPECTED_RESULT = [ 22 | "[ 'pointer', 'pointer' ]", 23 | "[ 'pointer', 'int' ]", 24 | "[ 'pointer' ]", 25 | '[]', 26 | '[]', 27 | ] 28 | t.same(result, EXPECTED_RESULT, 'should list the native param types correctly.') 29 | }) 30 | -------------------------------------------------------------------------------- /src/cli/source.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | import { 3 | command, 4 | option, 5 | optional, 6 | positional, 7 | string, 8 | } from 'cmd-ts' 9 | import { File } from 'cmd-ts/dist/cjs/batteries/fs.js' 10 | 11 | import { sourceHandler } from './source-handler.js' 12 | 13 | async function handler (args: any) { 14 | const result = await sourceHandler(args) 15 | console.log(result) 16 | } 17 | 18 | const source = command({ 19 | name: 'source', 20 | description: 'Dump sidecar agent source', 21 | args: { 22 | file: positional({ 23 | type : File, 24 | displayName : 'classFile', 25 | description : 'The file contains the sidecar class', 26 | }), 27 | name: option({ 28 | description: 'The name of class that decorated by @Sidecar', 29 | long: 'name', 30 | short: 'n', 31 | type: optional(string), 32 | }), 33 | }, 34 | handler, 35 | }) 36 | 37 | export { source } 38 | -------------------------------------------------------------------------------- /src/sidecar-body/payload-schemas.ts: -------------------------------------------------------------------------------- 1 | export interface SidecarPayloadLog { 2 | type : 'log' 3 | payload : { 4 | level : string, 5 | message : string, 6 | prefix : string, 7 | } 8 | } 9 | 10 | export interface SidecarPayloadHook { 11 | type : 'hook', 12 | payload : { 13 | method : string, 14 | args: { 15 | [k: string]: null | string | number 16 | }, 17 | // data?: null | Buffer, 18 | } 19 | } 20 | 21 | export type SidecarPayload = SidecarPayloadHook 22 | | SidecarPayloadLog 23 | 24 | export type SidecarPayloadType = SidecarPayload['type'] 25 | 26 | const isSidecarPayloadLog = ( 27 | payload: SidecarPayload, 28 | ): payload is SidecarPayloadLog => payload.type === 'log' 29 | 30 | const isSidecarPayloadHook = ( 31 | payload: SidecarPayload, 32 | ): payload is SidecarPayloadHook => payload.type === 'hook' 33 | 34 | export { 35 | isSidecarPayloadHook, 36 | isSidecarPayloadLog, 37 | } 38 | -------------------------------------------------------------------------------- /src/agent/templates/native-functions.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import Mustache from 'mustache' 5 | 6 | import { 7 | partialLookup, 8 | } from '../partial-lookup.js' 9 | 10 | import { getSidecarMetadataFixture } from '../../../tests/fixtures/sidecar-metadata.fixture.js' 11 | 12 | import { wrapView } from '../../wrappers/mod.js' 13 | 14 | test('native-functions.mustache', async t => { 15 | 16 | const SIDECAR_METADATA = getSidecarMetadataFixture() 17 | 18 | const view = wrapView(SIDECAR_METADATA) 19 | 20 | // console.log(view.nativeFunctionList) 21 | const template = await partialLookup('native-functions.mustache') 22 | 23 | // console.log(template) 24 | const result = Mustache.render(template, view) 25 | 26 | /** 27 | * Huan(202106): how could we test this script has been correctly generated? 28 | */ 29 | t.ok(result, 'should render to the right script (TBW)') 30 | }) 31 | -------------------------------------------------------------------------------- /src/cli/metadata.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | import { 3 | command, 4 | option, 5 | optional, 6 | positional, 7 | string, 8 | } from 'cmd-ts' 9 | import { File } from 'cmd-ts/dist/cjs/batteries/fs.js' 10 | 11 | import { metadataHandler } from './metadata-handler.js' 12 | 13 | async function handler (args: any) { 14 | const result = await metadataHandler(args) 15 | // print the result 16 | console.log(result) 17 | } 18 | 19 | const metadata = command({ 20 | name: 'metadata', 21 | description: 'Dump sidecar metadata', 22 | args: { 23 | file: positional({ 24 | type : File, 25 | displayName : 'classFile', 26 | description: 'The file contains the sidecar class', 27 | }), 28 | name: option({ 29 | description: 'The name of class that decorated by @Sidecar', 30 | long: 'name', 31 | short: 'n', 32 | type: optional(string), 33 | }), 34 | }, 35 | 36 | handler, 37 | }) 38 | 39 | export { metadata } 40 | -------------------------------------------------------------------------------- /tests/fixtures/sidecar-dump.metadata.smoke-testing.json.fixture: -------------------------------------------------------------------------------- 1 | { 2 | "interceptorList": [ 3 | { 4 | "address": { 5 | "name": "mt", 6 | "paramTypeList": [ 7 | [ 8 | "pointer", 9 | "Utf8String" 10 | ] 11 | ], 12 | "target": { 13 | "address": "0x5678", 14 | "moduleName": null, 15 | "type": "address" 16 | } 17 | } 18 | } 19 | ], 20 | "nativeFunctionList": [ 21 | { 22 | "address": { 23 | "name": "mo", 24 | "paramTypeList": [ 25 | [ 26 | "pointer", 27 | "Utf8String" 28 | ] 29 | ], 30 | "retType": [ 31 | "void" 32 | ], 33 | "target": { 34 | "address": "0x1234", 35 | "moduleName": null, 36 | "type": "address" 37 | } 38 | } 39 | } 40 | ], 41 | "sidecarTarget": { 42 | "target": "test", 43 | "type": "process" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/agent/templates/interceptors.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import Mustache from 'mustache' 5 | 6 | import { 7 | partialLookup, 8 | } from '../partial-lookup.js' 9 | 10 | import { getSidecarMetadataFixture } from '../../../tests/fixtures/sidecar-metadata.fixture.js' 11 | 12 | import { wrapView } from '../../wrappers/mod.js' 13 | 14 | test('interceptors.mustache', async t => { 15 | 16 | const SIDECAR_VIEW = getSidecarMetadataFixture() 17 | 18 | const view = wrapView(SIDECAR_VIEW) 19 | 20 | // console.log(JSON.stringify(view.interceptorList, null, 2)) 21 | const template = await partialLookup('interceptors.mustache') 22 | 23 | // console.log(template) 24 | const result = Mustache.render(template, view) 25 | // console.log(result) 26 | 27 | /** 28 | * Huan(202106): how could we test this script has been correctly generated? 29 | */ 30 | t.ok(result, 'should render to the right script (TBW)') 31 | }) 32 | -------------------------------------------------------------------------------- /examples/archive/raw/script-message-handler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ScriptMessageHandler, 3 | MessageType, 4 | } from './frida.js' 5 | import { log } from 'brolog' 6 | 7 | const scriptMessageHandler: ScriptMessageHandler = (message, data) => { 8 | log.verbose('Sidecar', 'scriptMessageHandler(%s, %s)', JSON.stringify(message), data) 9 | switch (message.type) { 10 | case MessageType.Send: 11 | log.silly('Sidecar', 12 | 'scriptMessagerHandler() MessageType.Send: %s', 13 | JSON.stringify(message.payload), 14 | ) 15 | break 16 | case MessageType.Error: 17 | log.silly('Sidecar', 18 | 'scriptMessagerHandler() MessageType.Error: %s', 19 | message.stack, 20 | ) 21 | break 22 | 23 | default: 24 | throw new Error('Sidecar/scriptMessagerHandler() Error: unknown message type: ' + message) 25 | } 26 | 27 | if (data) { 28 | log.silly('Sidecar', 'scriptMessageHandler() data:', data) 29 | } 30 | } 31 | 32 | export { scriptMessageHandler } 33 | -------------------------------------------------------------------------------- /src/wrappers/mod.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadata } from '../decorators/sidecar/metadata-sidecar.js' 2 | 3 | import { declareJsArgs } from './declare-js-args.js' 4 | import { declareNativeArgs } from './declare-native-args.js' 5 | import { jsArgs } from './js-args.js' 6 | import { jsRet } from './js-ret.js' 7 | import { logLevel } from './log-level.js' 8 | import { moduleName } from './module-name.js' 9 | import { nativeArgs } from './native-args.js' 10 | import { nativeFunctionNameList } from './native-function-name-list.js' 11 | import { nativeParamTypes } from './native-param-types.js' 12 | import { nativeRetType } from './native-ret-type.js' 13 | 14 | const wrapView = (metadata: SidecarMetadata) => ({ 15 | ...metadata, 16 | declareJsArgs, 17 | declareNativeArgs, 18 | jsArgs, 19 | jsRet, 20 | logLevel, 21 | moduleName, 22 | nativeArgs, 23 | nativeFunctionNameList, 24 | nativeParamTypes, 25 | nativeRetType, 26 | }) 27 | 28 | export { wrapView } 29 | -------------------------------------------------------------------------------- /src/agent/templates/libs/log.cjs.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SidecarPayloadHook, 3 | SidecarPayloadLog, 4 | } from '../../../sidecar-body/payload-schemas.js' 5 | 6 | /* eslint camelcase: 0 */ 7 | declare module './log.cjs' { 8 | 9 | export declare namespace log { 10 | 11 | export function error ( 12 | prefix: string, 13 | message: string, 14 | ...args: any[], 15 | ) 16 | 17 | export function warn ( 18 | prefix: string, 19 | message: string, 20 | ...args: any[], 21 | ) 22 | 23 | export function info ( 24 | prefix: string, 25 | message: string, 26 | ...args: any[], 27 | ) 28 | 29 | export function verbose ( 30 | prefix: string, 31 | message: string, 32 | ...args: any[], 33 | ) 34 | 35 | export function silly ( 36 | prefix: string, 37 | message: string, 38 | ...args: any[], 39 | ) 40 | 41 | export function level ( 42 | newLevel: number | 'silent' | 'error' | 'warn' | 'info' | 'verbose' | 'silly' 43 | ) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/decorators/ret-type/metadata-ret-type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | TypeChain, 3 | } from '../../frida.js' 4 | import { 5 | log, 6 | } from '../../config.js' 7 | 8 | import { RET_TYPE_SYMBOL } from './constants.js' 9 | 10 | function updateMetadataRetType ( 11 | target : any, 12 | propertyKey : string, 13 | typeChain : TypeChain, 14 | ): void { 15 | log.verbose('Sidecar', 'updateMetadataRetType(%s, %s, %s)', 16 | target.name, 17 | propertyKey, 18 | JSON.stringify(typeChain), 19 | ) 20 | // Update the parameter names 21 | Reflect.defineMetadata( 22 | RET_TYPE_SYMBOL, 23 | typeChain, 24 | target, 25 | propertyKey, 26 | ) 27 | } 28 | 29 | function getMetadataRetType ( 30 | target : Object, 31 | propertyKey : string, 32 | ): undefined | TypeChain { 33 | // Pull the array of parameter names 34 | const retTypeChain = Reflect.getMetadata( 35 | RET_TYPE_SYMBOL, 36 | target, 37 | propertyKey, 38 | ) 39 | return retTypeChain 40 | } 41 | 42 | export { 43 | getMetadataRetType, 44 | updateMetadataRetType, 45 | } 46 | -------------------------------------------------------------------------------- /src/decorators/hook/hook.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { FunctionTarget } from '../../function-target.js' 4 | 5 | import { 6 | Hook, 7 | getMetadataHook, 8 | HOOK_TARGET_SYMBOL, 9 | } from './hook.js' 10 | 11 | test('Hook with metadata', async t => { 12 | const TARGET: FunctionTarget = 0x42 13 | 14 | class Test { 15 | 16 | @Hook(TARGET) method () {} 17 | 18 | } 19 | 20 | const instance = new Test() 21 | const data = Reflect.getMetadata( 22 | HOOK_TARGET_SYMBOL, 23 | instance, 24 | 'method', 25 | ) 26 | 27 | /* eslint-disable no-sparse-arrays */ 28 | t.same(data, TARGET, 'should get the hook target data') 29 | }) 30 | 31 | test('getHookTarget()', async t => { 32 | const TARGET: FunctionTarget = 0x42 33 | 34 | class Test { 35 | 36 | @Hook(TARGET) method () {} 37 | 38 | } 39 | 40 | const instance = new Test() 41 | 42 | const data = getMetadataHook( 43 | instance, 44 | 'method', 45 | ) 46 | 47 | t.same(data, TARGET, 'should get hook target data') 48 | }) 49 | -------------------------------------------------------------------------------- /src/agent/templates/libs/log.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /* eslint-disable camelcase */ 3 | import { 4 | test, 5 | sinon, 6 | } from 'tstest' 7 | 8 | import cjsPayloadPkg from './payload.cjs' 9 | import cjsLogPkg from './log.cjs' 10 | 11 | const { __sidecar__payloadLog } = cjsPayloadPkg 12 | const { log } = cjsLogPkg 13 | 14 | // FIXME: Huan(202107) do not modify global settings 15 | ;(global as any)['__sidecar__payloadLog'] = __sidecar__payloadLog 16 | 17 | test('log()', async t => { 18 | const spy = sinon.spy() 19 | /** 20 | * Frida `send` method 21 | */ 22 | global['send'] = spy 23 | 24 | log.level('verbose') 25 | log.verbose('Test', 'message: %s', 'hello') 26 | log.silly('Test', 'message: %s', 'should not be seen') 27 | 28 | const EXPECTED = { 29 | payload: { 30 | level: 'verbose', 31 | message: 'message: hello', 32 | prefix: 'Test', 33 | }, 34 | type: 'log', 35 | } 36 | t.equal(spy.callCount, 1, 'should call spy only one') 37 | t.same(spy.args[0]![0], EXPECTED, 'should get correct payload event') 38 | }) 39 | -------------------------------------------------------------------------------- /src/wrappers/module-name.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { SidecarTargetObjSpawn } from '../decorators/sidecar/target.js' 4 | 5 | import { moduleName } from './module-name.js' 6 | 7 | test('moduleName() spawn with linux path', async t => { 8 | const DATA = { 9 | target: [ 10 | '/usr/bin/command', 11 | ['arg1'], 12 | ], 13 | type: 'spawn', 14 | } as SidecarTargetObjSpawn 15 | const EXPECT = 'command' 16 | const result = moduleName.call({ 17 | sidecarTarget: DATA, 18 | } as any) 19 | t.equal(result, EXPECT, 'should get module name from spawn for linux path') 20 | }) 21 | 22 | test('moduleName() spawn with windows path', async t => { 23 | const DATA = { 24 | target: [ 25 | 'C:\\Program Files\\folder\\command.exe', 26 | ['arg1'], 27 | ], 28 | type: 'spawn', 29 | } as SidecarTargetObjSpawn 30 | const EXPECT = 'command.exe' 31 | const result = moduleName.call({ 32 | sidecarTarget: DATA, 33 | } as any) 34 | t.equal(result, EXPECT, 'should get module name from spawn for windows path') 35 | }) 36 | -------------------------------------------------------------------------------- /src/agent/templates/interceptors-address.mustache: -------------------------------------------------------------------------------- 1 | /****************************************************************** 2 | * File: "interceptors-address.mustache" 3 | * 4 | * Interceptor Target: {{ name }} 5 | * - type: {{ target.type }} 6 | * - address: {{ target.address }} 7 | * - Parameters: {{{ nativeParamTypes }}} 8 | ******************************************************************/ 9 | 10 | ;(() => { 11 | const interceptorTarget = 12 | {{# target.moduleName }} 13 | Module.getBaseAddress('{{ target.moduleName }}') 14 | {{/ target.moduleName}} 15 | {{^ target.moduleName }} 16 | __sidecar__moduleBaseAddress 17 | {{/ target.moduleName}} 18 | .add({{ target.address }}) 19 | 20 | Interceptor.attach( 21 | interceptorTarget, 22 | { 23 | onEnter: args => { 24 | log.verbose( 25 | 'SidecarAgent', 26 | 'Interceptor.attach(0x%s) onEnter()', 27 | Number({{ target.address }}).toString(16), 28 | ) 29 | 30 | {{{ declareJsArgs }}} 31 | 32 | send(__sidecar__payloadHook( 33 | '{{ name }}', 34 | {{{ jsArgs }}} 35 | ), null) 36 | 37 | }, 38 | } 39 | ) 40 | })() 41 | -------------------------------------------------------------------------------- /examples/archive/win32/agent-message-box.ts: -------------------------------------------------------------------------------- 1 | const addressMessageBox = Module.findExportByName('user32.dll', 'MessageBoxW')! 2 | if (!addressMessageBox) { 3 | throw new Error('no messageBox found') 4 | } 5 | 6 | function test (): void { 7 | 8 | const text = '中文' 9 | const textBuf = Memory.allocUtf16String(text) 10 | console.log('textBuf:', textBuf) 11 | console.log('textBuf:', textBuf.readUtf16String()) 12 | console.log('addressMessageBox:', addressMessageBox) 13 | 14 | console.log(hexdump(textBuf, { 15 | offset: 0, 16 | length: 16, 17 | header: true, 18 | ansi: true, 19 | })) 20 | 21 | const implBox = Memory.alloc(Process.pageSize); 22 | 23 | Memory.patchCode(implBox, Process.pageSize, function (code) { 24 | var cw = new X86Writer(code, { pc: implBox }) 25 | 26 | cw.putPushU32(1) 27 | cw.putPushU32(textBuf.toInt32()) 28 | cw.putPushU32(textBuf.toInt32()) 29 | cw.putPushU32(0) 30 | 31 | cw.putCallAddress(addressMessageBox) 32 | cw.putRet() 33 | 34 | cw.flush() 35 | }) 36 | 37 | const testBox = new NativeFunction(implBox, 'uint', []) 38 | const ret = testBox() 39 | console.log('ret:', ret) 40 | } 41 | 42 | void test 43 | export {} 44 | -------------------------------------------------------------------------------- /src/agent/templates/native-functions-address.mustache: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | * File: "native-function-address.mustache" 3 | * 4 | * Native Function: {{ name }} 5 | * - address: {{ target.address }} 6 | * - Parameters: {{{ nativeParamTypes }}} 7 | * - Ret: {{ retType }} 8 | ******************************************************************/ 9 | 10 | const __sidecar__{{ name }}_Function_wrapper = (() => { 11 | const nativeFunctionAddress = 12 | {{# target.moduleName }} 13 | Module.getBaseAddress('{{ target.moduleName }}') 14 | {{/ target.moduleName}} 15 | {{^ target.moduleName }} 16 | __sidecar__moduleBaseAddress 17 | {{/ target.moduleName}} 18 | .add({{ target.address }}) 19 | 20 | const nativeFunction = new NativeFunction( 21 | nativeFunctionAddress, 22 | {{{ nativeRetType }}}, 23 | {{{ nativeParamTypes }}}, 24 | ) 25 | 26 | return function (...args) { 27 | log.verbose( 28 | 'SidecarAgent', 29 | '{{ name }}(%s)', 30 | args.join(', '), 31 | ) 32 | 33 | {{{ declareNativeArgs }}} 34 | 35 | const ret = nativeFunction(...{{{ nativeArgs }}}) 36 | return {{{ jsRet }}} 37 | } 38 | 39 | })() 40 | -------------------------------------------------------------------------------- /src/agent/templates/libs/payload.cjs: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | * File: templates/lib/payload.cjs 3 | * 4 | * To make sure the payload typing is right 5 | * See: sidecar-body/payload-schema.ts 6 | ********************************************/ 7 | /** 8 | * SidecarPayloadHook 9 | */ 10 | const __sidecar__payloadHook = ( 11 | method, // string 12 | args, // Arguments, Array 13 | ) => ({ 14 | payload: { 15 | /** 16 | * Convert `args` from Array to Object 17 | * to satisfy `SidecarPayloadHook` interface 18 | */ 19 | args: { 20 | ...args, 21 | }, 22 | method, 23 | }, 24 | type: 'hook', 25 | }) 26 | 27 | /** 28 | * SidecarPayloadLog 29 | */ 30 | const __sidecar__payloadLog = ( 31 | level, // error, warn, info, verbose, silly 32 | prefix, // module name 33 | message, // string 34 | ) => ({ 35 | payload: { 36 | level, 37 | message, 38 | prefix, 39 | }, 40 | type : 'log', 41 | }) 42 | 43 | /** 44 | * For unit testing under Node.js 45 | */ 46 | if (typeof module !== 'undefined' && module.exports) { 47 | module.exports = { 48 | ...module.exports, 49 | __sidecar__payloadHook, 50 | __sidecar__payloadLog, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/decorators/param-type/metadata-param-type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | TypeChain, 3 | } from '../../frida.js' 4 | 5 | import { PARAM_TYPE_SYMBOL } from './constants.js' 6 | 7 | function updateMetadataParamType ( 8 | target : Object, 9 | propertyKey : string, 10 | parameterIndex : number, 11 | typeChain : TypeChain, 12 | ): void { 13 | // Pull the array of parameter names 14 | const parameterTypeList = Reflect.getOwnMetadata( 15 | PARAM_TYPE_SYMBOL, 16 | target, 17 | propertyKey, 18 | ) || [] 19 | // Add the current parameter name 20 | parameterTypeList[parameterIndex] = typeChain 21 | // Update the parameter names 22 | Reflect.defineMetadata( 23 | PARAM_TYPE_SYMBOL, 24 | parameterTypeList, 25 | target, 26 | propertyKey, 27 | ) 28 | } 29 | 30 | function getMetadataParamType ( 31 | target : Object, 32 | propertyKey : string, 33 | ): TypeChain[] { 34 | // Pull the array of parameter names 35 | const parameterTypeList = Reflect.getMetadata( 36 | PARAM_TYPE_SYMBOL, 37 | target, 38 | propertyKey, 39 | ) 40 | return parameterTypeList || [] 41 | } 42 | 43 | export { 44 | getMetadataParamType, 45 | updateMetadataParamType, 46 | } 47 | -------------------------------------------------------------------------------- /src/wrappers/declare-js-args.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 2 | 3 | import { 4 | argName, 5 | jsArgName, 6 | } from './name-helpers.js' 7 | 8 | function declareJsArgs ( 9 | this: SidecarMetadataFunctionDescription, 10 | ): string { 11 | const typeList = this.paramTypeList 12 | // if (!typeList) { 13 | // throw new Error('no .paramTypeList found in SidecarMetadataFunctionDescription!') 14 | // } 15 | 16 | const argDeclarationList = [] 17 | for (const [idx, typeChain] of typeList.entries()) { 18 | const [nativeType, ...pointerTypeList] = typeChain 19 | // console.log(nativeType, pointerTypeList) 20 | 21 | const readChain = [ 22 | argName(idx), 23 | ] 24 | 25 | /** 26 | * 1. native pointer 27 | */ 28 | if (nativeType === 'pointer') { 29 | for (const pointerType of pointerTypeList) { 30 | readChain.push( 31 | `.read${pointerType}()`, 32 | ) 33 | } 34 | } 35 | 36 | const declaration = 'const ' + jsArgName(this.name, idx) + ' = ' + readChain.join('') 37 | argDeclarationList.push(declaration) 38 | } 39 | 40 | return argDeclarationList.join('\n') 41 | } 42 | 43 | export { declareJsArgs } 44 | -------------------------------------------------------------------------------- /src/sidecar-body/sidecar-emitter.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import type TypedEventEmitter from 'typed-emitter' 3 | 4 | import type { 5 | ATTACH_SYMBOL, 6 | DETACH_SYMBOL, 7 | INIT_SYMBOL, 8 | } from './constants.js' 9 | import type { 10 | SidecarPayloadHook, 11 | SidecarPayloadLog, 12 | } from './payload-schemas.js' 13 | 14 | type AttachedEventListener = () => void 15 | type DetachedEventListener = () => void 16 | type InitedEventListener = () => void 17 | 18 | type ErrorEventListener = (e: Error) => void | Promise 19 | type HookEventListener = (payload: SidecarPayloadHook['payload']) => void | Promise 20 | type LogEventListener = (payload: SidecarPayloadLog['payload']) => void | Promise 21 | 22 | interface SidecarEvents { 23 | [ATTACH_SYMBOL] : AttachedEventListener 24 | [DETACH_SYMBOL] : DetachedEventListener 25 | [INIT_SYMBOL] : InitedEventListener 26 | 27 | error : ErrorEventListener 28 | hook : HookEventListener 29 | log : LogEventListener 30 | } 31 | 32 | type SidecarEmitterType = new () => TypedEventEmitter< 33 | SidecarEvents 34 | > 35 | 36 | const SidecarEmitter = EventEmitter as any as SidecarEmitterType 37 | 38 | export { SidecarEmitter } 39 | -------------------------------------------------------------------------------- /examples/dynamic-library/README.md: -------------------------------------------------------------------------------- 1 | # How To Compile a Shared Library 2 | 3 | Credit: 4 | 5 | To compile `libfactorial.dylib` on OS X: 6 | 7 | > See: 8 | 9 | ``` bash 10 | gcc -dynamiclib -undefined suppress -flat_namespace factorial.c -o libfactorial-arm64e.dylib -target arm64e-apple-macos11 11 | gcc -dynamiclib -undefined suppress -flat_namespace factorial.c -o libfactorial-arm64.dylib -target arm64-apple-macos11 12 | gcc -dynamiclib -undefined suppress -flat_namespace factorial.c -o libfactorial-x86_64.dylib -target x86_64-apple-macos10.12 13 | lipo -create -output libfactorial.dylib libfactorial-arm64e.dylib libfactorial-arm64.dylib libfactorial-x86_64.dylib 14 | ``` 15 | 16 | To compile `libfactorial.so` on Linux/Solaris/etc.: 17 | 18 | ``` bash 19 | gcc -shared -fpic factorial.c -o libfactorial.so 20 | ``` 21 | 22 | To compile `libfactorial.dll` on Windows (): 23 | 24 | ``` bash 25 | cl.exe /D_USRDLL /D_WINDLL factorial.c /link /DLL /OUT:libfactorial.dll 26 | ``` 27 | 28 | To run the example: 29 | 30 | ``` bash 31 | $ ts-node factorial.ts 35 32 | Your output: 6399018521010896896 33 | ``` 34 | -------------------------------------------------------------------------------- /src/decorators/call/metadata-call.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | import type { 5 | FunctionTarget, 6 | } from '../../function-target.js' 7 | 8 | import { CALL_SYMBOL } from './constants.js' 9 | 10 | function updateMetadataCall ( 11 | target : Object, 12 | propertyKey : string, 13 | functionTarget : FunctionTarget, 14 | ): void { 15 | log.verbose('Sidecar', 16 | 'updateMetadataCall(%s, %s, %s)', 17 | target.constructor.name, 18 | propertyKey, 19 | typeof functionTarget === 'object' ? JSON.stringify(functionTarget) 20 | : typeof functionTarget === 'number' ? functionTarget.toString(16) 21 | : functionTarget, 22 | ) 23 | 24 | // Update the parameter names 25 | Reflect.defineMetadata( 26 | CALL_SYMBOL, 27 | functionTarget, 28 | target, 29 | propertyKey, 30 | ) 31 | } 32 | 33 | function getMetadataCall ( 34 | target : Object, 35 | propertyKey : string, 36 | ): undefined | FunctionTarget { 37 | // Pull the array of parameter names 38 | const fridaTarget = Reflect.getMetadata( 39 | CALL_SYMBOL, 40 | target, 41 | propertyKey, 42 | ) 43 | return fridaTarget 44 | } 45 | 46 | export { 47 | updateMetadataCall, 48 | getMetadataCall, 49 | } 50 | -------------------------------------------------------------------------------- /src/agent/build-agent-source.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { ChatboxSidecar } from '../../examples/chatbox-sidecar.js' 5 | import { getMetadataSidecar } from '../decorators/sidecar/metadata-sidecar.js' 6 | 7 | // import { Call, RetType, Sidecar } from '../decorators/mod.js' 8 | // import { sidecarMetadata } from '../decorators/sidecar/sidecar-metadata.js' 9 | // import { Ret } from '../ret.js' 10 | // import { SidecarBody } from '../sidecar-body/sidecar-body.js' 11 | 12 | import { 13 | buildAgentSource, 14 | } from './build-agent-source.js' 15 | import { getSidecarMetadataFixture } from '../../tests/fixtures/sidecar-metadata.fixture.js' 16 | 17 | test('buildAgentSource() from fixture', async t => { 18 | const metadata = getSidecarMetadataFixture() 19 | 20 | const source = await buildAgentSource(metadata) 21 | 22 | // console.log(source) 23 | t.ok(source, 'ok (tbw)') 24 | }) 25 | 26 | test('buildAgentSource() from example demo', async t => { 27 | 28 | const metadata = getMetadataSidecar(ChatboxSidecar)! 29 | // console.log(JSON.stringify(view, null, 2)) 30 | 31 | const source = await buildAgentSource(metadata) 32 | 33 | // console.log(source) 34 | t.ok(source, 'ok') 35 | }) 36 | -------------------------------------------------------------------------------- /src/agent/templates/agent.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import Mustache from 'mustache' 5 | 6 | import { 7 | partialLookup, 8 | } from '../partial-lookup.js' 9 | 10 | import { ChatboxSidecar } from '../../../examples/chatbox-sidecar.js' 11 | 12 | import { wrapView } from '../../wrappers/mod.js' 13 | import { getMetadataSidecar } from '../../decorators/sidecar/metadata-sidecar.js' 14 | 15 | test('agent.mustache', async t => { 16 | 17 | const view = getMetadataSidecar(ChatboxSidecar) 18 | 19 | // console.log(JSON.stringify(view, null, 2)) 20 | const wrappedView = wrapView(view!) 21 | 22 | // console.log(JSON.stringify(wrappedView, null, 2)) 23 | // console.log(Object.keys(wrappedView)) 24 | const template = await partialLookup('agent.mustache') 25 | 26 | // console.log(template) 27 | const result = Mustache.render( 28 | template, 29 | { 30 | ...wrappedView, 31 | initAgentScript: 'console.log("hello")', 32 | }, 33 | partialLookup, 34 | ) 35 | // console.log('result:', result) 36 | 37 | /** 38 | * Huan(202106): how could we test this script has been correctly generated? 39 | */ 40 | t.ok(result, 'should render to the right script (TBW)') 41 | }) 42 | -------------------------------------------------------------------------------- /src/agent/templates/interceptors-export.mustache: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * File: "interceptors-export.mustache" 3 | * 4 | * Interceptor Target: {{ name }} 5 | * - moduleName: {{ target.moduleName }} 6 | * - exportName: {{ target.exportName }} 7 | * - Parameters: {{{ nativeParamTypes }}} 8 | ****************************************************************************/ 9 | 10 | ;(() => { 11 | /** 12 | * TODO: Huan(202107): Need to be workaround for the `getExportByName` function 13 | * see: https://github.com/huan/sidecar/issues/10 14 | */ 15 | const interceptorTarget = Module.getExportByName( 16 | 17 | {{# target.moduleName }} 18 | '{{ target.moduleName }}', 19 | {{/ target.moduleName}} 20 | {{^ target.moduleName }} 21 | null, 22 | {{/ target.moduleName}} 23 | 24 | '{{ target.exportName }}', 25 | ) 26 | 27 | Interceptor.attach( 28 | interceptorTarget, 29 | { 30 | onEnter: args => { 31 | log.verbose( 32 | 'SidecarAgent', 33 | 'Interceptor.attach(%s) onEnter()', 34 | {{ target.exportName }}, 35 | ) 36 | 37 | {{{ declareJsArgs }}} 38 | 39 | send(__sidecar__payloadHook( 40 | '{{ name }}', 41 | {{{ jsArgs }}} 42 | ), null) 43 | 44 | }, 45 | } 46 | ) 47 | })() 48 | -------------------------------------------------------------------------------- /src/decorators/name/name.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | getParameterName, 6 | Name, 7 | PARAMETER_NAME_SYMBOL, 8 | } from './name.js' 9 | 10 | test('@Name with metadata', async t => { 11 | const NAME = 'test_name' 12 | 13 | class Test { 14 | 15 | method ( 16 | test: number, 17 | @Name(NAME) testName: string, 18 | ) { 19 | void test 20 | void testName 21 | } 22 | 23 | } 24 | 25 | const instance = new Test() 26 | const data = Reflect.getMetadata( 27 | PARAMETER_NAME_SYMBOL, 28 | instance, 29 | 'method', 30 | ) 31 | 32 | /* eslint-disable no-sparse-arrays */ 33 | const EXPECTED_DATA = [, NAME] 34 | t.same(data, EXPECTED_DATA, 'should get the parameter name data') 35 | }) 36 | 37 | test('getParameterName', async t => { 38 | const NAME = 'test_name' 39 | 40 | class Test { 41 | 42 | method ( 43 | test: number, 44 | @Name(NAME) testName: string, 45 | ) { 46 | void test 47 | void testName 48 | } 49 | 50 | } 51 | 52 | const instance = new Test() 53 | const nameList = [0, 1].map(i => getParameterName( 54 | instance, 55 | 'method', 56 | i, 57 | )) 58 | 59 | const EXPECTED_NAME_LIST = [undefined, NAME] 60 | t.same(nameList, EXPECTED_NAME_LIST, 'should get decorated name list') 61 | }) 62 | -------------------------------------------------------------------------------- /src/decorators/ret-type/ret-type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NativeType, 3 | PointerType, 4 | } from '../../frida.js' 5 | import { 6 | log, 7 | } from '../../config.js' 8 | 9 | import { 10 | updateMetadataRetType, 11 | } from './metadata-ret-type.js' 12 | import { guardRetType } from './guard-ret-type.js' 13 | 14 | function RetType ( 15 | nativeType : NativeType, 16 | ...pointerTypeList : PointerType[] 17 | ) { 18 | log.verbose('Sidecar', '@RetType(%s%s)', 19 | nativeType, 20 | pointerTypeList.length > 0 21 | ? `, [${pointerTypeList.join(',')}]` 22 | : '', 23 | ) 24 | 25 | return function retTypeMethodDecorator ( 26 | target : Object, 27 | propertyKey : string, 28 | _descriptor : PropertyDescriptor, 29 | ) { 30 | log.verbose('Sidecar', 31 | '@RetType(%s%s) retTypeMethodDecorator(%s, %s, descriptor)', 32 | nativeType, 33 | pointerTypeList.length > 0 34 | ? `, [${pointerTypeList.join(',')}]` 35 | : '', 36 | 37 | target.constructor.name, 38 | propertyKey, 39 | ) 40 | 41 | guardRetType( 42 | target, 43 | propertyKey, 44 | nativeType, 45 | pointerTypeList, 46 | ) 47 | 48 | updateMetadataRetType( 49 | target, 50 | propertyKey, 51 | [nativeType, ...pointerTypeList], 52 | ) 53 | } 54 | } 55 | 56 | export { RetType } 57 | -------------------------------------------------------------------------------- /src/decorators/sidecar/guard-metadata-sidecar.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import { getSidecarMetadataFixture } from '../../../tests/fixtures/sidecar-metadata.fixture.js' 4 | 5 | import { 6 | guardMetadataSidecar, 7 | } from './guard-metadata-sidecar.js' 8 | 9 | test('guardMetadataSidecar() for valid agent target', async t => { 10 | const fixture = getSidecarMetadataFixture() 11 | t.doesNotThrow(() => guardMetadataSidecar(fixture), 'should validate the fixture') 12 | }) 13 | 14 | test('guardMetadataSidecar() for invalid agent target: @ParamType', async t => { 15 | const fixture = getSidecarMetadataFixture() 16 | // get the first AgentTarget descriptor 17 | const desc = fixture.nativeFunctionList.filter(x => x.agent)[0]! 18 | desc.agent!.paramTypeList = [ 19 | ['pointer'], 20 | ] 21 | 22 | t.throws(() => guardMetadataSidecar(fixture), 'should throw for invalid agent target descriptor: unnecessary @ParamType') 23 | }) 24 | 25 | test('guardMetadataSidecar() for invalid agent target: @RetType', async t => { 26 | const fixture = getSidecarMetadataFixture() 27 | // get the first AgentTarget descriptor 28 | const desc = fixture.nativeFunctionList.filter(x => x.agent)[0]! 29 | desc.agent!.retType = ['pointer'] 30 | 31 | t.throws(() => guardMetadataSidecar(fixture), 'should throw for invalid agent target descriptor: unnecessary @RetType') 32 | }) 33 | -------------------------------------------------------------------------------- /examples/archive/raw/agent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar example agent 3 | * 4 | * Huan , June 24, 2021 5 | * https://github.com/huan/sidecar 6 | */ 7 | import { log } from 'brolog' 8 | 9 | log.level('silly') 10 | log.verbose('Agent', 'Entered') 11 | 12 | const MO_ADDR = ptr(0x5631598e21c9) 13 | const MT_ADDR = ptr(0x5631598e21f4) 14 | 15 | const moNativeFunc = new NativeFunction( 16 | MO_ADDR, 17 | 'void', 18 | ['pointer'], 19 | ) 20 | 21 | function mo (content: string): void { 22 | log.verbose('Agent', 'mo(%s)', content) 23 | 24 | moNativeFunc( 25 | Memory.allocUtf8String(content) 26 | ) 27 | } 28 | 29 | Interceptor.attach( 30 | MT_ADDR, 31 | { 32 | onEnter: args => { 33 | log.verbose('Agent', 'Interceptor.attach() onEnter(%s)', args[0]!.readUtf8String()) 34 | send({ 35 | payload: { 36 | content: args[0]!.readUtf8String(), 37 | }, 38 | type: 'MT_MESSAGE', 39 | }) 40 | }, 41 | } 42 | ) 43 | 44 | const fridaRecv: MessageCallback = (message: any, data: ArrayBuffer | null) => { 45 | log.verbose('Agent', 'fridaRec(%s, %s)', JSON.stringify(message), data) 46 | mo(JSON.stringify(message)) 47 | recv(fridaRecv) 48 | } 49 | 50 | log.verbose('Agent', 'recv(fridaRecv) registering...') 51 | recv(fridaRecv) 52 | log.verbose('Agent', 'recv(fridaRecv) registered') 53 | 54 | function init () { 55 | log.verbose('Agent', 'init()') 56 | } 57 | 58 | rpc.exports = { 59 | init, 60 | mo, 61 | } 62 | -------------------------------------------------------------------------------- /src/decorators/call/call.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | 5 | import type { 6 | FunctionTarget, 7 | } from '../../function-target.js' 8 | 9 | import { updateMetadataCall } from './metadata-call.js' 10 | import { updateRpcDescriptor } from './update-rpc-descriptor.js' 11 | 12 | function Call ( 13 | functionTarget: FunctionTarget, 14 | ) { 15 | log.verbose('Sidecar', '@Call(%s)', 16 | typeof functionTarget === 'string' ? functionTarget 17 | : typeof functionTarget === 'number' ? `0x${functionTarget.toString(16)}` 18 | : JSON.stringify(functionTarget), 19 | ) 20 | 21 | return function callMethodDecorator ( 22 | target : any, 23 | propertyKey : string, 24 | descriptor : PropertyDescriptor, 25 | ): PropertyDescriptor { 26 | log.verbose('Sidecar', 27 | '@Call(%s) callMethodDecorator(%s, %s, descriptor)', 28 | typeof functionTarget === 'object' ? JSON.stringify(functionTarget) 29 | : typeof functionTarget === 'number' ? '0x' + functionTarget.toString(16) 30 | : functionTarget, 31 | 32 | target.constructor.name, 33 | propertyKey, 34 | ) 35 | 36 | updateMetadataCall( 37 | target, 38 | propertyKey, 39 | functionTarget, 40 | ) 41 | 42 | const rpcDescriptor = updateRpcDescriptor( 43 | target, 44 | propertyKey, 45 | descriptor, 46 | ) 47 | 48 | return rpcDescriptor 49 | } 50 | } 51 | 52 | export { Call } 53 | -------------------------------------------------------------------------------- /examples/archive/raw-events/agent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar example agent 3 | * 4 | * Huan , June 24, 2021 5 | * https://github.com/huan/sidecar 6 | */ 7 | import { log } from 'brolog' 8 | 9 | import type { SidecarFridaPayload } from './schema.js' 10 | 11 | log.level('verbose') 12 | log.verbose('Agent', 'Entered') 13 | 14 | const MO_ADDR = ptr(0x55555b6441c9) 15 | const MT_ADDR = ptr(0x55555b6441f4) 16 | 17 | const moNativeFunc = new NativeFunction( 18 | MO_ADDR, 19 | 'void', 20 | ['pointer'], 21 | ) 22 | 23 | function mo (content: string): void { 24 | log.verbose('Agent', 'mo(%s)', content) 25 | 26 | moNativeFunc( 27 | Memory.allocUtf8String(content) 28 | ) 29 | } 30 | 31 | /** 32 | * To make sure the payload typing is right 33 | */ 34 | function sendSidecarPayload ( 35 | payload: SidecarFridaPayload, 36 | data: null | number[] | ArrayBuffer, 37 | ) { 38 | send(payload, data) 39 | } 40 | 41 | Interceptor.attach( 42 | MT_ADDR, 43 | { 44 | onEnter: args => { 45 | log.silly('Agent', 'Interceptor.attach() onEnter(%s)', args[0]!.readUtf8String()) 46 | const content = args[0]!.readUtf8String() 47 | const payload: SidecarFridaPayload = { 48 | args: { 49 | 0: content, 50 | content, 51 | }, 52 | method: 'mt', 53 | } 54 | sendSidecarPayload(payload, null) 55 | }, 56 | } 57 | ) 58 | 59 | function init () { 60 | log.verbose('Agent', 'init()') 61 | } 62 | 63 | rpc.exports = { 64 | init, 65 | mo, 66 | } 67 | -------------------------------------------------------------------------------- /examples/dynamic-library/main.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Sidecar - https://github.com/huan/sidecar 4 | * 5 | * @copyright 2021 Huan LI (李卓桓) 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | import assert from 'assert' 20 | 21 | import { 22 | attach, 23 | detach, 24 | } from '../../src/mod.js' 25 | 26 | import { FactorialSidecar } from './factorial-sidecar.js' 27 | 28 | async function main () { 29 | const sidecar = new FactorialSidecar() 30 | console.log('Sidecar attaching...') 31 | await attach(sidecar) 32 | console.log('Sidecar attached.') 33 | 34 | const ret = await sidecar.factorial(3) 35 | 36 | assert(typeof ret === 'number', 'factorial() returns type `number`') 37 | assert(ret === 6, 'factorial(3)=6') 38 | 39 | console.log('factorial(3)=' + ret) 40 | 41 | await detach(sidecar) 42 | } 43 | 44 | main() 45 | .catch(console.error) 46 | -------------------------------------------------------------------------------- /src/agent/templates/libs/payload.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /* eslint-disable camelcase */ 3 | import { test } from 'tstest' 4 | import type { 5 | SidecarPayloadHook, 6 | SidecarPayloadLog, 7 | } from '../../../sidecar-body/payload-schemas.js' 8 | 9 | import pkg from './payload.cjs' 10 | 11 | const { 12 | __sidecar__payloadHook, 13 | __sidecar__payloadLog, 14 | } = pkg 15 | 16 | test('__sidecar__payloadLog()', async t => { 17 | const message = 'test' as string 18 | 19 | const payload = __sidecar__payloadLog( 20 | 'verbose', 21 | 'Test', 22 | message, 23 | ) 24 | const EXPECTED: SidecarPayloadLog = { 25 | payload : { 26 | level: 'verbose', 27 | message, 28 | prefix: 'Test', 29 | }, 30 | type : 'log', 31 | } 32 | 33 | t.same(payload, EXPECTED, 'should get log payload correctly') 34 | }) 35 | 36 | test('__sidecar__payloadHook()', async t => { 37 | const METHOD = 'method' 38 | const ARGS = ['arg0', 'arg1'] 39 | 40 | const payload = __sidecar__payloadHook( 41 | METHOD, 42 | ARGS, 43 | ) 44 | 45 | const EXPECTED_PAYLOAD: SidecarPayloadHook = { 46 | payload: { 47 | args : {}, 48 | method : METHOD, 49 | }, 50 | type: 'hook', 51 | } 52 | for (const [idx, item] of ARGS.entries()) { 53 | EXPECTED_PAYLOAD.payload.args[idx] = item 54 | } 55 | 56 | t.same(payload, EXPECTED_PAYLOAD, 'should make hook payload correctly.') 57 | }) 58 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { 20 | Sidecar, 21 | SidecarBody, 22 | Call, 23 | Hook, 24 | ParamType, 25 | RetType, 26 | Ret, 27 | } from '../src/mod.js' 28 | 29 | @Sidecar(['examples/chatbox/chatbox-linux']) 30 | class ChatboxSidecar extends SidecarBody { 31 | 32 | @Call(0x11e9) // call address 33 | @RetType('int') // return type is `int` 34 | mo ( 35 | // parameter type is string (UTF-8) 36 | @ParamType('pointer', 'Utf8String') content: string, 37 | ): Promise { return Ret(content) } 38 | 39 | @Hook(0x121f) // hook address 40 | mt ( 41 | // parameter type is string (UTF-8) 42 | @ParamType('pointer', 'Utf8String') content: string, 43 | ) { return Ret(content) } 44 | 45 | } 46 | 47 | export { ChatboxSidecar } 48 | -------------------------------------------------------------------------------- /src/decorators/param-type/guard-param-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { 4 | NativeType, 5 | PointerType, 6 | } from '../../frida.js' 7 | 8 | import { 9 | guardParamType, 10 | } from './guard-param-type.js' 11 | 12 | test('guard parame type', async t => { 13 | 14 | /** 15 | * Huan(202106) decorator metadata is emitted only on decorated members 16 | * https://stackoverflow.com/questions/51493874/typescript-emits-no-decorator-metadata/51493888#51493888 17 | */ 18 | const d = (..._args: any[]) => {} 19 | 20 | class Test { 21 | 22 | @d 23 | method (s: string): void { 24 | void s 25 | } 26 | 27 | } 28 | 29 | const test = new Test() 30 | 31 | const EXPECTED_RESULTS: [ 32 | NativeType, 33 | PointerType, 34 | boolean, 35 | ][] = [ 36 | ['int', 'Int', false], 37 | ['pointer', 'Utf8String', true], 38 | ] 39 | 40 | for (const [nativeType, pointerType, shouldMatch] of EXPECTED_RESULTS) { 41 | if (shouldMatch) { 42 | guardParamType( 43 | test, 44 | 'method', 45 | 0, 46 | nativeType, 47 | [pointerType], 48 | ) 49 | t.pass('should not throw for nativeType: ' + nativeType) 50 | } else { 51 | t.throws(() => guardParamType( 52 | test, 53 | 'method', 54 | 0, 55 | nativeType, 56 | [pointerType], 57 | ), 'should throw for nativeType: ' + nativeType) 58 | } 59 | } 60 | }) 61 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar-pro/chatbox-sidecar-pro.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { 20 | Sidecar, 21 | SidecarBody, 22 | Call, 23 | Hook, 24 | ParamType, 25 | RetType, 26 | Ret, 27 | } from '../../src/mod.js' 28 | 29 | import { 30 | targetAddress, 31 | targetProgram, 32 | } from './sidecar-config.js' 33 | 34 | @Sidecar([ 35 | targetProgram(), // chatbox-linux 36 | ]) 37 | class ChatboxSidecarPro extends SidecarBody { 38 | 39 | @Call(targetAddress('mo')) 40 | @RetType('int') 41 | mo ( 42 | @ParamType('pointer', 'Utf8String') content: string, 43 | ): Promise { return Ret(content) } 44 | 45 | @Hook(targetAddress('mt')) 46 | mt ( 47 | @ParamType('pointer', 'Utf8String') content: string, 48 | ) { return Ret(content) } 49 | 50 | } 51 | 52 | export { ChatboxSidecarPro } 53 | -------------------------------------------------------------------------------- /src/agent/templates/native-functions-export.mustache: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | * File: "native-function-export.mustache" 3 | * 4 | * Native Function: {{ name }} 5 | * - moduleName: {{ target.moduleName }} 6 | * - exportName: {{ target.exportName }} 7 | * - Parameters: {{{ nativeParamTypes }}} 8 | * - Ret: {{ retType }} 9 | ******************************************************************/ 10 | 11 | const __sidecar__{{ name }}_Function_wrapper = (() => { 12 | /** 13 | * Huan(202107): Need to be workaround for the `getExportByName` function 14 | * see: https://github.com/huan/sidecar/issues/10 15 | */ 16 | const nativeFunctionAddress = 17 | {{# target.moduleName }} 18 | Module.enumerateExportsSync('{{ target.moduleName }}') 19 | .filter(x => x.type === 'function') 20 | .filter(x => x.name === '{{ target.exportName }}') 21 | .map(x => x.address) 22 | .pop() 23 | {{/ target.moduleName}} 24 | {{^ target.moduleName }} 25 | Module.getExportByName( 26 | null, 27 | '{{ target.exportName }}', 28 | ) 29 | {{/ target.moduleName}} 30 | 31 | const nativeFunction = new NativeFunction( 32 | nativeFunctionAddress, 33 | {{{ nativeRetType }}}, 34 | {{{ nativeParamTypes }}}, 35 | ) 36 | 37 | return function (...args) { 38 | log.verbose( 39 | 'SidecarAgent', 40 | '{{ name }}(%s)', 41 | args.join(', '), 42 | ) 43 | 44 | {{{ declareNativeArgs }}} 45 | 46 | const ret = nativeFunction(...{{{ nativeArgs }}}) 47 | return {{{ jsRet }}} 48 | } 49 | 50 | })() 51 | 52 | -------------------------------------------------------------------------------- /src/decorators/ret-type/guard-ret-type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NativeType, 3 | PointerType, 4 | } from '../../frida.js' 5 | import { 6 | log, 7 | } from '../../config.js' 8 | 9 | import { 10 | guardNativeType, 11 | guardPointerType, 12 | ReflectedDesignType, 13 | } from '../../type-guard.js' 14 | 15 | /** 16 | * Verify the TypeScript ret type is matching the NativeType from `RetType` 17 | */ 18 | function guardRetType ( 19 | target : Object, 20 | propertyKey : string, 21 | nativeType : NativeType, 22 | pointerTypeList : PointerType[], 23 | ): void { 24 | const designRetType = Reflect.getMetadata('design:returntype', target, propertyKey) as ReflectedDesignType 25 | 26 | log.verbose('Sidecar', 27 | 'guardRetType(%s.%s) designType/nativeType/pointerTypeList: %s/%s/[%s]', 28 | target.constructor.name, 29 | propertyKey, 30 | 31 | designRetType?.name ?? 'void', 32 | nativeType, 33 | pointerTypeList.join(','), 34 | ) 35 | 36 | try { 37 | guardNativeType(nativeType)(designRetType) 38 | if (nativeType === 'pointer') { 39 | guardPointerType(pointerTypeList)(designRetType) 40 | } 41 | } catch (e) { 42 | log.error('Sidecar', 'guardRetType() %s', e && (e as Error).message) 43 | throw new Error([ 44 | `The ${target.constructor.name}.${String(propertyKey)}()`, 45 | `decorated by "@RetType(${nativeType}, ...)"`, 46 | `does match the design return type "${designRetType?.name ?? 'void'}"`, 47 | ].join('\n')) 48 | } 49 | 50 | } 51 | 52 | export { guardRetType } 53 | -------------------------------------------------------------------------------- /bin/sidecar-dump.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node --no-warnings --loader ts-node/esm --experimental-vm-modules 2 | /** 3 | * https://github.com/huan/sidecar 4 | * 5 | * Author: Huan 6 | * License: Apache-2.0 7 | * 8 | * CLI Apps in TypeScript with `cmd-ts` (Part 1) 9 | * Using `cmd-ts` to easily build a type-safe TypeScript CLI app 10 | * 11 | * https://gal.hagever.com/posts/type-safe-cli-apps-in-typescript-with-cmd-ts-part-1/ 12 | */ 13 | /* eslint-disable sort-keys */ 14 | import { 15 | binary, 16 | run, 17 | subcommands, 18 | } from 'cmd-ts' 19 | // import { 20 | // REGISTER_INSTANCE, 21 | // } from 'ts-node' 22 | 23 | import { VERSION } from '../src/version.js' 24 | // import { log } from '../src/config.js' 25 | 26 | import { 27 | metadata, 28 | source, 29 | } from '../src/cli/mod.js' 30 | 31 | /** 32 | * Check ts-node loaded or not 33 | * See: https://github.com/TypeStrong/ts-node/blob/5643ad64cf39ee0dfa2a9323e8d1dd9f400e5884/src/index.ts#L54-L68 34 | * 35 | * Update: 36 | * - Huan(202109): We enable ESM 37 | */ 38 | // if (!process[REGISTER_INSTANCE]) { 39 | // log.verbose('sidecar-dump', 'Loading `ts-node/register`...') 40 | // require('ts-node/register') 41 | // } 42 | 43 | const sidecarDump = subcommands({ 44 | name: 'sidecar-dump', 45 | description: 'Sidecar utility for dumping metadata/source for a sidecar class', 46 | version: VERSION, 47 | cmds: { 48 | metadata, 49 | source, 50 | }, 51 | }) 52 | 53 | run( 54 | binary(sidecarDump), 55 | process.argv, 56 | ).catch(console.error) 57 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar-agent/chatbox-sidecar-agent.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { 20 | Sidecar, 21 | SidecarBody, 22 | Call, 23 | Hook, 24 | ParamType, 25 | Ret, 26 | agentTarget, 27 | } from '../../src/mod.js' 28 | 29 | import { 30 | targetProgram, 31 | loadAgentScript, 32 | } from './sidecar-config.js' 33 | 34 | @Sidecar( 35 | [targetProgram()], // chatbox-linux 36 | loadAgentScript(), // helper agent scripts 37 | ) 38 | class ChatboxSidecarAgent extends SidecarBody { 39 | 40 | @Call(agentTarget('moJsFunction')) 41 | mo ( 42 | content: string, 43 | ): Promise { return Ret(content) } 44 | 45 | @Hook(agentTarget('mtNativeCallback')) 46 | mt ( 47 | @ParamType('pointer', 'Utf8String') content: string, 48 | ) { return Ret(content) } 49 | 50 | } 51 | 52 | export { ChatboxSidecarAgent } 53 | -------------------------------------------------------------------------------- /examples/archive/raw/sidecar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar example agent 3 | * 4 | * Huan , June 24, 2021 5 | * https://github.com/huan/sidecar 6 | */ 7 | import { log } from 'brolog' 8 | 9 | import * as frida from '../../../src/frida' 10 | 11 | import { clean } from './clean.js' 12 | import { loadAgentSource } from './load-agent-source.js' 13 | import { scriptDestroyedHandler } from './script-destroyed-handler.js' 14 | import { scriptMessageHandler } from './script-message-handler.js' 15 | 16 | log.level('silly') 17 | 18 | const scriptPostTest = (script: frida.Script) => () => { 19 | return script.post({ 20 | data: 'XXX OOO', 21 | type: 'test', 22 | }) 23 | } 24 | async function main () { 25 | const session = await frida.attach('messaging') 26 | const agentSource = await loadAgentSource() 27 | const script = await session.createScript(agentSource) 28 | 29 | script.message.connect(scriptMessageHandler) 30 | script.destroyed.connect(scriptDestroyedHandler) 31 | 32 | process.on('SIGINT', () => clean(session, script)) 33 | process.on('SIGTERM', () => clean(session, script)) 34 | 35 | await script.load() 36 | 37 | const timer = setInterval(scriptPostTest(script), 1000) 38 | ;(timer as any).unref() 39 | 40 | try { 41 | await script.exports['init']!() 42 | } catch (e) { 43 | console.error(e) 44 | } 45 | // frida.resume(pid) 46 | try { 47 | await script.exports['mo']!('Sidebar: new messsage send by script.exports.mo()') 48 | } catch (e) { 49 | console.error(e) 50 | } 51 | } 52 | 53 | main() 54 | .catch(console.error) 55 | -------------------------------------------------------------------------------- /tests/library-sidecar.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Sidecar - https://github.com/huan/sidecar 4 | * 5 | * @copyright 2021 Huan LI (李卓桓) 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | import { test } from 'tstest' 21 | 22 | import { 23 | attach, 24 | detach, 25 | } from '../src/mod.js' 26 | 27 | import { FactorialSidecar } from '../examples/dynamic-library/factorial-sidecar.js' 28 | 29 | test('library export function call', async (t) => { 30 | if (process.platform !== 'linux' && process.platform !== 'win32') { 31 | void t.skip('This test will be skipped because it only support Linux(.so) and Windows(.dll) now') 32 | return 33 | } 34 | 35 | const sidecar = new FactorialSidecar() 36 | await attach(sidecar) 37 | 38 | const EXPECTED_RET_VALUE = 6 39 | const ret = await sidecar.factorial(3) 40 | 41 | await detach(sidecar) 42 | 43 | t.equal(ret, EXPECTED_RET_VALUE, 'should get the factorial(3) = 6') 44 | }) 45 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar-agent/sidecar-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import fs from 'fs' 20 | import path from 'path' 21 | 22 | import { codeRoot } from '../../src/cjs.js' 23 | 24 | /** 25 | * See: https://github.com/frida/frida-node/blob/master/test/data/index.ts 26 | */ 27 | function targetProgram () { 28 | const chatboxNameList = [ 29 | 'chatbox', 30 | '-', 31 | process.platform, 32 | ] 33 | 34 | if (process.platform === 'win32') { 35 | chatboxNameList.push('.exe') 36 | } 37 | 38 | return path.resolve( 39 | codeRoot, 40 | 'examples', 41 | 'chatbox', 42 | chatboxNameList.join(''), 43 | ) 44 | } 45 | 46 | function loadAgentScript () { 47 | const file = path.resolve(codeRoot, 'examples/chatbox-sidecar-agent/init-agent-script.js') 48 | return fs.readFileSync(file, 'utf8') 49 | } 50 | 51 | export { 52 | targetProgram, 53 | loadAgentScript, 54 | } 55 | -------------------------------------------------------------------------------- /src/decorators/param-type/param-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Data Type: 3 | * https://en.wikipedia.org/wiki/Data_type 4 | * 5 | * TypeScript Decorators: Parameter Decorators 6 | * https://blog.wizardsoftheweb.pro/typescript-decorators-parameter-decorators/ 7 | */ 8 | import type { 9 | NativeType, 10 | PointerType, 11 | } from '../../frida.js' 12 | import { 13 | log, 14 | } from '../../config.js' 15 | 16 | import { updateMetadataParamType } from './metadata-param-type.js' 17 | import { guardParamType } from './guard-param-type.js' 18 | 19 | function ParamType ( 20 | nativeType : NativeType, 21 | ...pointerTypeList : PointerType[] 22 | ) { 23 | log.verbose('Sidecar', 24 | '@ParamType(%s%s)', 25 | nativeType, 26 | pointerTypeList.length > 0 27 | ? `, [${pointerTypeList.join(',')}]` 28 | : '', 29 | ) 30 | 31 | return function paramTypeDecorator ( 32 | target : Object, 33 | propertyKey : string, 34 | parameterIndex : number, 35 | ) { 36 | log.verbose('Sidecar', 37 | '@ParamType(%s%s) paramTypeDecorator (%s, %s, %s)', 38 | nativeType, 39 | pointerTypeList.length > 0 40 | ? `, [${pointerTypeList.join(',')}]` 41 | : '', 42 | 43 | target.constructor.name, 44 | propertyKey, 45 | parameterIndex, 46 | ) 47 | 48 | guardParamType( 49 | target, 50 | propertyKey, 51 | parameterIndex, 52 | nativeType, 53 | pointerTypeList, 54 | ) 55 | 56 | updateMetadataParamType( 57 | target, 58 | propertyKey, 59 | parameterIndex, 60 | [nativeType, ...pointerTypeList], 61 | ) 62 | } 63 | } 64 | 65 | export { ParamType } 66 | -------------------------------------------------------------------------------- /src/cli/vm.ts: -------------------------------------------------------------------------------- 1 | import vm from 'vm' 2 | 3 | /** 4 | * Huan(202109): adding experimental vm classes/methods 5 | * 6 | * importModuleDynamically for vm module is cached #36351 7 | * https://github.com/nodejs/node/issues/36351 8 | */ 9 | declare module 'vm' { 10 | export interface SourceTextModuleOptions { 11 | importModuleDynamically: ( 12 | specifier: string, 13 | module?: any, 14 | ) => any 15 | context?: vm.Context 16 | } 17 | 18 | export type Linker = ( 19 | specifier: string, 20 | extra: Object, 21 | referencingModule: any, 22 | ) => any 23 | 24 | export class SourceTextModule { 25 | 26 | constructor ( 27 | code: string, 28 | options?: SourceTextModuleOptions, 29 | ) 30 | 31 | link (linker: Linker): Promise 32 | evaluate (): Promise 33 | 34 | } 35 | } 36 | 37 | const importModuleDynamically = ( 38 | identifier: string, 39 | ) => import(identifier) 40 | 41 | async function executeWithContext ( 42 | code: string, 43 | contextObj: object, 44 | ): Promise { 45 | let __ret: undefined | T 46 | 47 | /** 48 | * Huan(202109): Reflect is needed for importModuleDynamically 49 | */ 50 | const context = vm.createContext({ 51 | Reflect, 52 | console, 53 | ...contextObj, 54 | 55 | __ret, 56 | }) 57 | 58 | const assignRetCode = `__ret = await ${code}` 59 | 60 | const module = new vm.SourceTextModule(assignRetCode, { 61 | context, 62 | importModuleDynamically, 63 | }) 64 | 65 | await module.link(() => {}) 66 | await module.evaluate() 67 | 68 | return context['__ret'] as undefined | T 69 | } 70 | 71 | export { 72 | vm, 73 | executeWithContext, 74 | } 75 | export default vm 76 | -------------------------------------------------------------------------------- /examples/chatbox/chatbox.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | #ifdef _WIN32 20 | #include 21 | #else 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | char buf[100] = {0}; 29 | int counter = 0; 30 | 31 | char* randomMessage (const char* type) { 32 | sprintf(buf, "Messaging: %s message#%d", type, counter); 33 | return buf; 34 | } 35 | 36 | int mo (char* content) { 37 | printf("> %s\n", content); 38 | return strlen(content); 39 | } 40 | 41 | void mt (char* content) { 42 | printf("<< %s\n", content); 43 | } 44 | 45 | int main() { 46 | printf("mo() is at %p\n", mo); 47 | printf("mt() is at %p\n", mt); 48 | 49 | mo("Chatbox demo started."); 50 | 51 | while(++counter) { 52 | 53 | if (counter % 3 == 0) { 54 | mo(randomMessage("Send")); 55 | } else { 56 | mt(randomMessage("Receive")); 57 | } 58 | 59 | #ifdef _WIN32 60 | Sleep(3000); 61 | #else 62 | sleep(3); 63 | #endif 64 | } 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /src/decorators/sidecar/guard-metadata-sidecar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | 5 | import type { SidecarMetadata } from './metadata-sidecar.js' 6 | 7 | /** 8 | * Verify the Sidecar Metadata is satisfy as a whole 9 | */ 10 | function guardMetadataSidecar ( 11 | meta: SidecarMetadata, 12 | ): void { 13 | log.verbose('Sidecar', 'guardMetadataSidecar(meta)') 14 | 15 | for (const desc of meta.nativeFunctionList) { 16 | /** 17 | * Huan(202108): Check for `AgentTarget`: the agentTarget 18 | * will be mapped to a JavaScript function 19 | * inside the `initAgentScript` source code. 20 | * 21 | * Which means that it should not be annoated 22 | * by either `@ParamType` or `RetType` 23 | */ 24 | if (desc.agent) { 25 | if (desc.agent.paramTypeList.length > 0) { 26 | throw new Error([ 27 | `The sidecar method "${desc.agent.name}" is decorated as 'AgentType'`, 28 | 'which means that it will be mapped to a JavaScript function', 29 | 'in `initAgentScript` source code,', 30 | 'So decorated it with `@ParamType() is not allowed`,', 31 | 'Remove `@ParamType` and try again.', 32 | ].join('\n')) 33 | } 34 | if (desc.agent.retType) { 35 | throw new Error([ 36 | `The sidecar method "${desc.agent.name}" is decorated as 'AgentType'`, 37 | 'which means that it will be mapped to a JavaScript function', 38 | 'in `initAgentScript` source code,', 39 | 'So decorated it with `@RetType() is not allowed`,', 40 | 'Remove `@RetType` and try again.', 41 | ].join('\n')) 42 | } 43 | } 44 | } 45 | } 46 | 47 | export { guardMetadataSidecar } 48 | -------------------------------------------------------------------------------- /src/decorators/param-type/guard-param-type.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | NativeType, 3 | PointerType, 4 | } from '../../frida.js' 5 | import { 6 | log, 7 | } from '../../config.js' 8 | 9 | import { 10 | guardPointerType, 11 | guardNativeType, 12 | ReflectedDesignType, 13 | } from '../../type-guard.js' 14 | 15 | /** 16 | * Verify the TypeScript param type is matching the NativeType from `ParamType` 17 | */ 18 | function guardParamType ( 19 | target : Object, 20 | propertyKey : string, 21 | parameterIndex : number, 22 | nativeType : NativeType, 23 | pointerTypeList : PointerType[], 24 | ): void { 25 | const designParamTypeList = Reflect.getMetadata('design:paramtypes', target, propertyKey) as ReflectedDesignType[] 26 | const designParamType = designParamTypeList[parameterIndex] 27 | 28 | log.verbose('Sidecar', 29 | 'guardParamType(%s, %s, %s) %s.%s(args[%s]) designType/nativeType/pointerTypes: %s/%s/%s', 30 | target.constructor.name, 31 | propertyKey, 32 | parameterIndex, 33 | 34 | target.constructor.name, 35 | propertyKey, 36 | parameterIndex, 37 | 38 | designParamType?.name ?? 'void', 39 | nativeType, 40 | pointerTypeList.join(','), 41 | ) 42 | 43 | try { 44 | guardNativeType(nativeType)(designParamType) 45 | if (nativeType === 'pointer') { 46 | guardPointerType(pointerTypeList)(designParamType) 47 | } 48 | } catch (e) { 49 | log.error('Sidecar', [ 50 | `The "${target.constructor.name}.${String(propertyKey)}(args[${parameterIndex}])`, 51 | `decorated by "@ParamType(${nativeType}, ...)"`, 52 | `does match the design type "${designParamType?.name}"`, 53 | ].join('\n')) 54 | throw e 55 | } 56 | } 57 | 58 | export { guardParamType } 59 | -------------------------------------------------------------------------------- /src/agent/templates/interceptors-agent.mustache: -------------------------------------------------------------------------------- 1 | /********************************************************** 2 | * File: "interceptors-agent.mustache" 3 | * 4 | * Interceptor Target: {{ name }} 5 | * - funcName: {{ target.funcName }} 6 | * - Parameters: {{{ nativeParamTypes }}} 7 | **********************************************************/ 8 | 9 | /** 10 | * @link https://github.com/frida/frida/issues/1774#issuecomment-878173544 11 | * Thanks to the suggestion from @oleavr: 12 | * > GumJS currently configures Interceptor 13 | * > so it will ignore calls from Frida's internal threads. 14 | * > This only applies to listeners though (onEnter/onLeave) 15 | * > – so if you use Interceptor.replace() you will see calls from Frida's internal threads. 16 | * > (And should be prepared to handle that.) Cool project btw! 17 | */ 18 | 19 | ;(() => { 20 | const sendSidecarPayloadHook = (...args) => { 21 | log.verbose( 22 | 'SidecarAgent', 23 | 'Interceptor.attach(%s) onEnter()', 24 | '{{ target.funcName }}', 25 | ) 26 | 27 | {{{ declareJsArgs }}} 28 | 29 | send(__sidecar__payloadHook( 30 | '{{ name }}', 31 | {{{ jsArgs }}} 32 | ), null) 33 | 34 | } 35 | const nativeCallback = new NativeCallback( 36 | sendSidecarPayloadHook, 37 | {{{ nativeRetType }}}, 38 | {{{ nativeParamTypes }}}, 39 | ) 40 | 41 | /** 42 | * Huan(202107): `target` at here should be a native callback ptr 43 | * which is declared in the `initAgentScript` for workaround 44 | * 45 | * Notice that the `target.funcName` callback will be `Interceptor.replace()`-ed 46 | * which means it should not contains any logic code, it should just be a stub. 47 | */ 48 | Interceptor.replace( 49 | {{ target.funcName }}, 50 | nativeCallback, 51 | ) 52 | 53 | })() 54 | 55 | -------------------------------------------------------------------------------- /src/decorators/ret-type/guard-ret-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import type { 4 | NativeType, 5 | PointerType, 6 | } from '../../frida.js' 7 | 8 | import { 9 | guardRetType, 10 | } from './guard-ret-type.js' 11 | 12 | test('guard ret type', async t => { 13 | 14 | const triggerMetadata = (..._args: any[]) => {} 15 | 16 | class Test { 17 | 18 | // metadata will only be set when we have a decorator 19 | @triggerMetadata 20 | syncMethod (): string { // <--- `string` should be native type `pointer` 21 | return '' 22 | } 23 | 24 | @triggerMetadata 25 | asyncMethod (): Promise { 26 | return Promise.resolve('') 27 | } 28 | 29 | } 30 | 31 | const test = new Test() 32 | 33 | const EXPECTED_RESULTS: [ 34 | string, 35 | NativeType, 36 | PointerType, 37 | boolean, // `true` if the native type is compatible, `false` otherwise 38 | ][] = [ 39 | ['syncMethod', 'int', 'Int', false], 40 | ['syncMethod', 'pointer', 'Utf8String', true], 41 | 42 | ['asyncMethod', 'pointer', 'Utf8String', true], 43 | ['asyncMethod', 'int', 'Int', false], 44 | ] 45 | 46 | for (const [method, nativeType, pointerType, shouldMatch] of EXPECTED_RESULTS) { 47 | if (shouldMatch) { 48 | guardRetType( 49 | test, 50 | method, 51 | nativeType, 52 | [pointerType], 53 | ) 54 | t.pass('should not throw for method/nativeType: ' + method + '/' + nativeType) 55 | } else { 56 | t.throws(() => guardRetType( 57 | test, 58 | 'method', 59 | nativeType, 60 | [pointerType], 61 | ), 'should throw for method/nativeType: ' + method + '/' + nativeType) 62 | } 63 | } 64 | }) 65 | -------------------------------------------------------------------------------- /src/cli/extract-class-names.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm --experimental-vm-modules 2 | import { test } from 'tstest' 3 | 4 | import fs from 'fs' 5 | import path from 'path' 6 | 7 | import { 8 | extractClassNameListFromSource, 9 | } from './extract-class-names.js' 10 | import { codeRoot } from '../cjs.js' 11 | 12 | test('extractClassNameListFromSource()', async t => { 13 | const TS = ` 14 | @Sidecar( 15 | targetProgram(), 16 | loadAgentSource(), 17 | ) 18 | class ChatboxSidecar extends SidecarBody {} 19 | 20 | @Sidecar( 21 | targetProgram(), 22 | loadAgentSource(), 23 | ) 24 | 25 | class ChatboxSidecar2 extends SidecarBody {} 26 | ` 27 | const EXPECTED = ['ChatboxSidecar', 'ChatboxSidecar2'] 28 | 29 | const classNameList = await extractClassNameListFromSource(TS) 30 | t.same(classNameList, EXPECTED, 'should extract the class name correct') 31 | }) 32 | 33 | test('extractClassNameListFromSource() with export', async t => { 34 | const TS = ` 35 | @Sidecar( 36 | targetProgram(), 37 | loadAgentSource(), 38 | ) 39 | export class ChatboxSidecar extends SidecarBody {} 40 | ` 41 | const EXPECTED = ['ChatboxSidecar'] 42 | 43 | const classNameList = await extractClassNameListFromSource(TS) 44 | t.same(classNameList, EXPECTED, 'should extract the exported class name correct') 45 | }) 46 | 47 | test('extractClassNameListFromSource() with examples/chatbox-sidebar.ts', async t => { 48 | const TS = await fs.readFileSync(path.join( 49 | codeRoot, 50 | 'examples', 51 | 'chatbox-sidecar.ts', 52 | )).toString() 53 | 54 | const EXPECTED = ['ChatboxSidecar'] 55 | 56 | const classNameList = await extractClassNameListFromSource(TS) 57 | t.same(classNameList, EXPECTED, 'should extract the class name correct') 58 | }) 59 | -------------------------------------------------------------------------------- /src/agent/templates/rpc-exports.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /* eslint-disable camelcase */ 3 | import { test } from 'tstest' 4 | 5 | import vm from 'vm' 6 | import Mustache from 'mustache' 7 | 8 | import { 9 | partialLookup, 10 | } from '../partial-lookup.js' 11 | 12 | import { getSidecarMetadataFixture } from '../../../tests/fixtures/sidecar-metadata.fixture.js' 13 | import { wrapView } from '../../wrappers/mod.js' 14 | 15 | test('render rpc-exports()', async t => { 16 | 17 | const SIDECAR_METADATA = getSidecarMetadataFixture() 18 | const view = wrapView(SIDECAR_METADATA) 19 | 20 | const template = await partialLookup('rpc-exports.mustache') 21 | 22 | // console.log(template) 23 | const code = Mustache.render(template, view) 24 | // console.log(code) 25 | 26 | /** 27 | * https://nodejs.org/api/vm.html 28 | */ 29 | const context = { 30 | __sidecar__agentMethod_Function_wrapper : () => {}, 31 | __sidecar__anotherCall_Function_wrapper : () => {}, 32 | __sidecar__pointerMethod_Function_wrapper : () => {}, 33 | __sidecar__testMethod_Function_wrapper : () => {}, 34 | __sidecar__voidMethod_Function_wrapper : () => {}, 35 | rpc: { 36 | exports: {}, 37 | }, 38 | } 39 | 40 | vm.createContext(context) // Contextify the object. 41 | vm.runInContext(code, context) 42 | t.ok('testMethod' in context.rpc.exports, 'should export testMethod') 43 | t.ok('pointerMethod' in context.rpc.exports, 'should export pointerMethod') 44 | t.ok('anotherCall' in context.rpc.exports, 'should export anotherCall') 45 | t.ok('agentMethod' in context.rpc.exports, 'should export agentCall') 46 | 47 | /** 48 | * Do not export Hook/Interceptor methods 49 | */ 50 | t.notOk('hookMethod' in context.rpc.exports, 'should not export hookMethod') 51 | }) 52 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar-agent/init-agent-script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Call -> moHelper(message) 3 | * MO Sidecar Agent Helper 4 | */ 5 | const moJsFunction = (() => { 6 | /** 7 | * Huan(202107): We might need more code here, that's why we created a closure at here. 8 | */ 9 | const moNativeFunction = new NativeFunction( 10 | __sidecar__moduleBaseAddress.add(0x11e9), 11 | 'int', 12 | ['pointer'], 13 | ) 14 | 15 | return function (content) { 16 | const buf = Memory.allocUtf8String(content) 17 | const ret = moNativeFunction(buf) 18 | return ret + 1 19 | } 20 | })() 21 | 22 | /** 23 | * Hook -> mtNativeCallback 24 | * MT Sidecar Agent Helper 25 | */ 26 | const mtNativeCallback = (() => { 27 | const mtNativeCallback = new NativeCallback(() => {}, 'void', ['pointer']) 28 | const mtNativeFunction = new NativeFunction(mtNativeCallback, 'void', ['pointer']) 29 | 30 | Interceptor.attach( 31 | __sidecar__moduleBaseAddress.add(0x121f), 32 | { 33 | onEnter: args => { 34 | log.verbose('AgentScript', 35 | 'Interceptor.attach() onEnter() arg0: %s', 36 | args[0].readUtf8String(), 37 | ) 38 | /** 39 | * Huan(202107): 40 | * 1. We MUST use `setImmediate()` for calling `mtNativeFunction(arg0), 41 | * or the hook to mtNativeCallback will not be triggered. (???) 42 | * 2. `args` MUST be saved to arg0 so that it can be access in the `setImmediate` 43 | */ 44 | const arg0 = args[0] 45 | setImmediate(() => mtNativeFunction(arg0)) 46 | 47 | /** 48 | * https://github.com/frida/frida/issues/1774#issuecomment-878173544 49 | * Huan(20210713): it seems not work with `Interceptor.replace` too? 50 | */ 51 | // mtNativeFunction(args[0]) 52 | } 53 | } 54 | ) 55 | return mtNativeCallback 56 | })() 57 | -------------------------------------------------------------------------------- /src/decorators/sidecar/metadata-sidecar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | import type { 5 | TypeChain, 6 | } from '../../frida.js' 7 | import type { 8 | TargetPayloadObj, 9 | FunctionTargetType, 10 | } from '../../function-target.js' 11 | 12 | import { SIDECAR_SYMBOL } from './constants.js' 13 | import type { SidecarTargetObj } from './target.js' 14 | 15 | export interface SidecarMetadataFunctionDescription { 16 | name : string 17 | paramTypeList : TypeChain[] 18 | retType? : TypeChain 19 | target : TargetPayloadObj, 20 | } 21 | 22 | export type SidecarMetadataFunctionTypeDescription = { 23 | [type in FunctionTargetType]?: SidecarMetadataFunctionDescription 24 | } 25 | 26 | export interface SidecarMetadata { 27 | nativeFunctionList : SidecarMetadataFunctionTypeDescription[], 28 | interceptorList : SidecarMetadataFunctionTypeDescription[], 29 | initAgentScript? : string, 30 | sidecarTarget? : SidecarTargetObj, 31 | } 32 | 33 | function updateMetadataSidecar ( 34 | target : any, 35 | view : SidecarMetadata, 36 | ): void { 37 | log.verbose('Sidecar', 'updateMetadataSidecar(%s, "%s...")', 38 | target.name, 39 | JSON.stringify(view).substr(0, 20), 40 | ) 41 | // log.silly('Sidecar', 'updateMetadataSidecar(%s, %s)', 42 | // target.name, 43 | // JSON.stringify(view) 44 | // ) 45 | 46 | // Update the parameter names 47 | Reflect.defineMetadata( 48 | SIDECAR_SYMBOL, 49 | view, 50 | target, 51 | ) 52 | } 53 | 54 | function getMetadataSidecar ( 55 | target : Object, 56 | ): undefined | SidecarMetadata { 57 | // Pull the array of parameter names 58 | const view = Reflect.getMetadata( 59 | SIDECAR_SYMBOL, 60 | target, 61 | ) 62 | return view 63 | } 64 | 65 | export { 66 | getMetadataSidecar, 67 | updateMetadataSidecar, 68 | } 69 | -------------------------------------------------------------------------------- /src/decorators/call/call.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import type { 5 | TargetPayloadRaw, 6 | TargetPayloadObj, 7 | } from '../../function-target.js' 8 | import { Ret } from '../../ret.js' 9 | 10 | import { 11 | Call, 12 | } from './call.js' 13 | import { getMetadataCall } from './metadata-call.js' 14 | import { CALL_SYMBOL } from './constants.js' 15 | 16 | test('Call with metadata', async t => { 17 | const TARGET: TargetPayloadRaw = 0x42 18 | const METHOD_NAME = 'testMethod' 19 | 20 | class Test { 21 | 22 | @Call(TARGET) [METHOD_NAME] () { return Ret() } 23 | 24 | } 25 | 26 | const instance = new Test() 27 | const data = Reflect.getMetadata( 28 | CALL_SYMBOL, 29 | instance, 30 | METHOD_NAME, 31 | ) 32 | 33 | /* eslint-disable no-sparse-arrays */ 34 | t.same(data, TARGET, 'should get the Call target data') 35 | }) 36 | 37 | test('getCallTarget()', async t => { 38 | const TARGET: TargetPayloadRaw = 0x42 39 | const METHOD_NAME = 'testMethod' 40 | 41 | class Test { 42 | 43 | @Call(TARGET) [METHOD_NAME] () { return Ret() } 44 | 45 | } 46 | 47 | const instance = new Test() 48 | 49 | const data = getMetadataCall( 50 | instance, 51 | METHOD_NAME, 52 | ) 53 | 54 | t.same(data, TARGET, 'should get Call target data') 55 | }) 56 | 57 | test('getCallTarget() with agent target', async t => { 58 | const TARGET: TargetPayloadObj = { 59 | funcName : 'test', 60 | type : 'agent', 61 | } 62 | const METHOD_NAME = 'testMethod' 63 | 64 | class Test { 65 | 66 | @Call(TARGET) [METHOD_NAME] () { return Ret() } 67 | 68 | } 69 | 70 | const instance = new Test() 71 | 72 | const data = getMetadataCall( 73 | instance, 74 | METHOD_NAME, 75 | ) 76 | 77 | t.same(data, TARGET, 'should get Call target data by agent target') 78 | }) 79 | -------------------------------------------------------------------------------- /src/wrappers/js-ret.ts: -------------------------------------------------------------------------------- 1 | // import { log } from '../config.js' 2 | import type { SidecarMetadataFunctionDescription } from '../decorators/mod.js' 3 | import { log } from '../config.js' 4 | 5 | function jsRet ( 6 | this: SidecarMetadataFunctionDescription, 7 | ): string { 8 | const typeChain = this.retType 9 | if (!typeChain) { 10 | // throw new Error('no .retType found in SidecarMetadataFunctionDescription context!') 11 | /** 12 | * Huan(202108): No type chain means we have not specified `@RetType` 13 | * return raw data 14 | */ 15 | return 'ret' 16 | } 17 | 18 | const [nativeType, ...pointerTypeList] = typeChain 19 | // console.log(nativeType, pointerTypeList) 20 | 21 | const resultChain = [] 22 | 23 | if (nativeType === 'pointer') { 24 | /** 25 | * Pointer native type mapping/chaining 26 | */ 27 | if (pointerTypeList.length > 0) { 28 | resultChain.push( 29 | 'ret.readPointer()', 30 | ) 31 | for (const pointerType of pointerTypeList) { 32 | resultChain.push( 33 | `.read${pointerType}()`, 34 | ) 35 | } 36 | } else { 37 | /** 38 | * Raw pointer 39 | */ 40 | resultChain.push('ret') 41 | } 42 | } else { 43 | /** 44 | * Non-pointer native type mapping 45 | */ 46 | switch (nativeType) { 47 | case 'bool': 48 | log.silly('Sidecar', 'wrappers/js-ret NativeType(bool) for ret') 49 | resultChain.push('Boolean(ret)') 50 | break 51 | case 'void': 52 | log.silly('Sidecar', 'wrappers/js-ret NativeType(void) for ret') 53 | resultChain.push('undefined /* void */') 54 | break 55 | default: // all number types 56 | log.silly('Sidecar', 'wrappers/js-ret NativeType(number<%s>) for ret', nativeType) 57 | resultChain.push('Number(ret)') 58 | break 59 | } 60 | } 61 | 62 | return resultChain.join('') 63 | } 64 | 65 | export { jsRet } 66 | -------------------------------------------------------------------------------- /src/decorators/sidecar/sidecar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | 5 | import { SidecarBody } from '../../sidecar-body/sidecar-body.js' 6 | import { buildSidecarMetadata } from './build-sidecar-metadata.js' 7 | import { guardMetadataSidecar } from './guard-metadata-sidecar.js' 8 | 9 | import { updateMetadataSidecar } from './metadata-sidecar.js' 10 | import type { 11 | SidecarTarget, 12 | } from './target.js' 13 | 14 | function Sidecar ( 15 | sidecarTarget : SidecarTarget, 16 | initAgentScript? : string, 17 | ) { 18 | log.verbose('Sidecar', '@Sidecar(%s%s)', 19 | Array.isArray(sidecarTarget) 20 | ? JSON.stringify(sidecarTarget) 21 | : sidecarTarget, 22 | initAgentScript 23 | ? `, "${initAgentScript.substr(0, 20)}..."` 24 | : '', 25 | ) 26 | 27 | return classDecorator 28 | 29 | /** 30 | * See: https://www.typescriptlang.org/docs/handbook/decorators.html#class-decorators 31 | */ 32 | function classDecorator < 33 | T extends { 34 | new (...args: any[]): {}, 35 | } 36 | > ( 37 | Klass: T, 38 | ) { 39 | log.verbose('Sidecar', 40 | '@Sidecar(%s%s) classDecorator(%s)', 41 | Array.isArray(sidecarTarget) 42 | ? JSON.stringify(sidecarTarget) 43 | : (sidecarTarget || ''), 44 | `"${initAgentScript?.substr(0, 20)}..."` || '', 45 | Klass.name, 46 | ) 47 | 48 | // https://stackoverflow.com/a/14486171/1123955 49 | if (!(Klass.prototype instanceof SidecarBody)) { 50 | throw new Error('Sidecar: the class decorated by @Sidecar must extends from `SidecarBody`') 51 | } 52 | 53 | const meta = buildSidecarMetadata(Klass, { 54 | initAgentScript, 55 | sidecarTarget, 56 | }) 57 | 58 | /** 59 | * Validate metadata for sidecar 60 | */ 61 | guardMetadataSidecar(meta) 62 | 63 | /** 64 | * Save metadata 65 | */ 66 | updateMetadataSidecar(Klass, meta) 67 | } 68 | } 69 | 70 | export { Sidecar } 71 | -------------------------------------------------------------------------------- /src/cli/metadata-handler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | // import slash from 'slash' 3 | import { pathToFileURL } from 'url' 4 | 5 | import { log } from '../config.js' 6 | 7 | import { getMetadataSidecar } from '../decorators/sidecar/metadata-sidecar.js' 8 | import { extractClassNameList } from './extract-class-names.js' 9 | import { 10 | executeWithContext, 11 | } from './vm.js' 12 | 13 | const metadataHandler = async ({ 14 | file, 15 | name, 16 | }: { 17 | file: string, 18 | name?: string, 19 | }): Promise => { 20 | log.verbose('sidecar-dump ', 21 | 'file<%s>, name<%s>', 22 | file, 23 | name || '', 24 | ) 25 | 26 | const fileUrl = pathToFileURL(file) 27 | file = fileUrl.href 28 | 29 | /** 30 | * Check the class name parameter 31 | */ 32 | if (!name) { 33 | const classNameList = await extractClassNameList(fileUrl) 34 | if (classNameList.length === 0) { 35 | throw new Error(`There's no @Sidecar decorated class name found in file ${file}`) 36 | } else if (classNameList.length > 1) { 37 | console.error(`Found multiple @Sidecar decorated classes in ${file}, please specify the class name by --name:\n`) 38 | console.error(classNameList.map(x => ' ' + x).join('\n')) 39 | /** 40 | * return empty string when error 41 | */ 42 | return '' 43 | } 44 | name = classNameList[0] 45 | } 46 | 47 | const runFuncCode = [ 48 | '(async () => {', 49 | [ 50 | `const { ${name} } = await import('${file}')`, 51 | `const metadata = JSON.stringify(getMetadataSidecar(${name}), null, 2)`, 52 | 'return metadata', 53 | ].join('\n'), 54 | '})()', 55 | ].join('\n') 56 | 57 | log.silly('sidecar-dump ', runFuncCode) 58 | 59 | const metadata = await executeWithContext(runFuncCode, { 60 | getMetadataSidecar, 61 | url: fileUrl.href, 62 | }) 63 | 64 | if (!metadata) { 65 | throw new Error('no metadata found') 66 | } 67 | 68 | return metadata 69 | } 70 | 71 | export { metadataHandler } 72 | -------------------------------------------------------------------------------- /src/decorators/hook/hook.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | 5 | import type { 6 | FunctionTarget, 7 | } from '../../function-target.js' 8 | 9 | const HOOK_TARGET_SYMBOL = Symbol('hookTarget') 10 | 11 | function updateMetadataHook ( 12 | target : Object, 13 | propertyKey : string, 14 | functionTarget : FunctionTarget, 15 | ): void { 16 | // Update the parameter names 17 | Reflect.defineMetadata( 18 | HOOK_TARGET_SYMBOL, 19 | functionTarget, 20 | target, 21 | propertyKey, 22 | ) 23 | } 24 | 25 | function getMetadataHook ( 26 | target : Object, 27 | propertyKey : string, 28 | ): undefined | FunctionTarget { 29 | // Pull the array of parameter names 30 | const functionTarget = Reflect.getMetadata( 31 | HOOK_TARGET_SYMBOL, 32 | target, 33 | propertyKey, 34 | ) 35 | return functionTarget 36 | } 37 | 38 | function Hook ( 39 | functionTarget: FunctionTarget, 40 | ) { 41 | log.verbose('Sidecar', '@Hook(%s)', 42 | typeof functionTarget === 'object' ? JSON.stringify(functionTarget) 43 | : typeof functionTarget === 'number' ? functionTarget.toString(16) 44 | : functionTarget, 45 | ) 46 | 47 | return function hookMethodDecorator ( 48 | target : Object, 49 | propertyKey : string, 50 | descriptor : PropertyDescriptor, 51 | ): PropertyDescriptor { 52 | log.verbose('Sidecar', 53 | '@Hook(%s) hookMethodDecorator(%s, %s, descriptor)', 54 | typeof functionTarget === 'object' ? JSON.stringify(functionTarget) 55 | : typeof functionTarget === 'number' ? functionTarget.toString(16) 56 | : functionTarget, 57 | 58 | target.constructor.name, 59 | propertyKey, 60 | ) 61 | 62 | updateMetadataHook( 63 | target, 64 | propertyKey, 65 | functionTarget, 66 | ) 67 | 68 | // Huan(202106) TODO: add a replaced function to show a error message when be called. 69 | return descriptor 70 | } 71 | } 72 | 73 | export { 74 | Hook, 75 | getMetadataHook, 76 | HOOK_TARGET_SYMBOL, 77 | } 78 | -------------------------------------------------------------------------------- /src/wrappers/name-helper.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | argName, 6 | bufName, 7 | nativeArgName, 8 | jsArgName, 9 | } from './name-helpers.js' 10 | 11 | test('bufName()', async t => { 12 | 13 | const TEST_LIST: [[string, number, number?], string][] = [ 14 | [ 15 | ['test', 0, 1], 16 | 'test_Memory_0_1', 17 | ], 18 | [ 19 | ['demo', 3], 20 | 'demo_Memory_3', 21 | ], 22 | ] 23 | 24 | for (const [args, expected] of TEST_LIST) { 25 | const name = bufName(...args) 26 | t.equal(name, expected, `should get the expected name from "bufName(${args.join(', ')})"`) 27 | } 28 | }) 29 | 30 | test('argName()', async t => { 31 | 32 | const TEST_LIST: [number, string][] = [ 33 | [ 34 | 0, 35 | 'args[0]', 36 | ], 37 | [ 38 | 3, 39 | 'args[3]', 40 | ], 41 | ] 42 | 43 | for (const [idx, expected] of TEST_LIST) { 44 | const name = argName(idx) 45 | t.equal(name, expected, `should get the expected name from "argName(${idx})"`) 46 | } 47 | }) 48 | 49 | test('nativeArgName()', async t => { 50 | 51 | const TEST_LIST: [[string, number], string][] = [ 52 | [ 53 | ['test', 0], 54 | 'test_NativeArg_0', 55 | ], 56 | [ 57 | ['demo', 3], 58 | 'demo_NativeArg_3', 59 | ], 60 | ] 61 | 62 | for (const [args, expected] of TEST_LIST) { 63 | const name = nativeArgName(...args) 64 | t.equal(name, expected, `should get the expected name from "nativeArgName(${args.join(', ')})"`) 65 | } 66 | }) 67 | 68 | test('jsArgName()', async t => { 69 | 70 | const TEST_LIST: [[string, number], string][] = [ 71 | [ 72 | ['test', 0], 73 | 'test_JsArg_0', 74 | ], 75 | [ 76 | ['demo', 3], 77 | 'demo_JsArg_3', 78 | ], 79 | ] 80 | 81 | for (const [args, expected] of TEST_LIST) { 82 | const name = jsArgName(...args) 83 | t.equal(name, expected, `should get the expected name from "jsArgName(${args.join(', ')})"`) 84 | } 85 | }) 86 | -------------------------------------------------------------------------------- /src/decorators/ret-type/ret-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | RetType, 6 | } from './ret-type.js' 7 | import { 8 | getMetadataRetType, 9 | } from './metadata-ret-type.js' 10 | import { 11 | RET_TYPE_SYMBOL, 12 | } from './constants.js' 13 | 14 | test('RetType with metadata', async t => { 15 | const NATIVE_TYPE = 'pointer' 16 | const POINTER_TYPE_LIST = ['Pointer', 'Utf8String'] as const 17 | 18 | class Test { 19 | 20 | @RetType( 21 | NATIVE_TYPE, 22 | ...POINTER_TYPE_LIST, 23 | ) 24 | method (): string { return '' } 25 | 26 | } 27 | 28 | const instance = new Test() 29 | const data = Reflect.getMetadata( 30 | RET_TYPE_SYMBOL, 31 | instance, 32 | 'method', 33 | ) 34 | 35 | /* eslint-disable no-sparse-arrays */ 36 | const EXPECTED_DATA = [ 37 | NATIVE_TYPE, 38 | ...POINTER_TYPE_LIST, 39 | ] 40 | t.same(data, EXPECTED_DATA, 'should get the method ret type data') 41 | }) 42 | 43 | test('getRetType()', async t => { 44 | const NATIVE_TYPE = 'pointer' 45 | const POINTER_TYPE_LIST = ['Pointer', 'Utf8String'] as const 46 | 47 | class Test { 48 | 49 | @RetType( 50 | NATIVE_TYPE, 51 | ...POINTER_TYPE_LIST, 52 | ) 53 | method (): string { return '' } 54 | 55 | } 56 | 57 | const instance = new Test() 58 | const typeList = getMetadataRetType( 59 | instance, 60 | 'method', 61 | ) 62 | 63 | const EXPECTED_NAME_LIST = [ 64 | NATIVE_TYPE, 65 | ...POINTER_TYPE_LIST, 66 | ] 67 | t.same(typeList, EXPECTED_NAME_LIST, 'should get decorated method ret type list') 68 | }) 69 | 70 | test('guard ret native types', async t => { 71 | const NATIVE_TYPE = 'pointer' 72 | 73 | const getFixture = () => { 74 | class Test { 75 | 76 | @RetType(NATIVE_TYPE) 77 | testMethod (): number { 78 | return 42 79 | } 80 | 81 | } 82 | 83 | return Test 84 | } 85 | 86 | t.throws(getFixture, 'should throw because the RetType(pointer) is not match the design type `number`') 87 | }) 88 | -------------------------------------------------------------------------------- /tests/fixtures/smoke-testing.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | /** 4 | * Sidecar - https://github.com/huan/sidecar 5 | * 6 | * @copyright 2021 Huan LI (李卓桓) 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | /** 23 | * MUST before all import statements 24 | */ 25 | /// 26 | 27 | // https://stackoverflow.com/questions/64180480/how-do-i-detect-if-an-es-module-is-the-main-module 28 | import esMain from 'es-main' 29 | 30 | import { 31 | Sidecar, 32 | SidecarBody, 33 | Call, 34 | Hook, 35 | ParamType, 36 | RetType, 37 | Ret, 38 | VERSION, 39 | } from 'sidecar' 40 | 41 | @Sidecar('test') 42 | export class ChatboxSidecar extends SidecarBody { 43 | 44 | @Call(0x1234) 45 | @RetType('void') 46 | mo ( 47 | @ParamType('pointer', 'Utf8String') content: string, 48 | ): Promise { 49 | return Ret(content) 50 | } 51 | 52 | @Hook(0x5678) 53 | mt ( 54 | @ParamType('pointer', 'Utf8String') content: string, 55 | ) { 56 | return Ret(content) 57 | } 58 | 59 | } 60 | 61 | async function main () { 62 | const sidecar = new ChatboxSidecar() 63 | sidecar.on('hook', payload => console.log(payload)) 64 | 65 | if (VERSION === '0.0.0') { 66 | throw new Error('VERSION not set!') 67 | } 68 | 69 | console.log('PASSED: smoke testing OK') 70 | return 0 71 | } 72 | 73 | if (esMain(import.meta)) { 74 | main() 75 | .catch(e => { 76 | console.error(e) 77 | process.exit(1) 78 | }) 79 | } 80 | 81 | -------------------------------------------------------------------------------- /src/frida.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar frida 3 | * 4 | * Huan , June 25, 2021 5 | * https://github.com/huan/sidecar 6 | */ 7 | export * from 'frida' 8 | export type { 9 | ScriptMessageHandler, 10 | ScriptDestroyedHandler, 11 | } from 'frida/dist/script' 12 | 13 | /** 14 | * NativeFunction 15 | * https://frida.re/docs/javascript-api/#nativefunction 16 | */ 17 | export type NativeType = 'void' 18 | | 'pointer' 19 | | 'int' 20 | | 'uint' 21 | | 'long' 22 | | 'ulong' 23 | | 'char' 24 | | 'uchar' 25 | | 'size_t' 26 | | 'ssize_t' 27 | | 'float' 28 | | 'double' 29 | | 'int8' 30 | | 'uint8' 31 | | 'int16' 32 | | 'uint16' 33 | | 'int32' 34 | | 'uint32' 35 | | 'int64' 36 | | 'uint64' 37 | | 'bool' 38 | 39 | /** 40 | * NativePointer 41 | * https://frida.re/docs/javascript-api/#nativepointer 42 | */ 43 | export type PointerType = 'Pointer' 44 | | 'S8' 45 | | 'U8' 46 | | 'S16' 47 | | 'U16' 48 | | 'S32' 49 | | 'U32' 50 | | 'Short' 51 | | 'UShort' 52 | | 'Int' 53 | | 'UInt' 54 | | 'Float' 55 | | 'Double' 56 | | 'S64' 57 | | 'U64' 58 | | 'Long' 59 | | 'ULong' 60 | | 'ByteArray' 61 | | 'CString' 62 | | 'Utf8String' 63 | | 'Utf16String' 64 | | 'AnsiString' 65 | 66 | export type TypeChain = [NativeType, ...PointerType[]] 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | package-lock.json 107 | t.* 108 | t/ 109 | a.out 110 | -------------------------------------------------------------------------------- /scripts/npm-pack-testing.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # 5 | # Huan(202107) 6 | # We have generated the fixtures for the output of `sidecar-dump` utility. 7 | # We will use the fixtures to test the `sidecar-dump` utility. 8 | # However, the output is different when we are running it on different operation systems, 9 | # suck like Linux / Windows. 10 | # So we have to ignore the following characters: 11 | # - \r: --strip-trailing-cr 12 | # - spaces: --ignore-space-change 13 | # - blank-lines: --ignore-blank-lines 14 | # 15 | # Credit: https://stackoverflow.com/a/48287203/1123955 16 | # 17 | function diff_lines () { 18 | diff \ 19 | -y \ 20 | --strip-trailing-cr \ 21 | --suppress-common-lines \ 22 | --ignore-space-change \ 23 | --ignore-blank-lines \ 24 | $1 \ 25 | $2 \ 26 | | wc -l 27 | } 28 | 29 | npm run dist 30 | npm pack 31 | 32 | TMPDIR="/tmp/npm-pack-testing.$$" 33 | mkdir "$TMPDIR" 34 | # trap "rm -fr '$TMPDIR'" EXIT 35 | 36 | mv ./*-*.*.*.tgz "$TMPDIR" 37 | cp tests/fixtures/* "$TMPDIR" 38 | 39 | cd $TMPDIR 40 | npm init -y 41 | npm install ./*-*.*.*.tgz \ 42 | es-main \ 43 | pkg-jq \ 44 | @chatie/tsconfig 45 | 46 | # ES Modules 47 | npx pkg-jq -i '.type="module"' 48 | 49 | npx tsc \ 50 | --target es2020 \ 51 | --module es2020 \ 52 | --skipLibCheck \ 53 | --strict \ 54 | --experimentalDecorators \ 55 | --emitDecoratorMetadata \ 56 | --moduleResolution node \ 57 | smoke-testing.ts 58 | 59 | echo 60 | echo "ES Module: pack testing..." 61 | node smoke-testing.js 62 | 63 | # 64 | # Dump testing (ESM only) 65 | # 66 | npx sidecar-dump metadata smoke-testing.ts > smoke-testing.metadata.json 67 | if [[ $(diff_lines smoke-testing.metadata.json sidecar-dump.metadata.smoke-testing.json.fixture) -gt 10 ]]; then 68 | >&2 echo "FAILED: sidecar-dump metadata smoke-testing.ts" 69 | exit 1 70 | fi 71 | echo "PASSED: sidecar-dump metadata smoke-testing.ts" 72 | 73 | npx sidecar-dump source smoke-testing.ts > smoke-testing.source.js 74 | if [[ $(diff_lines smoke-testing.source.js sidecar-dump.source.smoke-testing.js.fixture) -gt 10 ]]; then 75 | >&2 echo "FAILED: sidecar-dump source smoke-testing.ts" 76 | exit 1 77 | fi 78 | echo "PASSED: sidecar-dump source smoke-testing.ts (ESM only)" 79 | -------------------------------------------------------------------------------- /tests/decorator-execute-order.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Huan(202106) see: 4 | * http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4 5 | * 6 | * Another great blog post with the decorator runtime order tests: 7 | * https://medium.com/jspoint/anatomy-of-typescript-decorators-and-their-usage-patterns-487729b34ae6 8 | */ 9 | import { test } from 'tstest' 10 | 11 | test('decorator execute order', async t => { 12 | const orderList = [] as string[] 13 | 14 | // arguments.length === 1 15 | const decorateClass = (target: Function) => { orderList.push('class:' + target.name) } 16 | // arguments.length === 2 17 | const decorateProperty = (_target: Object, propertyKey: string) => { orderList.push('property:' + propertyKey) } 18 | // arguments.length === 3 && typeof arguments[2] === 'number' 19 | const decorateParam = (_target: Object, methodKey: string, index: number) => { orderList.push('param:' + methodKey + '/' + index) } 20 | // arguments.length === 3 && typeof arguments[2] === 'object' 21 | const decorateMethod = (_target: Object, methodKey: string, _descriptor: any) => { orderList.push('method:' + methodKey) } 22 | 23 | @decorateClass 24 | class Test { 25 | 26 | @decorateProperty _prop1: any 27 | @decorateProperty _prop2: any 28 | 29 | @decorateMethod method1 ( 30 | @decorateParam _arg: any, 31 | ) {} 32 | 33 | @decorateMethod method2 ( 34 | @decorateParam _arg1: any, 35 | @decorateParam _arg2: any, 36 | ) {} 37 | 38 | } 39 | void Test 40 | 41 | /** 42 | * Huan(202106): Decorator Evaluation 43 | * There is a well defined order to how decorators applied to 44 | * various declarations inside of a class are applied: 45 | * https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-evaluation 46 | */ 47 | const EXPECTED_ORDER_LIST = [ 48 | 'property:_prop1', 49 | 'property:_prop2', 50 | 'param:method1/0', 51 | 'method:method1', 52 | 'param:method2/1', 53 | 'param:method2/0', 54 | 'method:method2', 55 | 'class:Test', 56 | ] 57 | t.same(orderList, EXPECTED_ORDER_LIST, 'should get the expected execute order of decorators') 58 | }) 59 | -------------------------------------------------------------------------------- /examples/chatbox-sidecar-pro/sidecar-config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import path from 'path' 20 | 21 | import type { FunctionTarget } from '../../src/function-target.js' 22 | 23 | import { codeRoot } from '../../src/cjs.js' 24 | 25 | /** 26 | * See: https://github.com/frida/frida-node/blob/master/test/data/index.ts 27 | */ 28 | function targetProgram () { 29 | const chatboxNameList = [ 30 | 'chatbox', 31 | '-', 32 | process.platform, 33 | ] 34 | 35 | if (process.platform === 'win32') { 36 | chatboxNameList.push('.exe') 37 | } 38 | 39 | return path.resolve( 40 | codeRoot, 41 | 'examples', 42 | 'chatbox', 43 | chatboxNameList.join(''), 44 | ) 45 | } 46 | 47 | interface TargetAddressConfig { 48 | [platform: string]: { 49 | [arch: string]: { 50 | [call: string]: FunctionTarget, 51 | } 52 | } 53 | } 54 | 55 | const chatboxConfig: TargetAddressConfig = { 56 | darwin: { 57 | arm64: { 58 | mo: 0x3d88, 59 | mt: 0x3dc8, 60 | }, 61 | }, 62 | linux: { 63 | x64: { 64 | mo: 0x11e9, 65 | mt: 0x121f, 66 | }, 67 | }, 68 | win32: { 69 | x64: { 70 | mo: 0x0, 71 | mt: 0x0, 72 | }, 73 | }, 74 | } 75 | 76 | const targetAddressConfig = (config: TargetAddressConfig) => ( 77 | call: string, 78 | ) => config[ 79 | process.platform 80 | ]![ 81 | process.arch 82 | ]![ 83 | call 84 | ]! 85 | 86 | const targetAddress = targetAddressConfig(chatboxConfig) 87 | 88 | export { 89 | targetProgram, 90 | targetAddress, 91 | } 92 | -------------------------------------------------------------------------------- /src/function-target.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import { 4 | addressTarget, 5 | agentTarget, 6 | exportTarget, 7 | TargetPayloadAddress, 8 | TargetPayloadAgent, 9 | TargetPayloadExport, 10 | } from './function-target.js' 11 | 12 | test('addressTarget()', async t => { 13 | const DATA = 0x1234 14 | const EXPECTED: TargetPayloadAddress = { 15 | address : '0x1234', 16 | moduleName : null, 17 | type : 'address', 18 | } 19 | 20 | const result = addressTarget(DATA) 21 | 22 | t.same(result, EXPECTED, 'should get the correct address target for number') 23 | }) 24 | 25 | test('addressTarget() with module', async t => { 26 | const DATA = 0x1234 27 | const MODULE_NAME = 'myModule' 28 | const EXPECTED: TargetPayloadAddress = { 29 | address : '0x1234', 30 | moduleName : MODULE_NAME, 31 | type : 'address', 32 | } 33 | 34 | const result = addressTarget(DATA, MODULE_NAME) 35 | 36 | t.same(result, EXPECTED, 'should get the correct address target for number and module name') 37 | }) 38 | 39 | test('agentTarget()', async t => { 40 | const DATA = 'myPtr' 41 | const EXPECTED: TargetPayloadAgent = { 42 | funcName : DATA, 43 | type : 'agent', 44 | } 45 | 46 | const result = agentTarget(DATA) 47 | 48 | t.same(result, EXPECTED, 'should get the correct agent target for var name') 49 | }) 50 | 51 | test('exportTarget()', async t => { 52 | const DATA = 'testExport' 53 | const EXPECTED: TargetPayloadExport = { 54 | exportName : DATA, 55 | moduleName : null, 56 | type : 'export', 57 | } 58 | 59 | const result = exportTarget(DATA) 60 | 61 | t.same(result, EXPECTED, 'should get the correct export target for number') 62 | }) 63 | 64 | test('exportTarget() with export', async t => { 65 | const DATA = 'testExport' 66 | const MODULE_NAME = 'myModule' 67 | const EXPECTED: TargetPayloadExport = { 68 | exportName : DATA, 69 | moduleName : MODULE_NAME, 70 | type : 'export', 71 | } 72 | 73 | const result = exportTarget(DATA, MODULE_NAME) 74 | 75 | t.same(result, EXPECTED, 'should get the correct export target for name and module name') 76 | }) 77 | -------------------------------------------------------------------------------- /src/cli/source-handler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable sort-keys */ 2 | import { pathToFileURL } from 'url' 3 | 4 | import { log } from '../config.js' 5 | 6 | import { getMetadataSidecar } from '../decorators/sidecar/metadata-sidecar.js' 7 | import { buildAgentSource } from '../agent/build-agent-source.js' 8 | 9 | import { extractClassNameList } from './extract-class-names.js' 10 | import { executeWithContext } from './vm.js' 11 | 12 | const sourceHandler = async ({ 13 | file, 14 | name, 15 | }: { 16 | file: string, 17 | name?: string, 18 | }): Promise => { 19 | log.verbose('sidecar-dump ', 20 | 'file<%s>%s', 21 | file, 22 | name 23 | ? `, name<${name}>` 24 | : '', 25 | ) 26 | 27 | const fileUrl = pathToFileURL(file) 28 | file = fileUrl.href 29 | 30 | /** 31 | * Check the class name parameter 32 | */ 33 | if (!name) { 34 | const classNameList = await extractClassNameList(fileUrl) 35 | if (classNameList.length === 0) { 36 | throw new Error(`There's no @Sidecar decorated class name found in file ${file}`) 37 | } else if (classNameList.length > 1) { 38 | console.error(`Found multiple @Sidecar decorated classes in ${file}, please specify the class name by --name:\n`) 39 | console.error(classNameList.map(x => ' ' + x).join('\n')) 40 | /** 41 | * return empty string when error 42 | */ 43 | return '' 44 | } 45 | name = classNameList[0] 46 | log.silly('sidecar-dump ', 47 | 'detected class name: "%s"', 48 | name, 49 | ) 50 | } 51 | 52 | const runFuncCode = [ 53 | '(async () => {', 54 | [ 55 | `const { ${name} } = await import('${file}')`, 56 | `const metadata = getMetadataSidecar(${name})`, 57 | 'const agentSource = await buildAgentSource(metadata)', 58 | 'return agentSource', 59 | ].join('\n'), 60 | '})()', 61 | ].join('\n') 62 | 63 | log.silly('sidecar-dump ', runFuncCode) 64 | 65 | const agentSource = await executeWithContext(runFuncCode, { 66 | buildAgentSource, 67 | getMetadataSidecar, 68 | url: fileUrl.href, 69 | }) 70 | 71 | if (!agentSource) { 72 | throw new Error('no agentSource found') 73 | } 74 | 75 | return agentSource 76 | } 77 | 78 | export { sourceHandler } 79 | -------------------------------------------------------------------------------- /examples/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { wrapAsyncError } from 'gerror' 20 | 21 | import { 22 | attach, 23 | detach, 24 | } from '../src/mod.js' 25 | 26 | /** 27 | * The `ChatboxSidecarPro` has more complicated settings 28 | * You can read it and learn more in the [example](./chatbox-sidecar-pro.ts) 29 | */ 30 | import { ChatboxSidecarAgent } from './chatbox-sidecar-agent/chatbox-sidecar-agent.js' 31 | // import { ChatboxSidecarPro } from './chatbox-sidecar-pro/chatbox-sidecar-pro.js' 32 | // import { ChatboxSidecar } from './chatbox-sidecar.js' 33 | 34 | async function main () { 35 | const sidecar = new ChatboxSidecarAgent() 36 | // const sidecar = new ChatboxSidecarPro() 37 | // const sidecar = new ChatboxSidecar() 38 | 39 | /** 40 | * 0. Initialize the sidecar by `attach()` 41 | */ 42 | console.log('sidecar attaching...') 43 | await attach(sidecar) 44 | console.log('sidecar attached.') 45 | 46 | /** 47 | * 1. @Hook sidecar.mt(...) 48 | */ 49 | sidecar.on('hook', async ({ method, args }) => { 50 | console.log(`sidecar @Hook() ${method}() received message: "${args[0]}"`) 51 | 52 | /** 53 | * 2. @Call sidecar.mo(...) 54 | */ 55 | const reply = 'sidecar @Call() mt() greeting!' 56 | const ret = await sidecar.mo(reply) 57 | console.log(`replied with: "${reply}", ret: ${ret}\n`) 58 | }) 59 | 60 | const clean = wrapAsyncError(console.error)(() => detach(sidecar)) 61 | process.on('SIGINT', clean) 62 | process.on('SIGTERM', clean) 63 | } 64 | 65 | main() 66 | .catch(console.error) 67 | -------------------------------------------------------------------------------- /scripts/post-install.cjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Huan(202108): Frida using `prebuild` NPM module to publish artifacts to GitHub Release, 4 | * and using `prebuild-install` to download the binary at installation. 5 | * 6 | * In China, the internet has been blocked to visit some of the AWS S3, 7 | * which might block the user to `npm install frida`. 8 | * 9 | * https://github.com/wechaty/wechaty-puppet-xp/issues/3 10 | * 11 | */ 12 | const spawn = require('cross-spawn') 13 | const path = require('path') 14 | const fs = require('fs') 15 | const pkgDir = require('pkg-dir') 16 | 17 | async function needReinstall () { 18 | try { 19 | await import('frida') 20 | return false 21 | } catch (_) { 22 | return true 23 | } 24 | } 25 | 26 | async function reinstall () { 27 | console.error('Sidecar: checking frida installation (frida_binding.node) failed, try to reinstall with cdn mirror...') 28 | 29 | const pkgRoot = await pkgDir(__dirname) 30 | if (!pkgRoot) { 31 | throw new Error('no package.json found') 32 | } 33 | 34 | const innerCwd = path.resolve(pkgRoot, 'node_modules/frida') 35 | const outerCwd = path.resolve(pkgRoot, '../frida') 36 | 37 | const cwd = fs.existsSync(innerCwd) ? innerCwd 38 | : fs.existsSync(outerCwd) ? outerCwd 39 | : undefined 40 | 41 | if (!cwd) { 42 | throw new Error('can not find "node_modules/frida"') 43 | } 44 | 45 | const args = [ 46 | 'prebuild-install', 47 | '--tag-prefix', 48 | '', 49 | ] 50 | 51 | const env = { 52 | ...process.env, 53 | npm_config_frida_binary_host_mirror: 'https://cdn.chatie.io/mirrors/github.com/frida/frida/releases/download', 54 | } 55 | 56 | const ret = spawn.sync( 57 | 'npx', 58 | [...args], 59 | { 60 | cwd, 61 | env, 62 | }, 63 | ) 64 | 65 | // console.log(ret) 66 | if (ret.status === 0) { 67 | console.log('Sidecar: install frida_binding.node successed.') 68 | } else { 69 | const message = ret.error || ret.stdout.toString() || ret.stderr.toString() 70 | console.error('Sidecar: install failed:', message) 71 | } 72 | } 73 | 74 | async function main () { 75 | if (await needReinstall()) { 76 | await reinstall() 77 | } 78 | } 79 | 80 | main() 81 | .catch(console.error) 82 | -------------------------------------------------------------------------------- /src/decorators/name/name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Data Type: 3 | * https://en.wikipedia.org/wiki/Data_type 4 | * 5 | * TypeScript Decorators: Parameter Decorators 6 | * https://blog.wizardsoftheweb.pro/typescript-decorators-parameter-decorators/ 7 | * 8 | * TypeScript Decorators: Parameter Decorators 9 | * https://blog.wotw.pro/typescript-decorators-parameter-decorators/ 10 | */ 11 | import { log } from '../../config.js' 12 | 13 | const PARAMETER_NAME_SYMBOL = Symbol('parameterName') 14 | 15 | /** 16 | * Credit: https://blog.wotw.pro/typescript-decorators-parameter-decorators/ 17 | */ 18 | function updateParameterName ( 19 | target : Object, 20 | propertyKey : string, 21 | parameterIndex : number, 22 | name : string, 23 | ) { 24 | // Pull the array of parameter names 25 | const parameterNameList = Reflect.getOwnMetadata( 26 | PARAMETER_NAME_SYMBOL, 27 | target, 28 | propertyKey, 29 | ) || [] 30 | // Add the current parameter name 31 | parameterNameList[parameterIndex] = name 32 | // Update the parameter names 33 | Reflect.defineMetadata( 34 | PARAMETER_NAME_SYMBOL, 35 | parameterNameList, 36 | target, 37 | propertyKey, 38 | ) 39 | } 40 | 41 | function getParameterName ( 42 | target : Object, 43 | propertyKey : string, 44 | parameterIndex : number, 45 | ) { 46 | // Pull the array of parameter names 47 | const parameterNameList = Reflect.getMetadata( 48 | PARAMETER_NAME_SYMBOL, 49 | target, 50 | propertyKey, 51 | ) || [] 52 | return parameterNameList[parameterIndex] 53 | } 54 | 55 | function Name (parameterName: string) { 56 | log.verbose('Sidecar', 57 | '@Name(%s)', 58 | parameterName, 59 | ) 60 | 61 | return function nameParameterDecorator ( 62 | target : Object, 63 | propertyKey : string, 64 | parameterIndex : number, 65 | ) { 66 | log.verbose('Sidecar', 67 | '@Name(%s) nameParameterDecorator(%s, %s, %s)', 68 | parameterName, 69 | 70 | target.constructor.name, 71 | propertyKey, 72 | parameterIndex, 73 | ) 74 | 75 | updateParameterName( 76 | target, 77 | propertyKey, 78 | parameterIndex, 79 | parameterName, 80 | ) 81 | } 82 | } 83 | 84 | export { 85 | getParameterName, 86 | Name, 87 | PARAMETER_NAME_SYMBOL, 88 | } 89 | -------------------------------------------------------------------------------- /src/mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | import { VERSION } from './version.js' 20 | import { Ret } from './ret.js' 21 | import { 22 | SidecarBody, 23 | attach, 24 | detach, 25 | } from './sidecar-body/mod.js' 26 | 27 | import { 28 | INIT_SYMBOL, 29 | ATTACH_SYMBOL, 30 | DETACH_SYMBOL, 31 | 32 | SCRIPT_DESTROYED_HANDLER_SYMBOL, 33 | SCRIPT_MESSAGRE_HANDLER_SYMBOL, 34 | 35 | LOG_EVENT_HANDLER, 36 | HOOK_EVENT_HANDLER, 37 | } from './sidecar-body/constants.js' 38 | 39 | /** 40 | * Decorators 41 | */ 42 | import { Call } from './decorators/call/mod.js' 43 | import { Hook } from './decorators/hook/mod.js' 44 | import { ParamType } from './decorators/param-type/mod.js' 45 | import { RetType } from './decorators/ret-type/mod.js' 46 | import { Sidecar } from './decorators/sidecar/mod.js' 47 | 48 | /** 49 | * Target helpers 50 | */ 51 | import { 52 | addressTarget, 53 | agentTarget, 54 | exportTarget, 55 | FunctionTarget, 56 | } from './function-target.js' 57 | 58 | const debug = { 59 | ATTACH_SYMBOL, 60 | DETACH_SYMBOL, 61 | HOOK_EVENT_HANDLER, 62 | INIT_SYMBOL, 63 | LOG_EVENT_HANDLER, 64 | SCRIPT_DESTROYED_HANDLER_SYMBOL, 65 | SCRIPT_MESSAGRE_HANDLER_SYMBOL, 66 | } 67 | 68 | export type { 69 | FunctionTarget, 70 | } 71 | export { 72 | addressTarget, 73 | agentTarget, 74 | attach, 75 | Call, 76 | debug, 77 | detach, 78 | exportTarget, 79 | Hook, 80 | ParamType, 81 | Ret, 82 | RetType, 83 | Sidecar, 84 | SidecarBody, 85 | VERSION, 86 | } 87 | -------------------------------------------------------------------------------- /src/decorators/call/update-rpc-descriptor.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | import { EventEmitter } from 'events' 4 | import { 5 | Ret, 6 | RET_SYMBOL, 7 | } from '../../ret.js' 8 | import { DEBUG_CALL_RET_ERROR } from './constants.js' 9 | 10 | import { 11 | updateRpcDescriptor, 12 | } from './update-rpc-descriptor.js' 13 | 14 | test('update & get call target metadata', async t => { 15 | 16 | const AFTER_VALUE = 42 17 | 18 | class Test { 19 | 20 | script = { 21 | exports: { 22 | testMethod: () => AFTER_VALUE, 23 | }, 24 | } 25 | 26 | testMethod () { return Ret() } 27 | 28 | } 29 | 30 | const test = new Test() 31 | 32 | const descriptor = Reflect.getOwnPropertyDescriptor(Test.prototype, 'testMethod') 33 | const beforeValue = await descriptor?.value() 34 | t.equal(beforeValue, RET_SYMBOL, 'should get RET_SYMBOL before update rpc method') 35 | 36 | const rpcDescriptor = updateRpcDescriptor( 37 | Test, 38 | 'testMethod', 39 | descriptor!, 40 | ) 41 | 42 | const rpcValue = await rpcDescriptor.value.bind(test)() 43 | t.equal(rpcValue, AFTER_VALUE, 'should get AFTER_VALUE from rpcValue') 44 | 45 | Object.defineProperty(Test.prototype, 'testMethod', rpcDescriptor) 46 | const ret = await test.testMethod() 47 | t.equal(ret, AFTER_VALUE, 'should get a updated method return value') 48 | 49 | await new Promise(setImmediate) 50 | t.notOk((Test as any)[DEBUG_CALL_RET_ERROR], 'should not trigger error if the method returns "Ret()"') 51 | }) 52 | 53 | test('method to be proxyed must retur "Ret()"', async t => { 54 | 55 | const RET_VALUE = 42 56 | 57 | class Test extends EventEmitter { 58 | 59 | method () { 60 | return Promise.resolve(RET_VALUE) 61 | } 62 | 63 | } 64 | 65 | const descriptor = Reflect.getOwnPropertyDescriptor(Test.prototype, 'method') 66 | const beforeValue = await descriptor?.value() 67 | t.equal(beforeValue, RET_VALUE, 'should get ret value from the descriptor') 68 | 69 | updateRpcDescriptor( 70 | Test, 71 | 'method', 72 | descriptor!, 73 | ) 74 | await new Promise(setImmediate) 75 | 76 | t.ok((Test as any)[DEBUG_CALL_RET_ERROR], 'should trigger error if the method does not return "Ret()" (We can safely ignore the Error message above this test)') 77 | }) 78 | -------------------------------------------------------------------------------- /src/decorators/sidecar/target.ts: -------------------------------------------------------------------------------- 1 | import type { spawn } from 'child_process' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import type { TargetProcess } from 'frida' 5 | 6 | import { log } from '../../config.js' 7 | 8 | type SpawnParameters = Parameters 9 | export type SidecarTargetRawSpawn = [ 10 | command : SpawnParameters[0], 11 | args? : SpawnParameters[1], 12 | ] 13 | export type SidecarTargetRaw = TargetProcess 14 | | SidecarTargetRawSpawn 15 | 16 | interface SidecarTargetObjProcess { 17 | type: 'process', 18 | target: TargetProcess 19 | } 20 | export interface SidecarTargetObjSpawn { 21 | type: 'spawn', 22 | target: SidecarTargetRawSpawn, 23 | } 24 | export type SidecarTargetObj = SidecarTargetObjProcess 25 | | SidecarTargetObjSpawn 26 | 27 | export type SidecarTarget = SidecarTargetRaw 28 | | SidecarTargetObj 29 | 30 | const sidecarTargetObjProcess = (target: TargetProcess) => ({ 31 | target, 32 | type: 'process', 33 | }) as SidecarTargetObjProcess 34 | 35 | const sidecarTargetObjSpawn = (target: SidecarTargetRawSpawn) => ({ 36 | target, 37 | type: 'spawn', 38 | }) as SidecarTargetObjSpawn 39 | 40 | const normalizeSidecarTarget = ( 41 | target?: SidecarTarget, 42 | ): undefined | SidecarTargetObj => { 43 | if (typeof target === 'string' || typeof target === 'number') { 44 | return sidecarTargetObjProcess(target) 45 | } else if (Array.isArray(target)) { 46 | const command = path.resolve(process.cwd(), target[0]) 47 | if (fs.existsSync(command)) { 48 | log.verbose('Sidecar', 'normalizeSidecarTarget() spawn command found: "%s"', command) 49 | target[0] = command 50 | } else { 51 | log.warn('Sidecar', 'normalizeSidecarTarget() spawn command not found: "%s"', command) 52 | } 53 | 54 | return sidecarTargetObjSpawn(target) 55 | } else { 56 | return target 57 | } 58 | } 59 | 60 | const isSidecarTargetProcess = (target?: SidecarTarget): target is SidecarTargetObjProcess => typeof target === 'object' && !Array.isArray(target) && target.type === 'process' 61 | const isSidecarTargetSpawn = (target?: SidecarTarget): target is SidecarTargetObjSpawn => typeof target === 'object' && !Array.isArray(target) && target.type === 'spawn' 62 | 63 | export { 64 | normalizeSidecarTarget, 65 | isSidecarTargetProcess, 66 | isSidecarTargetSpawn, 67 | } 68 | -------------------------------------------------------------------------------- /src/decorators/sidecar/target.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | normalizeSidecarTarget, 6 | SidecarTargetRawSpawn, 7 | SidecarTargetObjSpawn, 8 | isSidecarTargetSpawn, 9 | isSidecarTargetProcess, 10 | } from './target.js' 11 | 12 | test('normalizeSidecarTarget() processTarget: number', async t => { 13 | const TARGET = 0x1234 14 | const EXPECTED = { 15 | target: TARGET, 16 | type: 'process', 17 | } 18 | 19 | const actual = normalizeSidecarTarget(TARGET) 20 | t.same(actual, EXPECTED, 'should normalize number to process target') 21 | 22 | t.ok(isSidecarTargetProcess(actual), 'should be a process target') 23 | }) 24 | 25 | test('normalizeSidecarTarget() processTarget: string', async t => { 26 | const TARGET = 'namedTarget' 27 | const EXPECTED = { 28 | target: TARGET, 29 | type: 'process', 30 | } 31 | 32 | const actual = normalizeSidecarTarget(TARGET) 33 | t.same(actual, EXPECTED, 'should normalize string to process target') 34 | 35 | t.ok(isSidecarTargetProcess(actual), 'should be a process target') 36 | }) 37 | 38 | test('normalizeSidecarTarget() spawnTarget: []', async t => { 39 | const TARGET = [ 40 | 'command', 41 | [ 42 | 'arg1', 43 | 'arg2', 44 | ], 45 | ] as SidecarTargetRawSpawn 46 | const EXPECTED = { 47 | target: TARGET, 48 | type: 'spawn', 49 | } 50 | 51 | const actual = normalizeSidecarTarget(TARGET) 52 | t.same(actual, EXPECTED, 'should normalize array to spawn target') 53 | 54 | t.ok(isSidecarTargetSpawn(actual), 'should be a spawn target') 55 | }) 56 | 57 | test('normalizeSidecarTarget() obj: {}', async t => { 58 | const TARGET = { 59 | target: [ 60 | 'command', 61 | ['arg1'], 62 | ], 63 | type: 'spawn', 64 | } as SidecarTargetObjSpawn 65 | 66 | const actual = normalizeSidecarTarget(TARGET) 67 | t.same(actual, TARGET, 'should normalize obj unchanged') 68 | 69 | t.ok(isSidecarTargetSpawn(actual), 'should be a spawn target') 70 | }) 71 | 72 | test('normalizeSidecarTarget() undefined', async t => { 73 | const TARGET = undefined 74 | const actual = normalizeSidecarTarget(TARGET) 75 | t.same(actual, undefined, 'should normalize undefined to undefined') 76 | 77 | t.notOk(isSidecarTargetSpawn(actual), 'should not be a spawn target') 78 | t.notOk(isSidecarTargetProcess(actual), 'should not be a process target') 79 | }) 80 | -------------------------------------------------------------------------------- /src/frida.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | 3 | import { test } from 'tstest' 4 | 5 | import type { 6 | ScriptMessageHandler, 7 | NativeType, 8 | PointerType, 9 | } from './frida.js' 10 | import { 11 | normalizeFunctionTarget, 12 | TargetPayloadObj, 13 | TargetPayloadRaw, 14 | } from './function-target.js' 15 | 16 | /** 17 | * Huan(202109): #18 - `dts` package conflict node_modules/.bin/tsc version 18 | * https://github.com/huan/sidecar/issues/18 19 | * 20 | * Testing static types in TypeScript 21 | * https://2ality.com/2019/07/testing-static-types.html 22 | */ 23 | type AssertEqual = 24 | T extends Expected 25 | ? (Expected extends T ? true : never) 26 | : never; 27 | 28 | test('PointerType typing', async t => { 29 | type EXPECTED_TYPE = 'Pointer' | 'Int' | 'Utf8String' 30 | type T = Extract 31 | const typeTest: AssertEqual = true 32 | t.ok(typeTest, 'PointerType should be typing right') 33 | }) 34 | 35 | test('NativeType typing', async t => { 36 | type EXPECTED_TYPE = 'void' | 'pointer' | 'int' 37 | type T = Extract 38 | const typeTest: AssertEqual = true 39 | t.ok(typeTest, 'NativeType should be typing right') 40 | }) 41 | 42 | test('TargetType typing', async t => { 43 | type EXPECTED_TYPE = number | string 44 | const typeTest: AssertEqual = true 45 | t.ok(typeTest, 'TargetType should be typing right') 46 | }) 47 | 48 | test('ScriptMessageHandler typing', async t => { 49 | type EXPECTED_TYPE = Buffer | null 50 | const handler: Parameters[1] = {} as any 51 | const typeTest: AssertEqual = true 52 | t.ok(typeTest, 'ScriptMessageHandler should be typing right') 53 | }) 54 | 55 | test('normalizeFunctionTarget()', async t => { 56 | const TEST_LIST: [ 57 | TargetPayloadRaw, 58 | TargetPayloadObj, 59 | ][] = [ 60 | [ 61 | 'stringTarget', 62 | { funcName: 'stringTarget', type: 'agent' }, 63 | ], 64 | [ 65 | 0x42, 66 | { address: '0x42', moduleName: null, type: 'address' }, 67 | ], 68 | ] 69 | 70 | const result = TEST_LIST.map(pair => pair[0]).map(normalizeFunctionTarget) 71 | const expected = TEST_LIST.map(pair => pair[1]) 72 | 73 | t.same(result, expected, 'should normalize function target as expected') 74 | }) 75 | -------------------------------------------------------------------------------- /src/agent/templates/agent.mustache: -------------------------------------------------------------------------------- 1 | /***************************************** 2 | * File: "templates/agent.mustache" 3 | * -------------------------------- 4 | * Sidecar Frida Agent Mustache Template 5 | * 6 | * https://github.com/huan/sidecar 7 | * Huan 8 | * June 24, 2021 9 | * 10 | * All sidecar agent variable/function names 11 | * MUST be started with the namespace `__sidecar__` 12 | * 13 | * i.e.: 14 | * const __sidecar__variableName = {} 15 | * function __sidecar__functionName () {} 16 | * 17 | *****************************************/ 18 | 19 | /*********************************** 20 | * File: "templates/agent.mustache" 21 | * > Partials: "libs/payload.cjs" 22 | ***********************************/ 23 | {{> libs/payload.cjs}} 24 | 25 | /*********************************** 26 | * File: "templates/agent.mustache" 27 | * > Partials: "libs/log.cjs" 28 | ***********************************/ 29 | {{> libs/log.cjs }} 30 | 31 | log.level('{{ logLevel }}') 32 | 33 | /**************************************************** 34 | * File: "templates/agent.mustache" 35 | * > Get base address for target "{{{ sidecarTarget.target }}}" 36 | ****************************************************/ 37 | const __sidecar__moduleBaseAddress = Module.getBaseAddress('{{{ moduleName }}}') 38 | 39 | /*********************************** 40 | * File: "templates/agent.mustache" 41 | * > Variable: "initAgentScript" 42 | ***********************************/ 43 | {{{ initAgentScript }}} 44 | 45 | /******************************************** 46 | * File: "templates/agent.mustache" 47 | * > Partials: "native-functions.mustache" 48 | ********************************************/ 49 | {{> native-functions.mustache }} 50 | 51 | /************************************** 52 | * File: "templates/agent.mustache" 53 | * > Partials: "interceptors.mustache" 54 | **************************************/ 55 | {{> interceptors.mustache }} 56 | 57 | /******************** 58 | * RPC Exports Init * 59 | ********************/ 60 | function __sidecar__init () { 61 | log.verbose('SidecarAgent', 'init()') 62 | 63 | /** 64 | * DEBUG: Huan(202106) return 42 to let caller to make sure that 65 | * this function has been runned successfully. 66 | */ 67 | return 42 68 | } 69 | 70 | rpc.exports = { 71 | init: __sidecar__init, 72 | ...rpc.exports, 73 | } 74 | 75 | /************************************** 76 | * File: "templates/agent.mustache" 77 | * > Partials: "rpc-exports.mustache" 78 | **************************************/ 79 | {{> rpc-exports.mustache }} 80 | -------------------------------------------------------------------------------- /src/decorators/call/update-rpc-descriptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | log, 3 | } from '../../config.js' 4 | import { RET_SYMBOL } from '../../ret.js' 5 | import type { SidecarBody } from '../../sidecar-body/mod.js' 6 | import { DEBUG_CALL_RET_ERROR } from './constants.js' 7 | 8 | function updateRpcDescriptor ( 9 | target : Function, 10 | propertyKey : string, 11 | descriptor : PropertyDescriptor, 12 | ): PropertyDescriptor { 13 | log.verbose('Sidecar', 14 | 'updateRpcDescriptor(%s, %s, descriptor)', 15 | target.constructor.name, 16 | propertyKey, 17 | ) 18 | 19 | // console.log('value:', descriptor.value) 20 | const ret = descriptor.value() 21 | 22 | if (!(ret instanceof Promise)) { 23 | const e = new Error(`The "${target.constructor.name}.${propertyKey}" method return a non-promise: it must return the Ret() to make Sidecar @Call happy.`) 24 | console.error(e.stack) 25 | throw e 26 | } else { 27 | ret.then((result: any) => { 28 | /** 29 | * FIXME: Huan(202006) 30 | * check Ret value and deal the error more gentle 31 | */ 32 | if (result !== RET_SYMBOL) { 33 | const e = new Error(`The "${target.constructor.name}.${propertyKey}" method must return the Ret() to make Sidecar @Call happy.`) 34 | console.error(e.stack) 35 | throw e 36 | } 37 | return result 38 | }).catch((e: Error) => { 39 | (target as any)[DEBUG_CALL_RET_ERROR] = e 40 | console.error(e) 41 | }) 42 | } 43 | 44 | async function proxyMethod ( 45 | this: SidecarBody, 46 | ...args: any[] 47 | ) { 48 | // // https://github.com/huan/clone-class/blob/master/src/instance-to-class.ts 49 | // const klass = (this.constructor.name as any as typeof SidecarBody) 50 | 51 | // console.log('target:', target) 52 | // console.log('target.name:', target.name) 53 | // console.log('target.constructor.name:', target.constructor.name) 54 | 55 | log.verbose( 56 | `${target.constructor.name}`, 57 | `${propertyKey}(%s)`, 58 | args.join(', '), 59 | ) 60 | 61 | if (!this.script) { 62 | log.warn(`Sidecar<${target.constructor.name}>`, 63 | '%s(%s) updateRpcDescriptor() > proxyMethod() > "this.script" is undefined.', 64 | propertyKey, 65 | args.join(', '), 66 | ) 67 | this.emit('error', new Error('proxyMethod() found this.script is undefined')) 68 | return 69 | } 70 | 71 | return this.script.exports[propertyKey]!(...args) 72 | } 73 | 74 | /** 75 | * Update the method 76 | */ 77 | descriptor.value = proxyMethod 78 | return descriptor 79 | } 80 | 81 | export { updateRpcDescriptor } 82 | -------------------------------------------------------------------------------- /examples/dynamic-library/factorial-sidecar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sidecar - https://github.com/huan/sidecar 3 | * 4 | * @copyright 2021 Huan LI (李卓桓) 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | import path from 'path' 19 | 20 | import type { 21 | SidecarTargetRawSpawn, 22 | } from '../../src/decorators/sidecar/target.js' 23 | import { 24 | Call, 25 | Ret, 26 | Sidecar, 27 | SidecarBody, 28 | RetType, 29 | ParamType, 30 | exportTarget, 31 | } from '../../src/mod.js' 32 | import { codeRoot } from '../../src/cjs.js' 33 | 34 | /** 35 | * Inspired by https://github.com/iddoeldor/frida-snippets#socket-activity 36 | */ 37 | const libFileConfig: { 38 | [k in typeof process.platform]?: string 39 | } = { 40 | darwin : 'libfactorial.dylib', 41 | linux : 'libfactorial-x64.so', 42 | win32 : 'libfactorial-x64.dll', 43 | } 44 | 45 | const spawnTargetConfig: { 46 | [k in typeof process.platform]?: SidecarTargetRawSpawn 47 | } = { 48 | darwin : ['/bin/sleep', ['10']], 49 | linux : ['/bin/sleep', ['10']], 50 | win32 : ['C:\\Windows\\notepad.exe'], 51 | } 52 | 53 | const libFile = libFileConfig[process.platform] 54 | const spawnTarget = spawnTargetConfig[process.platform] 55 | 56 | if (!libFile || !spawnTarget) { 57 | console.error(`process.platform: ${process.platform} is not supported yet.`) 58 | throw new Error('no libFile or spawnTarget found!') 59 | } 60 | 61 | console.log([ 62 | 'libFile:', 63 | libFile, 64 | '\n', 65 | 'spawnTarget:', 66 | spawnTarget, 67 | ].join('')) 68 | 69 | const libPath = path.join( 70 | codeRoot, 71 | 'examples', 72 | 'dynamic-library', 73 | libFile, 74 | ).replace(/\\/g, '\\\\') 75 | 76 | const initAgentScript = `Module.load('${libPath}')` 77 | 78 | @Sidecar( 79 | spawnTarget, 80 | initAgentScript, 81 | ) 82 | class FactorialSidecar extends SidecarBody { 83 | 84 | @Call(exportTarget('factorial', libFile)) 85 | @RetType('uint64') 86 | factorial ( 87 | @ParamType('int') n: number, 88 | ): Promise { return Ret(n) } 89 | 90 | } 91 | 92 | export { FactorialSidecar } 93 | -------------------------------------------------------------------------------- /src/decorators/param-type/param-type.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | ParamType, 6 | } from './param-type.js' 7 | import { 8 | getMetadataParamType, 9 | } from './metadata-param-type.js' 10 | import { 11 | PARAM_TYPE_SYMBOL, 12 | } from './constants.js' 13 | 14 | test('ParamType with metadata', async t => { 15 | const NATIVE_TYPE = 'pointer' 16 | const POINTER_TYPE_LIST = ['Pointer', 'Utf8String'] as const 17 | 18 | class Test { 19 | 20 | method ( 21 | n: number, 22 | @ParamType( 23 | NATIVE_TYPE, 24 | ...POINTER_TYPE_LIST, 25 | ) content: string, 26 | ) { 27 | void n 28 | void content 29 | } 30 | 31 | } 32 | 33 | const instance = new Test() 34 | const data = Reflect.getMetadata( 35 | PARAM_TYPE_SYMBOL, 36 | instance, 37 | 'method', 38 | ) 39 | 40 | /* eslint-disable no-sparse-arrays */ 41 | const EXPECTED_DATA = [, [ 42 | NATIVE_TYPE, 43 | ...POINTER_TYPE_LIST, 44 | ]] 45 | t.same(data, EXPECTED_DATA, 'should get the parameter type data') 46 | }) 47 | 48 | test('getParamType', async t => { 49 | const NATIVE_TYPE = 'pointer' 50 | const POINTER_TYPE_LIST = ['Pointer', 'Utf8String'] as const 51 | 52 | class Test { 53 | 54 | method ( 55 | n: number, 56 | @ParamType( 57 | NATIVE_TYPE, 58 | ...POINTER_TYPE_LIST, 59 | ) content: string, 60 | ) { 61 | void n 62 | void content 63 | } 64 | 65 | } 66 | 67 | const instance = new Test() 68 | const typeList = getMetadataParamType( 69 | instance, 70 | 'method', 71 | ) 72 | 73 | const EXPECTED_NAME_LIST = [, [ 74 | NATIVE_TYPE, 75 | ...POINTER_TYPE_LIST, 76 | ]] 77 | t.same(typeList, EXPECTED_NAME_LIST, 'should get decorated parameter type list') 78 | }) 79 | 80 | test('guard parameter native types', async t => { 81 | const NATIVE_TYPE = 'pointer' 82 | const POINTER_TYPE_LIST = ['Pointer', 'Utf8String'] as const 83 | 84 | const getFixture = () => { 85 | class Test { 86 | 87 | method ( 88 | n: number, 89 | @ParamType( 90 | NATIVE_TYPE, 91 | ...POINTER_TYPE_LIST, 92 | ) content: number, 93 | ) { 94 | void n 95 | void content 96 | } 97 | 98 | } 99 | 100 | return Test 101 | } 102 | 103 | // getFixture() 104 | t.throws(getFixture, 'should throw because the ParamType(pointer) is not match the design type `number`') 105 | }) 106 | -------------------------------------------------------------------------------- /src/type-guard.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import type { 5 | NativeType, 6 | PointerType, 7 | } from './frida.js' 8 | 9 | import { 10 | guardNativeType, 11 | guardPointerType, 12 | ReflectedDesignType, 13 | } from './type-guard.js' 14 | 15 | test('guardNativeType()', async t => { 16 | const DESIGN_NATIVE_PAIR_LIST:[ 17 | ReflectedDesignType, 18 | NativeType, 19 | boolean, // expected match result 20 | ][] = [ 21 | [String, 'pointer', true], 22 | [Boolean, 'bool', true], 23 | [Number, 'int', true], 24 | [undefined, 'void', true], 25 | [undefined, 'pointer', true], // Huan(202107): 'pointer' with `null` value 26 | 27 | // Huan(202107): Number might be a point to the number. 28 | // Need to check the PointerType to make sure it is a number. 29 | [Number, 'pointer', true], 30 | 31 | [String, 'char', false], 32 | [undefined, 'char', false], 33 | [Boolean, 'int', false], 34 | ] 35 | 36 | for (const [designType, nativeType, shouldMatch] of DESIGN_NATIVE_PAIR_LIST) { 37 | if (shouldMatch) { 38 | t.doesNotThrow(() => guardNativeType(nativeType)(designType), [ 39 | 'should not throw when match:', 40 | `"${designType?.name}" <> "${nativeType}"`, 41 | ].join(' ')) 42 | } else { 43 | t.throws(() => guardNativeType(nativeType)(designType), [ 44 | 'should throw when does not match:', 45 | `"${designType?.name}" <> "${nativeType}"`, 46 | ].join(' ')) 47 | } 48 | } 49 | }) 50 | 51 | test('guardPointerType()', async t => { 52 | const DESIGN_POINTER_PAIR_LIST:[ 53 | ReflectedDesignType, 54 | PointerType, 55 | boolean, // expected match result 56 | ][] = [ 57 | [String, 'Utf8String', true], 58 | [Boolean, 'Int', true], 59 | [Number, 'Long', true], 60 | /** 61 | * Huan(202107): it is no sense for `void` has a pointer type: 62 | * we just do not check it for convenience. (by setting match to `true`) 63 | */ 64 | [undefined, 'U8', true], 65 | 66 | [Number, 'Pointer', false], 67 | [String, 'U32', false], 68 | ] 69 | 70 | for (const [designType, pointerType, shouldMatch] of DESIGN_POINTER_PAIR_LIST) { 71 | if (shouldMatch) { 72 | t.doesNotThrow(() => guardPointerType([pointerType])(designType), [ 73 | 'should not throw when match:', 74 | `"${designType?.name}" <> "${pointerType}"`, 75 | ].join(' ')) 76 | } else { 77 | t.throws(() => guardPointerType([pointerType])(designType), [ 78 | 'should throw when does not match:', 79 | `"${designType?.name}" <> "${pointerType}"`, 80 | ].join(' ')) 81 | } 82 | } 83 | }) 84 | -------------------------------------------------------------------------------- /src/cli/source-handler.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm --experimental-vm-modules 2 | import { test } from 'tstest' 3 | 4 | import fs from 'fs' 5 | import path from 'path' 6 | 7 | import stringSimilarity from 'string-similarity' 8 | 9 | import { 10 | sourceHandler, 11 | } from './source-handler.js' 12 | import { codeRoot } from '../cjs.js' 13 | 14 | test('sourceHandler()', async t => { 15 | const CLASS_FILE = path.join( 16 | codeRoot, 17 | 'examples', 18 | 'chatbox-sidecar.ts', 19 | ) 20 | const FIXTURE_FILE = path.join( 21 | codeRoot, 22 | 'tests', 23 | 'fixtures', 24 | 'sidecar-dump.source.chatbox-sidecar.js.fixture', 25 | ) 26 | 27 | const normalize = (text: string) => text 28 | /** 29 | * Strip file path line for CI under Linux & Windows 30 | */ 31 | .replace(/^.*chatbox.*$/gm, '') 32 | .replace(/[^\S\r\n]+/g, ' ') 33 | .replace(/^ +$/gm, '') // remove spaces in empty line 34 | .replace(/\r/g, '') // Windows will add \r, which need to be removed for comparing 35 | .replace(/\n+$/s, '') // strip all the ending newlines 36 | 37 | const FIXTURE = await fs 38 | .readFileSync(FIXTURE_FILE) 39 | .toString() 40 | 41 | const source = await sourceHandler({ file: CLASS_FILE }) 42 | // console.info('source:', source) 43 | 44 | /** 45 | * Generate the testing fixture file, Huan(202107) 46 | * 47 | * When we have updated the examples/chatbox-sidecar.ts file, 48 | * we need to update the `tests/fixtures/sidecar-dump.source.chatbox-sidecar.js.fixture` 49 | * so that the unit testing can be match the updated frida agent source code. 50 | */ 51 | // fs.writeFileSync('t.js', source) 52 | 53 | /** 54 | * We remove all spaces in the file so that the comparision will ignore all spaces 55 | */ 56 | const normalizedSource = normalize(source) 57 | const normalizedFixture = normalize(FIXTURE) 58 | 59 | /** 60 | * String Similarity Comparision in JS with Examples 61 | * https://sumn2u.medium.com/string-similarity-comparision-in-js-with-examples-4bae35f13968 62 | */ 63 | const similarity = stringSimilarity.compareTwoStrings( 64 | normalizedFixture, 65 | normalizedSource, 66 | ) 67 | 68 | void normalizedSource 69 | void normalizedFixture 70 | // console.log('normalizedSource:', normalizedSource) 71 | // console.log('####################') 72 | // console.log('normalizedFixture:', normalizedFixture) 73 | // console.log('###:', normalizedSource.length) 74 | 75 | const THRESHOLD = 0.95 76 | const ok = similarity > THRESHOLD 77 | // console.log('similarity:', similarity) 78 | 79 | t.ok(ok, `should get the source from ts file with similarity(${Math.floor(similarity * 100)}%) > ${THRESHOLD * 100}%`) 80 | }) 81 | -------------------------------------------------------------------------------- /src/decorators/sidecar/sidecar.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { Ret } from '../../ret.js' 5 | import { SidecarBody } from '../../sidecar-body/sidecar-body.js' 6 | import { Call } from '../call/call.js' 7 | import { Hook } from '../hook/hook.js' 8 | import { ParamType } from '../param-type/param-type.js' 9 | import { RetType } from '../ret-type/ret-type.js' 10 | import { getMetadataSidecar } from './metadata-sidecar.js' 11 | 12 | import { Sidecar } from './sidecar.js' 13 | 14 | const getFixture = () => { 15 | @Sidecar('chatbox') 16 | class Test extends SidecarBody { 17 | 18 | @Call(0x42) 19 | @RetType('pointer', 'Utf8String') 20 | testMethod ( 21 | @ParamType('pointer', 'Utf8String') content: string, 22 | @ParamType('int') n: number, 23 | ): Promise { return Ret(content, n) } 24 | 25 | @Hook(0x17) 26 | hookMethod ( 27 | @ParamType('int') n: number, 28 | ) { return Ret(n) } 29 | 30 | // Huan(202106) TODO: support { label } 31 | // @Call({ label: 'label1' }) anotherCall () { return Ret() } 32 | 33 | } 34 | 35 | return Test 36 | } 37 | 38 | test('@Sidecar() smoke testing', async t => { 39 | 40 | @Sidecar('chatbox') class Test extends SidecarBody {} 41 | 42 | const test = new Test() 43 | 44 | t.equal(Test.name, 'Test', 'should have the original class name after @Sidecar decorated') 45 | t.ok(test, 'should instanciate decorated class successfully') 46 | }) 47 | 48 | test('@Sidecar() viewMetadata()', async t => { 49 | 50 | const Test = getFixture() 51 | 52 | const metadata = getMetadataSidecar(Test) 53 | const EXPECTED_DATA = { 54 | initAgentScript: undefined, 55 | interceptorList: [ 56 | { 57 | address: { 58 | name: 'hookMethod', 59 | paramTypeList: [ 60 | [ 61 | 'int', 62 | ], 63 | ], 64 | retType: undefined, 65 | target: { address: '0x17', moduleName: null, type: 'address' }, 66 | }, 67 | }, 68 | ], 69 | nativeFunctionList: [ 70 | { 71 | address: { 72 | name: 'testMethod', 73 | paramTypeList: [ 74 | [ 75 | 'pointer', 76 | 'Utf8String', 77 | ], 78 | [ 79 | 'int', 80 | ], 81 | ], 82 | retType: [ 83 | 'pointer', 84 | 'Utf8String', 85 | ], 86 | target: { address: '0x42', moduleName: null, type: 'address' }, 87 | }, 88 | }, 89 | ], 90 | sidecarTarget: { 91 | target: 'chatbox', 92 | type: 'process', 93 | }, 94 | } 95 | 96 | t.same(metadata, EXPECTED_DATA, 'should get view from metadata correct') 97 | }) 98 | -------------------------------------------------------------------------------- /tests/integration.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | /** 3 | * Sidecar - https://github.com/huan/sidecar 4 | * 5 | * @copyright 2021 Huan LI (李卓桓) 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | import { test } from 'tstest' 21 | 22 | import { 23 | Call, 24 | Hook, 25 | Ret, 26 | Sidecar, 27 | SidecarBody, 28 | RetType, 29 | ParamType, 30 | agentTarget, 31 | } from '../src/mod.js' 32 | 33 | function getFixture () { 34 | @Sidecar('messaging', 'console.log("Sidecar inited")') 35 | class MessagingSidecar extends SidecarBody { 36 | 37 | @Call(0x1234) 38 | @RetType('pointer', 'Utf8String') 39 | mo ( 40 | @ParamType('pointer', 'Utf8String') content: string, 41 | @ParamType('int') count: number, 42 | ): Promise { 43 | return Ret(content, count) 44 | } 45 | 46 | @Hook(agentTarget('label1')) 47 | mt ( 48 | @ParamType('pointer', 'Utf8String') message: string, 49 | ) { 50 | return Ret(message) 51 | } 52 | 53 | } 54 | 55 | return MessagingSidecar 56 | } 57 | 58 | test('smoke testing', async (t) => { 59 | const MessagingSidecar = getFixture() 60 | const sidecar = new MessagingSidecar() 61 | 62 | sidecar.on('hook', payload => { 63 | console.log('payload:', payload) 64 | }) 65 | 66 | const EXPECTED_RET_VALUE = 42 67 | 68 | sidecar.script = { 69 | exports: { 70 | mo: () => Promise.resolve(EXPECTED_RET_VALUE), 71 | }, 72 | } as any 73 | 74 | const ret = await sidecar.mo('hello', 2) 75 | t.equal(ret, EXPECTED_RET_VALUE, 'should get the proxyed method value from script') 76 | }) 77 | 78 | test('ChatboxSidecar testing', async (t) => { 79 | const MessagingSidecar = getFixture() 80 | const sidecar = new MessagingSidecar() 81 | 82 | sidecar.on('hook', payload => { 83 | console.log('payload:', payload) 84 | }) 85 | 86 | const EXPECTED_RET_VALUE = 42 87 | 88 | sidecar.script = { 89 | exports: { 90 | mo: () => Promise.resolve(EXPECTED_RET_VALUE), 91 | }, 92 | } as any 93 | 94 | const ret = await sidecar.mo('hello', 2) 95 | t.equal(ret, EXPECTED_RET_VALUE, 'should get the proxyed method value from script') 96 | }) 97 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | 4 | "editor.fontFamily": "'Fira Code iScript', Consolas, 'Courier New', monospace", 5 | "editor.fontLigatures": true, 6 | 7 | "editor.tokenColorCustomizations": { 8 | "textMateRules": [ 9 | { 10 | "scope": [ 11 | //following will be in italics (=Pacifico) 12 | "comment", 13 | // "entity.name.type.class", //class names 14 | "keyword", //import, export, return… 15 | "support.class.builtin.js", //String, Number, Boolean…, this, super 16 | "storage.modifier", //static keyword 17 | "storage.type.class.js", //class keyword 18 | "storage.type.function.js", // function keyword 19 | "storage.type.js", // Variable declarations 20 | "keyword.control.import.js", // Imports 21 | "keyword.control.from.js", // From-Keyword 22 | "entity.name.type.js", // new … Expression 23 | "keyword.control.flow.js", // await 24 | "keyword.control.conditional.js", // if 25 | "keyword.control.loop.js", // for 26 | "keyword.operator.new.js", // new 27 | ], 28 | "settings": { 29 | "fontStyle": "italic", 30 | }, 31 | }, 32 | { 33 | "scope": [ 34 | //following will be excluded from italics (My theme (Monokai dark) has some defaults I don't want to be in italics) 35 | "invalid", 36 | "keyword.operator", 37 | "constant.numeric.css", 38 | "keyword.other.unit.px.css", 39 | "constant.numeric.decimal.js", 40 | "constant.numeric.json", 41 | "entity.name.type.class.js" 42 | ], 43 | "settings": { 44 | "fontStyle": "", 45 | }, 46 | } 47 | ] 48 | }, 49 | 50 | "files.exclude": { 51 | "dist/": true, 52 | "doc/": true, 53 | "node_modules/": true, 54 | "package/": true, 55 | }, 56 | "alignment": { 57 | "operatorPadding": "right", 58 | "indentBase": "firstline", 59 | "surroundSpace": { 60 | "colon": [1, 1], // The first number specify how much space to add to the left, can be negative. The second number is how much space to the right, can be negative. 61 | "assignment": [1, 1], // The same as above. 62 | "arrow": [1, 1], // The same as above. 63 | "comment": 2, // Special how much space to add between the trailing comment and the code. 64 | // If this value is negative, it means don't align the trailing comment. 65 | } 66 | }, 67 | "editor.formatOnSave": false, 68 | "python.pythonPath": "python3", 69 | "eslint.validate": [ 70 | "javascript", 71 | "typescript", 72 | ], 73 | "files.associations": { 74 | "dlfcn.h": "c" 75 | }, 76 | } 77 | -------------------------------------------------------------------------------- /src/function-target.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Huan(202106): 3 | * - type: agent 4 | * A label for the frida target address 5 | * This is for the dynamic purpose, 6 | * i.e. we need to call/hook a PTR that generated by the init agent. 7 | * - type: objc: 8 | * ?? 9 | * - type: module 10 | * loadModule('libc', 'open') 11 | * 12 | * See: Jumping to Labels in Inline Assembly 13 | * https://docs.microsoft.com/en-us/cpp/assembler/inline/jumping-to-labels-in-inline-assembly 14 | * 15 | */ 16 | /** 17 | * Function Target 18 | * 19 | * number: memory address of the function 20 | * - 0x1234 21 | * 22 | * string: export name of the function 23 | * - lib: 'open' 24 | * - ObjC: '- connection:didReceiveData:' 25 | */ 26 | /** 27 | * Memory address 28 | */ 29 | export interface TargetPayloadAddress { 30 | address : string 31 | moduleName : null | string 32 | type : 'address' 33 | } 34 | 35 | /** 36 | * The variable name in the `initAgentScript` 37 | */ 38 | export interface TargetPayloadAgent { 39 | type : 'agent' 40 | funcName : string 41 | } 42 | 43 | /** 44 | * The module name and the module export 45 | */ 46 | export interface TargetPayloadExport { 47 | exportName : string 48 | moduleName : null | string 49 | type : 'export' 50 | } 51 | 52 | // TODO: 53 | // | 'java' // TODO: 54 | // | 'objc' // TODO: 55 | 56 | export type TargetPayloadRaw = number 57 | | string 58 | 59 | export type TargetPayloadObj = TargetPayloadAddress 60 | | TargetPayloadAgent 61 | | TargetPayloadExport 62 | 63 | export type FunctionTarget = TargetPayloadRaw | TargetPayloadObj 64 | export type FunctionTargetType = TargetPayloadObj['type'] 65 | 66 | const addressTarget = ( 67 | address : number, 68 | moduleName : null | string = null, 69 | ): TargetPayloadAddress => ({ 70 | address: `0x${Number(address).toString(16)}`, 71 | moduleName, 72 | type: 'address', 73 | }) 74 | 75 | const agentTarget = ( 76 | funcName: string, 77 | ): TargetPayloadAgent => ({ 78 | funcName, 79 | type : 'agent', 80 | }) 81 | 82 | const exportTarget = ( 83 | exportName: string, 84 | moduleName: null | string = null, 85 | ): TargetPayloadExport => ({ 86 | exportName, 87 | moduleName, 88 | type: 'export', 89 | }) 90 | 91 | /** 92 | * Convert the `string` and `number` type target to Obj 93 | */ 94 | function normalizeFunctionTarget ( 95 | target: FunctionTarget, 96 | ): TargetPayloadObj { 97 | if (typeof target === 'number') { 98 | return addressTarget(target) 99 | } else if (typeof target === 'string') { 100 | return agentTarget(target) 101 | } 102 | return target 103 | } 104 | 105 | export { 106 | addressTarget, 107 | agentTarget, 108 | exportTarget, 109 | 110 | normalizeFunctionTarget, 111 | } 112 | -------------------------------------------------------------------------------- /examples/archive/decorator/reflect-metadata.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Huan(202106) see: 3 | * http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-4 4 | * 5 | * TypeScript Decorators Examples 6 | * https://gist.github.com/remojansen/16c661a7afd68e22ac6e 7 | * 8 | * TypeScript Decorators: Parameter Decorators 9 | * https://blog.wotw.pro/typescript-decorators-parameter-decorators/ 10 | */ 11 | import 'reflect-metadata' 12 | 13 | function decorateProperty (target : any, key : string) { 14 | console.log('decorateProperty') 15 | const t = Reflect.getMetadata('design:type', target, key) 16 | console.log(`${key} type: ${t.name}`) 17 | console.log('t:', t) 18 | } 19 | 20 | function decorateMethod (target : any, key : string, _descriptor: PropertyDescriptor) { 21 | console.log('decorateMethod') 22 | const types = Reflect.getMetadata('design:paramtypes', target, key) 23 | const s = types.map((a: any) => a.name).join() 24 | console.log(`${key} param types: ${s}`) 25 | // const s2 = types.map((a: any) => a).join() 26 | // console.log(`${key} param types2: ${s2}`) 27 | 28 | const r = Reflect.getMetadata('design:returntype', target, key) 29 | console.log(`${key} return type: ${r.name}`) 30 | } 31 | 32 | function decorateParam ( 33 | target: Object, 34 | propertyKey: string, 35 | parameterIndex: number, 36 | ) { 37 | console.log('decorateParam') 38 | void target 39 | void propertyKey 40 | void parameterIndex 41 | } 42 | 43 | function decorateClass < 44 | T extends { 45 | new (...args: any[]): {}, 46 | } 47 | > ( 48 | constructor:T, 49 | ) { 50 | void constructor 51 | console.log('decorateClass') 52 | } 53 | 54 | @decorateClass 55 | class Demo { 56 | 57 | @decorateProperty // apply property decorator 58 | public attr1: Demo 59 | 60 | constructor () { 61 | this.attr1 = {} as any 62 | } 63 | 64 | @decorateMethod // apply parameter decorator 65 | doSomething ( 66 | @decorateParam param1 : string, 67 | param2 : number, 68 | // param3 : Foo, 69 | param4 : { test : string }, 70 | // param5 : InterfaceFoo, 71 | param6 : Function, 72 | param7 : (a : number) => void, 73 | ) : number { 74 | void param1 75 | void param2 76 | // void param3 77 | void param4 78 | // void param5 79 | void param6 80 | void param7 81 | return 1 82 | } 83 | 84 | } 85 | 86 | void Demo 87 | void decoratorFactory 88 | 89 | function decoratorFactory (this: any, ...args : any[]) { 90 | switch (args.length) { 91 | case 1: 92 | return decorateClass.call(this, args[0]) 93 | case 2: 94 | return decorateProperty.call(this, args[0], args[1]) 95 | case 3: 96 | if (typeof args[2] === 'number') { 97 | return decorateParam.call(this, args[0], args[1], args[2]) 98 | } 99 | return decorateMethod.call(this, args[0], args[1], args[2]) 100 | default: 101 | throw new Error() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/sidecar-body/operations.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { 5 | SidecarBody, 6 | } from './sidecar-body.js' 7 | import { 8 | init, 9 | attach, 10 | detach, 11 | } from './operations.js' 12 | import { Sidecar } from '../decorators/mod.js' 13 | 14 | import { 15 | INIT_SYMBOL, 16 | ATTACH_SYMBOL, 17 | DETACH_SYMBOL, 18 | } from './constants.js' 19 | 20 | const targetProgram = () => 21 | process.platform === 'linux' ? '/bin/ls' 22 | : process.platform === 'darwin' ? '/bin/ls' 23 | : process.platform === 'win32' ? 'c:\\Windows\\notepad.exe' 24 | : 'targteProgram(): Unknown process.platform:' + process.platform 25 | 26 | test('init()', async t => { 27 | 28 | @Sidecar([targetProgram()]) 29 | class SidecarTest extends SidecarBody {} 30 | 31 | const s = new SidecarTest() 32 | const future = new Promise(resolve => s.on(INIT_SYMBOL, resolve)) 33 | 34 | try { 35 | await init(s) 36 | 37 | await Promise.race([ 38 | future, 39 | new Promise((resolve, reject) => { 40 | void resolve 41 | setTimeout(reject, 100) 42 | }), 43 | ]) 44 | 45 | t.pass('init() successfully') 46 | } catch (e) { 47 | t.fail('Rejection:' + e && (e as Error).message) 48 | console.error(e) 49 | } 50 | }) 51 | 52 | test('attach()', async t => { 53 | 54 | @Sidecar([targetProgram()]) 55 | class SidecarTest extends SidecarBody {} 56 | 57 | const sidecar = new SidecarTest() 58 | 59 | sidecar.script = { 60 | unload: (..._: any[]) => { return {} as any }, 61 | } as any 62 | sidecar.session = { 63 | detach: (..._: any[]) => { return {} as any }, 64 | } as any 65 | 66 | const future = new Promise(resolve => sidecar.on(ATTACH_SYMBOL, resolve)) 67 | 68 | try { 69 | await attach(sidecar) 70 | 71 | await Promise.race([ 72 | future, 73 | new Promise((resolve, reject) => { 74 | void resolve 75 | setTimeout(reject, 100) 76 | }), 77 | ]) 78 | t.pass('attach() successfully') 79 | } catch (e) { 80 | t.fail('Rejection:' + e && (e as Error).message) 81 | } finally { 82 | try { 83 | await detach(sidecar) 84 | } catch (e) {} 85 | } 86 | }) 87 | 88 | test('detach()', async t => { 89 | 90 | @Sidecar([targetProgram()]) 91 | class SidecarTest extends SidecarBody {} 92 | 93 | const sidecar = new SidecarTest() 94 | sidecar.on('error', () => {}) 95 | 96 | const future = new Promise(resolve => sidecar.on(DETACH_SYMBOL, resolve)) 97 | 98 | try { 99 | await init(sidecar) 100 | await attach(sidecar) 101 | 102 | await detach(sidecar) 103 | 104 | await Promise.race([ 105 | future, 106 | new Promise((resolve, reject) => { 107 | void resolve 108 | setTimeout(reject, 100) 109 | }), 110 | ]) 111 | 112 | t.pass('detach() successfully') 113 | } catch (e) { 114 | t.fail('Rejection:' + e && (e as Error).message) 115 | } 116 | }) 117 | -------------------------------------------------------------------------------- /tests/fixtures/sidecar-metadata.fixture.ts: -------------------------------------------------------------------------------- 1 | import type { SidecarMetadata } from '../../src/decorators/mod.js' 2 | 3 | /** 4 | * Sidecar View Fixtures 5 | */ 6 | const SIDECAR_METADATA: SidecarMetadata = { 7 | interceptorList: [ 8 | { 9 | address: { 10 | name: 'hookMethod', 11 | paramTypeList: [ 12 | [ 13 | 'int', 14 | ], 15 | [ 16 | 'pointer', 17 | 'Utf8String', 18 | ], 19 | ], 20 | retType: undefined, 21 | target: { 22 | address : '0x17', 23 | moduleName : null, 24 | type : 'address', 25 | }, 26 | }, 27 | }, 28 | ], 29 | nativeFunctionList: [ 30 | { 31 | address: { 32 | name: 'anotherCall', 33 | paramTypeList: [ 34 | [ 35 | 'pointer', 36 | 'Int', 37 | ], 38 | [ 39 | 'pointer', 40 | 'Pointer', 41 | 'Utf8String', 42 | ], 43 | ], 44 | retType: [ 45 | 'pointer', 46 | 'Int', 47 | ], 48 | target: { 49 | address: '0x4d', 50 | moduleName: null, 51 | type: 'address', 52 | }, 53 | }, 54 | }, 55 | { 56 | address: { 57 | name: 'testMethod', 58 | paramTypeList: [ 59 | [ 60 | 'pointer', 61 | 'Utf8String', 62 | ], 63 | [ 64 | 'int', 65 | ], 66 | ], 67 | retType: [ 68 | 'pointer', 69 | 'Utf8String', 70 | ], 71 | target: { 72 | address: '0x42', 73 | moduleName: null, 74 | type: 'address', 75 | }, 76 | }, 77 | }, 78 | { 79 | export: { 80 | name: 'pointerMethod', 81 | paramTypeList: [ 82 | [ 83 | 'pointer', 84 | ], 85 | ], 86 | retType: [ 87 | 'pointer', 88 | ], 89 | target: { 90 | exportName: 'MessageBoxW', 91 | moduleName: 'user32.dll', 92 | type: 'export', 93 | }, 94 | }, 95 | }, 96 | { 97 | address: { 98 | name: 'voidMethod', 99 | paramTypeList: [], 100 | retType: ['void'], 101 | target: { 102 | address : '0x1234', 103 | moduleName : 'test', 104 | type : 'address', 105 | }, 106 | }, 107 | }, 108 | { // AgentType: should not be decorated by neither @ParamType nor @RetType 109 | agent: { 110 | name: 'agentMethod', 111 | paramTypeList: [], 112 | retType: undefined, 113 | target: { 114 | funcName : 'agentFunctionName', 115 | type : 'agent', 116 | }, 117 | }, 118 | }, 119 | ], 120 | sidecarTarget: { 121 | target: 'chatbox-linux', 122 | type: 'process', 123 | }, 124 | } 125 | 126 | function getSidecarMetadataFixture (): SidecarMetadata { 127 | // https://stackoverflow.com/a/12690181/1123955 128 | return JSON.parse(JSON.stringify(SIDECAR_METADATA)) 129 | } 130 | 131 | export { getSidecarMetadataFixture } 132 | -------------------------------------------------------------------------------- /src/decorators/sidecar/build-sidecar-metadata.spec.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S node --no-warnings --loader ts-node/esm 2 | import { test } from 'tstest' 3 | 4 | import { Ret } from '../../ret.js' 5 | import { Call } from '../call/call.js' 6 | import { Hook } from '../hook/hook.js' 7 | import { ParamType } from '../param-type/param-type.js' 8 | import { RetType } from '../ret-type/ret-type.js' 9 | 10 | import { 11 | buildSidecarMetadata, 12 | } from './build-sidecar-metadata.js' 13 | 14 | import { Sidecar } from './sidecar.js' 15 | import { SidecarBody } from '../../sidecar-body/mod.js' 16 | import { 17 | agentTarget, 18 | exportTarget, 19 | } from '../../function-target.js' 20 | 21 | const getFixture = () => { 22 | @Sidecar('chatbox') 23 | class Test extends SidecarBody { 24 | 25 | @Call(0x42) 26 | @RetType('pointer', 'Utf8String') 27 | testMethod ( 28 | @ParamType('pointer', 'Utf8String') content: string, 29 | @ParamType('int') n: number, 30 | ): Promise { return Ret(content, n) } 31 | 32 | @Hook(agentTarget('agentVar')) 33 | hookMethod ( 34 | @ParamType('int') n: number, 35 | ) { return Ret(n) } 36 | 37 | @Call(exportTarget('exportNameTest', 'moduleNameTest')) 38 | @RetType('pointer', 'Int') 39 | anotherCall ( 40 | @ParamType('pointer', 'Int') i: number, 41 | ): Promise { return Ret(i) } 42 | 43 | } 44 | 45 | return Test 46 | } 47 | 48 | test('@Sidecar() buildSidecarMetadata()', async t => { 49 | 50 | const Test = getFixture() 51 | 52 | const meta = buildSidecarMetadata(Test, { 53 | sidecarTarget: 'chatbox', 54 | }) 55 | const EXPECTED_DATA = { 56 | initAgentScript: undefined, 57 | interceptorList: [ 58 | { 59 | agent: { 60 | name: 'hookMethod', 61 | paramTypeList: [ 62 | [ 63 | 'int', 64 | ], 65 | ], 66 | retType: undefined, 67 | target: { funcName: 'agentVar', type: 'agent' }, 68 | }, 69 | }, 70 | ], 71 | nativeFunctionList: [ 72 | { 73 | address: { 74 | name: 'testMethod', 75 | paramTypeList: [ 76 | [ 77 | 'pointer', 78 | 'Utf8String', 79 | ], 80 | [ 81 | 'int', 82 | ], 83 | ], 84 | retType: [ 85 | 'pointer', 86 | 'Utf8String', 87 | ], 88 | target: { address: '0x42', moduleName: null, type: 'address' }, 89 | }, 90 | }, 91 | { 92 | export: { 93 | name: 'anotherCall', 94 | paramTypeList: [ 95 | [ 96 | 'pointer', 97 | 'Int', 98 | ], 99 | ], 100 | retType: [ 101 | 'pointer', 102 | 'Int', 103 | ], 104 | target: { exportName: 'exportNameTest', moduleName: 'moduleNameTest', type: 'export' }, 105 | }, 106 | }, 107 | ], 108 | sidecarTarget: { 109 | target: 'chatbox', 110 | type: 'process', 111 | }, 112 | } 113 | 114 | t.same(meta, EXPECTED_DATA, 'should get metadata correct') 115 | }) 116 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: NPM 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | strategy: 9 | matrix: 10 | os: 11 | - ubuntu-latest 12 | # - macos-latest 13 | - windows-latest 14 | node-version: 15 | - 16 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node }} 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: 'npm' 24 | cache-dependency-path: package.json 25 | 26 | - name: Install Dependencies 27 | run: npm install 28 | 29 | - name: Test 30 | run: npm test 31 | 32 | pack: 33 | name: Pack 34 | needs: build 35 | strategy: 36 | matrix: 37 | os: 38 | # - ubuntu-latest 39 | # - macos-latest 40 | - windows-latest 41 | node: 42 | - 16 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions/setup-node@v2 47 | with: 48 | node-version: 16 49 | cache: 'npm' 50 | cache-dependency-path: package.json 51 | 52 | - name: Install Dependencies 53 | run: npm install 54 | 55 | - name: Generate Version 56 | run: bash -x ./scripts/generate-version.sh 57 | 58 | - name: Pack Testing 59 | run: bash -x ./scripts/npm-pack-testing.sh 60 | 61 | publish: 62 | if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/v')) 63 | name: Publish to NPM 64 | needs: [build, pack] 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v2 68 | - uses: actions/setup-node@v2 69 | with: 70 | node-version: 16 71 | registry-url: https://registry.npmjs.org/ 72 | cache: 'npm' 73 | cache-dependency-path: package.json 74 | 75 | - name: Install Dependencies 76 | run: npm install 77 | 78 | - name: Generate Version 79 | run: ./scripts/generate-version.sh 80 | 81 | - name: Set Publish Config 82 | run: ./scripts/package-publish-config-tag.sh 83 | 84 | - name: Build Dist 85 | run: npm run dist 86 | 87 | - name: Check Branch 88 | id: check-branch 89 | run: | 90 | if [[ ${{ github.ref }} =~ ^refs/heads/(main|v[0-9]+\.[0-9]+.*)$ ]]; then 91 | echo ::set-output name=match::true 92 | fi # See: https://stackoverflow.com/a/58869470/1123955 93 | - name: Is A Publish Branch 94 | if: steps.check-branch.outputs.match == 'true' 95 | run: | 96 | NAME=$(npx pkg-jq -r .name) 97 | VERSION=$(npx pkg-jq -r .version) 98 | if npx version-exists "$NAME" "$VERSION" 99 | then echo "$NAME@$VERSION exists on NPM, skipped." 100 | else npm publish 101 | fi 102 | env: 103 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 104 | - name: Is Not A Publish Branch 105 | if: steps.check-branch.outputs.match != 'true' 106 | run: echo 'Not A Publish Branch' 107 | --------------------------------------------------------------------------------