SkillAgentSearch skills...

Sidecar

Easily hook/call binary functions using ES6 class with TypeScript annotation (Powered by Frida)

Install / Use

/learn @huan/Sidecar
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Sidecar

NPM NPM Version npm (tag) Powered by Frida

Sidecar is a runtime hooking tool for intercepting function calls by TypeScript annotation with ease, powered by Frida.RE.

Frida Sidecar

Image source: 1920s Raleigh Box Sidecar Outfit & ShellterProject

What is a "Sidecar" Pattern?

Segregating the functionalities of an application into a separate process can be viewed as a Sidecar pattern. The Sidecar design pattern allows you to add a number of capabilities to your application without the need of additional configuration code for 3rd party components.

As a sidecar is attached to a motorcycle, similarly in software architecture a sidecar is attached to a parent application and extends/enhances its functionalities. A Sidecar is loosely coupled with the main application.

— SOURCE: Sidecar Design Pattern in your Microservices Ecosystem, Samir Behara, July 23, 2018

What is a "Hooking" Patern?

Hook: by intercepting function calls or messages or events passed between software components. — SOURCE: Hooking, Wikipedia

Features

  1. Easy to use by TypeScript decorators/annotations
    1. @Call(memoryAddress) for make a API for calling memory address from the binary
    2. @Hook(memoryAddress) for emit arguments when a memory address is being called
  2. Portable on Windows, macOS, GNU/Linux, iOS, Android, and QNX, as well as X86, Arm, Thumb, Arm64, AArch64, and Mips.
  3. Powered by Frida.RE and can be easily extended by any agent script.

Requirements

  1. Mac: disable System Integrity Protection

Introduction

When you are running an application on the Linux, Mac, Windows, iPhone, or Android, you might want to make it programmatic, so that your can control it automatically.

The SDK and API are designed for achieving this, if there are any. However, most of the application have very limited functionalities for providing a SDK or API to the developers, or they have neither SDK nor API at all, what we have is only the binary executable application.

How can we make a binary executable application to be able to called from our program? If we can call the function in the application process directly, then we will be able to make the application as our SDK/API, then we can make API call to control the application, or hook function inside the application to get to know what happened.

I have the above question and I want to find an universal way to solve it: Frida is for rescue. Frida is a dynamic instrumentation toolkit for developers and reverse-engineers, which can help us easily call the internal function from a process, or hook any internal function inside the process. And it has a nice Node.js binding and TypeScript support, which is nice to me because I love TypeScript much.

That's why I start build this project: Sidecar. Sidecar is a runtime hooking tool for intercepting function calls by decorate a TypeScript class with annotation.

Here's an example code example for demostration that how easy it can help you to hook a exiting application.

Talk is cheap, show me the code

@Sidecar('chatbox')
class ChatboxSidecar extends SidecarBody {

  @Call(0x11c9)
  @RetType('void')
  mo (
    @ParamType('pointer', 'Utf8String') content: string,
  ): Promise<string> { return Ret(content) }

  @Hook(0x11f4)
  mt (
    @ParamType('pointer', 'Utf8String') content: string,
  ) { return Ret(content) }

}

async function main () {
  const sidecar = new ChatboxSidecar()
  await attach(sidecar)

  sidecar.on('hook', ({ method, args }) => {
    console.log('method:', method)
    console.log('args:', args)
    sidecar.mo('Hello from Sidecar'),
  })

  process.on('SIGINT',  () => detach(sidecar))
  process.on('SIGTERM', () => detach(sidecar))
}

main().catch(console.error)

Learn more from the sidecar example: https://github.com/huan/sidecar/blob/main/examples

To-do list

  • [x] Intercepter.attach() a NativeCallback() ~~ptr not work in Sidecar generated script. (it is possible by direct using the frida cli)~~ worked! (#9)
  • [x] Add typing.d.ts for Sidecar Agent pre-defined variables & functions
  • [ ] Add @Name() support for specify parameter names in @Hook()-ed method args.
  • [ ] Calculate Memory.alloc() in sidecar agent scripts automatically.

Explanation

1. Sidecar Steps

When we are running a Sidecar Class, the following steps will happend:

  1. From the sidecar class file, decorators save all configs to the class metadata.
    1. @Sidecar(): <src/decorators/sidecar/sidecar.ts>, <src/decorators/sidecar/build-sidecar-metadata.ts>
    2. @Call(): <src/decorators/call/call.ts>
    3. @ParamType(): <src/decorators/param-type/param-type.ts>
    4. @RetType(): <src/decorators/ret-type/ret-type.ts>
    5. @Hook(), @Name, etc.
  2. SidecarBody(<src/sidecar-body/sidecar-body.ts>) base class will generate agentSource:
    1. getMetadataSidecar()(<src/decorators/sidecar/metadata-sidecar.ts>) for get the sidecar metadata from the class
    2. buildAgentSource()(<src/agent/build-agent-source.ts>) for generate the agent source code for the whole sidecar system.
  3. Call the attach() method to attach the sidecar to the target
  4. Call the detach() method to detach the sidecar to the target

References

1. @Sidecar(sidecarTarget, initAgentScript)

  1. sidecarTarget : SidecarTarget,
  2. initAgentScript? : string,

The class decorator.

sidecarTarget is the executable binary name, and the initAgentScript is a Frida agent script that help you to do whatever you want to do with Frida system.

Example:

import { Sidecar } from 'sidecar'
@Sidecar('chatbox')
class ChatboxSidecar {}

It is possible to load a init agent script, for example:

const initAgentScript = 'console.log("this will be runned when the sidecar class initiating")'
@Sidecar(
  'chatbox', 
  initAgentScript,
)

sidecarTarget supports Spawn mode, by specifing the sidecarTarget as a array:

@Sidecar([
  '/bin/sleep', // command
  [10],         // args
])

To learn more about the power of initAgentScript, see also this great repo with lots of examples: Hand-crafted Frida examples

2. class SidecarBody

Base class for the Sidecar class. All Sidecar class need to extends from the SidecarBody, or the system will throw an error.

Example:

import { SidecarBody } from 'sidecar'
class ChatboxSidecar extends SidecarBody {}

3. @Call(functionTarget)

  1. functionTarget: FunctionTarget

The native call method decorator.

functionTarget is the address (in number type) of the function which we need to call in the executable binary.

Example:

import { Call } from 'sidecar'
class ChatboxSidecar {
  @Call(0x11c9) mo () {}
}

If the functionTarget is not the type of number, then it can be string or an FunctionTarget object. See FunctionTarget section to learn more about the advanced usage of FunctionTarget.

4. @Hook(functionTarget)

  1. functionTarget: FunctionTarget

The hook method decorator.

functionTarget is the address (in number type) of the function which we need to hook in the executable binary.

Example:

import { Hook } from 'sidecar'
class ChatboxSidecar {
  @Hook(0x11f4) mt () {}
}

If the functionTarget is not the type of number, then it can be string or an FunctionTarget object. See FunctionTarget section to learn more about the advanced usage of FunctionTarget.

5. @RetType(nativeType, ...pointerTypeList)

  1. nativeType : NativeType
  2. pointerTypeList : PointerType[]
import { RetType } from 'sidecar'
class ChatboxSidecar {
  @RetType('void') mo () {}

6. @ParamType(nativeType, ...pointerTypeList)

  1. nativeType : NativeType
  2. pointerTypeList : PointerType[]
import { ParamType } from 'sidecar'
class ChatboxSidecar {
  mo (
    @ParamType('pointer', 'Utf8String') content: string,
  ) {}

7. Name(parameterName)

TODO: to be implemented.

  1. parameterName: string

The parameter name.

This is especially useful for Hook methods. The hook event will be emit with the method name and the arguments array. If the Name(parameterName) has been set, then the event will have additional information for the parameter names.

import { Name } from 'sidecar'
class ChatboxSidecar {
  mo (
    @Name('content') content: string,
  ) {}

8. Ret(...args)

  1. args: any[]

Example:

import { Ret } from 'sidecar'
class ChatboxSidecar {
  mo () { return Ret() }

9. FunctionTarget

The FunctionTarget is where @Call or @Hook to be located. It can be created by the following factory helper functions:

  1. addressTarget(address: number, module?: string): memory address. i.e. 0x369adf. Can specify a second module to call address in a specified module
  2. agentTarget(funcName: string): the JavaScript function name in initAgentScript to be used
  3. exportTarget(exportName: string, exportModule?: string): export name of a function. Can s
View on GitHub
GitHub Stars50
CategoryDevelopment
Updated2mo ago
Forks7

Languages

JavaScript

Security Score

100/100

Audited on Jan 19, 2026

No findings