Got my hands on a beta app - Wukong. Opened it up — an invite code input box popped up.

Invite code?

I didn’t manage to get an invite code today…

Most people give up at this point.

My first reaction was: how is this thing implemented?


I didn’t expect this little investigation to burn through my entire Coding Plan Pro quota.


1. Initial Exploration

With a target to crack, the first step is figuring out its architecture.

file Wukong.app
# Mach-O ARM64 executable, 117MB

One run of strings filled the screen with Rust symbols — DingTalkReal::dingtalk_core::login, LoginModule, LoginPhase

This is no ordinary app; it’s a Tauri 2.x app: Rust backend + web frontend (WKWebView).

The invite code validation could live in one of two places:

  1. Frontend — a pure JS check
  2. Backend — a Rust state machine

Tried tampering with localStorage — no use. The frontend assets aren’t even in Resources; they’re loaded dynamically over IPC.

The answer is in the backend.


2. Locating the Target

Listed the symbol table with nm and locked onto one function:

on_invite_code_validated

This is the “invite code validation” Tauri command. Its address is 0x1020376b4.

Finding the function alone isn’t enough — you also have to understand its logic.

I wasn’t familiar with ARM64 assembly before either — but that’s fine, you only need to find the few key lines:

1020376d0: ldr  x8, [x0, #0x180]    ; load the internal object pointer
1020376d4: mov  w9, #0x1              ; w9 = 1 (true)
1020376d8: stlurb w9, [x8, #0x10]   ; atomic store to the flag

This is the code that sets the “invite code validated” flag.


3. The Patching Approach

With the flag-setting code found, the approach becomes clear:

Simplify this function so it sets the flag and returns directly, skipping all validation logic.

Patch bytes:

08 c0 40 f9 29 00 80 52 09 01 01 19 c0 03 5f d6

Corresponding assembly:

ldr x8, [x0, #0x180]
mov w9, #1
stlurb w9, [x8, #0x10]
ret

But this is just one function. It later turned out several other functions also needed patching for a complete bypass — this is a multi-layer validation system:

FunctionRole
on_invite_code_validatedTauri command entry point
LoginManager::on_invite_code_validatedActually sets the flag
is_agentbay_environmentAgentBay environment detection
get_launch_modeLaunch mode

4. In Practice: Command Line All the Way

No Hopper, no GUI. Command line all the way.

#!/bin/bash
BINARY="/Applications/Wukong.app/Contents/MacOS/DingTalkReal"

# Backup
cp "$BINARY" "DingTalkReal.backup"

# Patch 1: the Tauri command returns success directly
printf '\x00\x00\x80\x52\xc0\x03\x5f\xd6' | \
dd of="$BINARY" bs=1 seek=$((0x12eaa04)) conv=notrunc

# Patch 2: simplify the validation logic
printf '\x08\xc0\x40\xf9\x29\x00\x80\x52\x09\x01\x01\x19\xc0\x03\x5f\xd6\x1f\x20\x03\xd5\x1f\x20\x03\xd5\x1f\x20\x03\xd5\x1f\x20\x03\xd5' | \
dd of="$BINARY" bs=1 seek=$((0x20376b4)) conv=notrunc

# Sign — mandatory on macOS
codesign --sign - --force "$BINARY"

The core idea in one sentence: use printf to write bytes, dd to seek to the offset, and codesign to re-sign.


5. Pitfall Log

Pitfall 1: Offset Calculation

Given the virtual address 0x1020376b4, how do you convert it to a file offset?

The __TEXT segment of the Mach-O starts at 0x100000000, which maps to file offset 0. So:

file offset = virtual address - 0x100000000

But this only applies to the __TEXT segment. Different segments have different base addresses; check the segment table with otool -l.

Pitfall 2: All Addresses Change After an Upgrade

After the app updates, all function addresses change:

FunctionOld OffsetNew Offset
on_invite_code_validated0x12eaa040x2b16c
LoginManager0x20376b40xfca10

Solution: relocate the functions with nm and recalculate the offsets.

Pitfall 3: macOS Code Signing

After patching, the app wouldn’t open — the code signature was broken.

codesign --remove-signature "$BINARY"
codesign --sign - --force "$BINARY"

6. Results and Reflections

The invite code validation was ultimately bypassed successfully.

But the interesting part is: bypassing the frontend is only the first step. The app does reach the main interface, yet the backend services (fetching the model list) remain unavailable — which means the real validation logic lives on the server side, and the invite code is used for account binding.

This experience taught me a few things:

First, the security boundary of Tauri apps — the core logic is in the backend; don’t blindly assume “bypassing the frontend equals cracking the app”.

Second, ARM64 reverse engineering isn’t as hard as it seems — read the manual more, get your hands dirty more.

Third, the power of command-line tools — nm + xxd + strings combined aren’t much worse than IDA.


Technical Appendix

Key tools: nm, xxd, strings, otool, codesign

Common ARM64 instructions:

InstructionMachine Code
mov w0, #000 00 80 52
mov w0, #120 00 80 52
retc0 03 5f d6

Core finding: the invite code validation is implemented in a Rust state machine; the frontend is merely a presentation layer.

Final result: I can get in, but can’t do anything~~