├── 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 |
--------------------------------------------------------------------------------