Skip to content

Repository files navigation

WasmPatch ๐Ÿงฑ

Repository: https://github.com/everettjf/wasmpatch

GitHub StarsGitHub ForksLicenseIssues

WebAssembly-driven hot patching for iOS/macOS Objective-C apps

English

Hot-fix iOS/macOS apps using WebAssembly payloads. Compile C code to WASM and replace Objective-C methods at runtime.


๐ŸŽฏ What is WasmPatch?

WasmPatch bridges Objective-C and WebAssembly. It compiles C code into WebAssembly modules and lets those modules call any Objective-C class or method dynamically โ€” so you can hot-fix bugs, add features, and replace methods at runtime without shipping a new binary.

Capabilities at a glance

Patch & bridge

  • ๐Ÿ”„ Replace class & instance methods at runtime; call any Obj-C method from wasm
  • ๐Ÿ“ Pass/return structs by value โ€” CGPoint/CGSize/CGRect/NSRangeand arbitrary structs via alloc_struct + field accessors
  • ๐Ÿงฉ Blocks both ways โ€” invoke completion handlers you're handed (invoke_block) and create blocks to pass into Obj-C (create_block)
  • ๐Ÿ”ข Value-type bridging for strings, numbers, BOOL, objects, selectors, C strings

Swift support

  • ๐Ÿฆ… Hook @objc dynamic Swift methods; module-qualified class names
  • ๐Ÿ”Ž Tool/scan-hookable.sh lists a binary's hookable surface; clean Swift API via NS_SWIFT_NAME โ€” see SWIFT.md

Authoring & tooling

  • โœ๏ธ WAP_REPLACE_* macros (a misspelled target fails to compile), scope cleanup pools, call_*_3/4
  • ๐Ÿ› ๏ธ Tool/wasmpatch CLI โ€” doctor, build (auto-injects <wasmpatch.h>, emits sha256/meta), keygen, sign
  • ๐Ÿ“ฆ Integrates via Swift Package Manager and CocoaPods

Delivery & safety

  • ๐ŸŒ WAPPatchManager โ€” fetch, verify, cache, and apply remote patches
  • ๐Ÿ” EC P-256 signature verification (authenticity) on top of SHA-256 (integrity)
  • ๐Ÿฉบ Host log handler, strict-hook load policy, and structured load/runtime errors

WasmPatch Architecture

Architecture at a glance

flowchart LR
subgraph Author["โœ๏ธ Author side"]
C["patch.c"]
CLI["Tool/wasmpatch build"]
WASM["patch.wasm<br/>+ .sha256 + signature"]
C --> CLI --> WASM
end
subgraph App["๐Ÿ“ฑ Your iOS / macOS app"]
LOADER["WAPPatchLoader /<br/>WAPPatchManager"]
RT["wasm3 interpreter"]
BRIDGE["Host export bridge (C ABI)"]
OBJC["Objective-C / Swift runtime"]
LOADER --> RT --> BRIDGE --> OBJC
end
WASM -->|"verify ยท cache ยท deliver"| LOADER
OBJC -. "replaced IMP via libffi" .-> RT
style WASM fill:#bbf,color:#000
style OBJC fill:#bfb,color:#000
Loading

A patch is just a C file compiled to a tiny WebAssembly module. The app verifies and loads it into a sandboxed wasm3 interpreter, whose only powers are the host functions WasmPatch exports โ€” that bridge is what reaches into the live Objective-C / Swift runtime.


โœจ Features

FeatureDescription
๐Ÿงฑ WASM CompilationCompile C code to WebAssembly with clang/LLVM
๐Ÿ”— Objective-C BridgeCall any Obj-C class or method from WebAssembly
๐Ÿ”„ Method ReplacementHot-fix by replacing Obj-C methods at runtime
๐ŸŽ Cross-PlatformWorks on both iOS and macOS
๐Ÿ“ Struct BridgingPass/return geometry structs and arbitrary by-value structs โ€” including unions & bitfields (alloc_struct, field + bit accessors)
โœ๏ธ Author ErgonomicsWAP_REPLACE_* macros (typos fail to compile), scope cleanup pools, call_*_3/4
๐Ÿ› ๏ธ Runtime DiagnosticsHost log handler, strict-hook policy, structured load/runtime errors
๐Ÿ“ฆ SPM + CocoaPodsSwift Package Manager and CocoaPods integration
๐Ÿงฉ Block CallbacksInvoke received blocks (invoke_block) and create blocks to pass into Obj-C (create_block)
๐Ÿฆ… Swift Support@objc dynamic hooking + hookable-surface scanner โ€” see SWIFT.md
๐ŸŒ Remote DeliveryWAPPatchManager โ€” fetch, SHA-256 verify, cache, apply
๐Ÿ” Patch SigningEC P-256 / ECDSA signature verification before load (Tool/wasmpatch sign)
๐Ÿงช Regression AssetsTest case bundle and fixture hosts for bridge validation

๐Ÿ—๏ธ How It Works

graph TD
A[C Code] --> B[clang/LLVM]
B --> C[WebAssembly Module]
C --> D[WasmPatch Runtime]
D --> E[Objective-C Runtime]
E --> F[Hot-fix Applied!]
style A fill:#f9f,color:#000
style C fill:#bbf,color:#000
style F fill:#bfb,color:#000
Loading
  1. Write your patch logic in C
  2. Compile to WebAssembly using clang/LLVM
  3. Load the wasm module in your app
  4. Call Objective-C classes/methods from WebAssembly
  5. Replace methods on the fly

๐Ÿš€ Quick Start

Prerequisites

# Install LLVM with WebAssembly target
brew install llvm
# Or use the provided script
sh Tool/install-llvm.sh

1. Clone and Setup

git clone https://github.com/everettjf/WasmPatch.git
cd WasmPatch

Integrate into your app via Swift Package Manager:

// Package.swift
.package(url:"https://github.com/everettjf/WasmPatch.git", branch:"master")

or CocoaPods (pod 'WasmPatch'). Swift apps: see SWIFT.md for what @objc dynamic methods can be hooked and how value/struct types bridge.

2. Compile a Patch

# Check the toolchain is ready (clang wasm target, wasm-ld, SDK header)
Tool/wasmpatch doctor
# Compile a patch โ€” the author SDK header <wasmpatch.h> is on the include path# automatically, and a .sha256 + .meta.json are emitted next to the .wasm.
Tool/wasmpatch build your_patch.c
Tool/wasmpatch build your_patch.c build/your_patch.wasm
# (the older entry point still works)
sh Tool/build-patch.sh your_patch.c

Patch sources just #include <wasmpatch.h> and use the ergonomic macros:

#include<wasmpatch.h>WAPObjectmy_token(WAPObjectself, constchar*cmd) {
returnnew_objc_nsstring("patched");
}
intentry() {
// Registered name is derived from the real symbol โ€” a typo won't compile.WAP_REPLACE_CLASS(MyClass, "token", my_token);
WAP_POOL_BEGIN(pool); // scope-based cleanupWAPObjects=WAP_KEEP(pool, new_objc_nsstring("hi"));
call_class_method_1("Logger", "log:", s);
WAP_POOL_END(pool); // frees everything keptreturn0;
}

3. Load in Your App

#import<WasmPatch/WAPPatchLoader.h>NSError *error = nil;
WAPPatchLoaderOptions *options = [WAPPatchLoader recommendedOptions];
options.expectedSHA256Hex = @"<optional sha256>";
BOOL success = [WAPPatchLoader loadPatchNamed:@"your_patch"inBundle:NSBundle.mainBundle
options:options
error:&error];
if (!success) {
NSLog(@"load failed: %@", error.localizedDescription);
NSLog(@"runtime detail: %@", error.userInfo[WAPPatchLoaderRuntimeMessageKey]);
}

Low-level C API is still available when you need it:

#import<WasmPatch/WasmPatch.h>BOOL success = wap_load_file("your_patch.wasm");
if (success) {
NSLog(@"loaded");
} else {
NSLog(@"load failed: %s", wap_last_error());
}

๐Ÿ“ Project Structure

WasmPatch/
โ”œโ”€โ”€ WasmPatch/ # Core framework
โ”‚ โ”œโ”€โ”€ WasmPatch.h # Public C API
โ”‚ โ”œโ”€โ”€ core/runtime/ # WASM runtime and exports
โ”‚ โ””โ”€โ”€ core/method/ # Obj-C method bridge and hooks
โ”œโ”€โ”€ Tool/ # Build tools
โ”‚ โ”œโ”€โ”€ c2wasm.sh # C to WASM compiler
โ”‚ โ””โ”€โ”€ install-llvm.sh # LLVM installer
โ”œโ”€โ”€ TestCase/ # Test cases
โ”‚ โ”œโ”€โ”€ compile-testcase.sh # Test compiler
โ”‚ โ””โ”€โ”€ WasmPatch-TestCase/ # Sample host classes and wasm fixtures
โ”œโ”€โ”€ Image/ # Documentation images
โ”œโ”€โ”€ Demo/ # Demo projects
โ”‚ โ”œโ”€โ”€ iOS/ # iOS demo (Objective-C)
โ”‚ โ”œโ”€โ”€ macOS/ # macOS demo (Objective-C)
โ”‚ โ””โ”€โ”€ WasmPatch-SwiftUI/ # macOS SwiftUI app โ€” live hot-patch of an @objc dynamic method
โ””โ”€โ”€ README.md

๐Ÿ’ป Examples

Public Runtime API

boolwap_load_file(constchar * path);
boolwap_load_data(constvoid * bytes, unsignedint size);
voidwap_reset_runtime(void);
boolwap_runtime_is_loaded(void);
constchar * wap_last_error(void);

High-Level Objective-C API

NSError *error = nil;
WAPPatchLoaderOptions *options = [WAPPatchLoader recommendedOptions];
options.allowReload = YES;
options.resetBeforeLoad = YES;
[WAPPatchLoader loadPatchAtPath:path options:options error:&error];
[WAPPatchLoader loadPatchNamed:@"objc"inBundle:bundle options:options error:&error];
[WAPPatchLoader loadPatchData:data options:options error:&error];
[WAPPatchLoader reset];

Error handling:

if (error.code == WAPPatchLoaderErrorCodeSHA256Mismatch) {
NSLog(@"patch tampered: %@", error.userInfo[WAPPatchLoaderRuntimeMessageKey]);
}

Call Objective-C from WASM

// advanced_patch.c#include<wasmpatch.h>intentry() {
WAPObjectmessage=new_objc_nsstring("WasmPatch detected request");
call_class_method_1("NetworkManager", "logRequest:", message);
dealloc_object(message);
replace_instance_method("NetworkManager", "sendRequest:", "wasm_custom_send_request");
return0;
}

๐Ÿ› ๏ธ Development

Requirements

RequirementVersionDescription
macOS10.14+Development environment
Xcode11+iOS/macOS SDK
LLVM/Clang14+C to WASM compilation
wabtlatestwasm2wat tooling

Build

# Build the frameworkcd WasmPatch/WasmPatch.xcodeproj
xcodebuild -project WasmPatch.xcodeproj \
-scheme WasmPatch \
-configuration Release \
-sdk iphoneos build
# Build for macOS
xcodebuild -project WasmPatch.xcodeproj \
-scheme WasmPatch \
-configuration Release \
-sdk macosx build

Test

# Compile testcase wasm fixturescd TestCase
sh compile-testcase.sh
# Compile your own patch with defaults
sh Tool/build-patch.sh path/to/patch.c
# Verify wasm modules
wasm2wat your_patch.wasm -o your_patch.wat

Production validation:

sh Tool/validate-production.sh

macOS host validation only:

sh Tool/run-macos-validation.sh

๐Ÿ“ฑ Platform Support

PlatformSupportMin Version
iOSโœ… Full10.0
macOSโœ… Full10.14
Simulatorโœ… FullSame as above

๐Ÿงช Test Cases

Test CaseDescription
objc.cObjective-C bridge coverage and method replacement fixture
CallMeHost methods for bridge argument/result validation
ReplaceMeHost methods used for runtime replacement verification

Run all tests:

sh TestCase/compile-testcase.sh

๐Ÿ“š Documentation

Full docs live in pages/ (a GitHub Pages site โ€” see pages/DEPLOY.md to publish it):

Reference: Architecture ยท Runtime API ยท Tooling ยท Roadmap

Remote Delivery

WAPPatchManager downloads a patch, verifies its SHA-256, caches it, and applies it:

sequenceDiagram
participant App
participant Mgr as WAPPatchManager
participant Srv as Patch server
App->>Mgr: fetchPatchFromURL:named:sha256:
Mgr->>Srv: GET patch.wasm
Srv-->>Mgr: bytes
Mgr->>Mgr: verify SHA-256 (integrity)
Mgr->>Mgr: cache on disk
Mgr-->>App: completion(cachedPath)
App->>Mgr: applyCachedPatchNamed:
Mgr->>Mgr: load + apply via libffi
Loading
#import<WasmPatch/WAPPatchManager.h>
[[WAPPatchManager sharedManager] fetchPatchFromURL:url
named:@"login_fix"sha256:expectedHash
completion:^(NSString *cachedPath, NSError *error) {
if (cachedPath) {
[[WAPPatchManager sharedManager] applyCachedPatchNamed:@"login_fix"options:nilerror:nil];
}
}];

Patch Signing

SHA-256 (expectedSHA256Hex) proves integrity; an EC P-256 signature proves authenticity โ€” a tampered patch can't be re-signed without the private key.

flowchart TD
KG["wasmpatch keygen<br/>โ†’ patch_key.pem (private)"] --> SIGN["wasmpatch sign"]
BUILD["wasmpatch build<br/>โ†’ patch.wasm"] --> SIGN
SIGN --> PUB["publicKeyECBase64<br/>(embedded in app)"]
SIGN --> SIG["signatureBase64<br/>(shipped with patch)"]
PUB --> GATE
SIG --> GATE
PATCH["delivered patch.wasm"] --> GATE{"verify SHA-256<br/>+ EC P-256"}
GATE -->|valid| OK["โœ… load and apply"]
GATE -->|invalid| NO["โ›” refuse:<br/>SignatureInvalid"]
style OK fill:#bfb,color:#000
style NO fill:#fbb,color:#000
Loading
# one-time: create a signing key (keep the .pem private)
Tool/wasmpatch keygen patch_key.pem
# sign a built patch; prints publicKeyECBase64 + signatureBase64
Tool/wasmpatch sign your_patch.wasm patch_key.pem
WAPPatchLoaderOptions *options = [WAPPatchLoader recommendedOptions];
options.publicKeyECBase64 = @"<embedded public key (uncompressed point), base64>";
options.signatureBase64 = @"<signature delivered alongside the patch>";
NSError *error = nil;
if (![WAPPatchLoader loadPatchAtPath:path options:options error:&error] &&
error.code == WAPPatchLoaderErrorCodeSignatureInvalid) {
NSLog(@"refused: patch is not authentically signed");
}

Current Maturity

  • Author ergonomics (macros, cleanup pools, CLI), struct bridging, completion- handler (block) invocation, host log handler, strict-hook load policy, SPM support, remote delivery (WAPPatchManager), and Swift @objc dynamic hooking are in place and exercised end-to-end (Tool/validate-*.sh).
  • Bidirectional block bridging, EC P-256 patch signing, and generic by-value struct bridging (including structs with unions and bitfields) are all in place. Remaining edges (bare top-level unions, signing-key distribution tooling) are noted in ROADMAP.md.

Release Checklist

sh Tool/build-patch.sh your_patch.c
sh Tool/validate-production.sh

If both pass, the current repo state is ready for internal release and integration validation.


๐Ÿค Contributing

Contributions are welcome! Areas to help:

  • ๐Ÿ› Bug fixes
  • โœจ New features
  • ๐Ÿ“ Documentation
  • ๐Ÿงช Test cases
  • ๐Ÿ’ก Performance improvements

๐Ÿ“œ License

WasmPatch is released under the MIT License.


๐Ÿ™ Acknowledgements

Inspired by:


๐Ÿ“ˆ Star History

Star History Chart


๐Ÿ“ž Support

GitHub IssuesWeChat

ๆœ‰้—ฎ้ข˜๏ผŸๅŽป Issues ๆ้—ฎ๏ผ


Made with โค๏ธ by Everett

Project Link:https://github.com/everettjf/WasmPatch

About

๐ŸงฑYet Another Patch Module for iOS/macOS via WebAssembly

Topics

Resources

Stars

103 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages