WeChat RQTX IRVMP Recovery: Architecture, Instructions, and Runtime
WeChat RQTX IRVMP Recovery: Architecture, Instructions, and Runtime
This article records the reverse-engineering and recovery process for the RQTX IRVMP path. The important part is not a single hook point. The useful result is a chain of engineering objects that can be inspected and tested: the entry point, VM initialization, the rqtx.dat layout, instruction encoding, runner semantics, CFG cleanup, and an offline runtime.
Each layer is explained in the same order: what it is, why it matters, and how it can be proven. The article should read as an evidence chain rather than a work log.
Key Conclusions
- The WeChat 8.7.0 RQTX path can be split into seven stages: ObjC anchor, slot/body proof, VM init and layout proof, payload dump, runner localization, CFG skeleton stripping, and offline runtime verification.
rqtx.datis not just an opcode list. It behaves like a packaged VM image that contains an opcode map, payload image, relocation/export slots, and internal VM blocks.- RQTX IRVMP instruction decoding depends on
raw_word, the current IP, and the opcode map. The same high six bits can map to different semantics at different IPs. - Version migration should preserve the evidence chain and layout relationships instead of directly reusing old runtime addresses.
- The offline runtime is useful only when it reconstructs the VM image, context, handler semantics, and observed traffic behavior. A single hook return value is not enough.
The reading path has four parts. First, we straighten the call chain. Then we explain how the VM image and context are initialized. After that, we recover instruction decoding and the CFG-obfuscated runner. Finally, we reduce the evidence into an offline runtime and a version-migration method.
Keywords And Terms
| Term | Meaning |
|---|---|
| The client environment where the RQTX path is observed. | |
| RQTX | A protected computation path in the request chain. |
| IRVMP | Instruction-level virtual machine protection used by the RQTX path. |
| rqtx.dat | A VM data file carrying the opcode map, payload image, and relocation slots. |
| CFG deobfuscation | Block coverage, dispatcher stripping, and semantic recovery for a flattened native runner. |
| IDA Pro / Frida | Static analysis and dynamic instrumentation tools used for proof and validation. |
Related articles are collected in the WeChat RQTX IRVMP Research Index.
RQTX is difficult because several protection layers are stacked together:
- the upper layer is an Objective-C selector that provides a stable business entry;
- the middle layer contains native stages that are flattened by CFG and are responsible for object construction, runtime handles, entry parsing, and bridging;
- the lower layer is an IRVMP runner that interprets
rqtx.dat; - the data layer includes an opcode map, payload image, relocation slots, and VM context;
- the dynamic-analysis layer must handle ASLR, thread noise, hot-path overhead, and watchdog or re-entry behavior.
For this kind of target, reading one pseudo-code fragment and making a conclusion is risky. The recovery method should make every conclusion falsifiable: the static address should match, the dynamic path should be hit, the VM state should explain the behavior, and the offline runtime should reproduce the observed result. Anything that cannot pass that bar stays marked as unknown.
Overall Path
The first object to recover is the live path. Without it, every later statement about rqtx.dat, VM context, or the runner is floating. With it, the high-level selector, body wrapper, bridge, and VM runner can be placed on the same graph.
The live path can be summarized as:
WCCalRqtDataMgr - calRqtData:len:cmd:uin: -> slot dispatcher / wrapper -> rqtx_md5_body_dispatch -> rqtx_calc_stage_dispatch -> rqtx_vm_bridge -> rqtx_vm_runner_flat -> rqtx.dat + VM context + opcode mapThe slot chain is not a single jump. After the calRqtData layer loads the slot, the analysis still needs to follow a thunk chain through several wrappers before reaching the body dispatch stage. Stopping at the first dispatcher makes the wrapper look like the algorithm, which is the wrong boundary.
The role table is more important than the raw address values:
| Role | Purpose |
|---|---|
| ObjC anchor | Stable top-level selector for the request-chain entry. |
| body wrapper | Accepts the normalized input and enters the RQTX computation path. |
| calc stage | Checks the runtime handle and parses entry-like fields. |
| VM bridge | Loads the blob/context and initializes VM registers. |
| VM runner | Executes the IRVMP instruction stream with heavy CFG flattening. |
IRVMP Design Model
The entry and slot chain answer “where does execution go?” The next question is “why does the code not look like a normal native algorithm after we arrive there?” The answer is the IRVMP layering.
The RQTX IRVMP can be understood as five layers.
The first layer is the packaged VM image. The core logic is not simply hard-coded inside a native function. It is expressed by a VM image carried by rqtx.dat, and the native runner interprets that image.
The second layer is an objectized runtime. rqtx.dat is not executed as a raw file. It passes through global initialization, provider or handle management, blob getters, and context getters before it becomes the runtime object used by the bridge.
The third layer is VM context. Each bridge call prepares a context buffer that contains a register file, IP, next IP, VM state slots, and memory-window pointers.
The fourth layer is IP-aware instruction decoding. Opcode extraction is not merely word >> 26. The decoder mixes the current IP and the opcode map. Register fields are also XOR-obfuscated.
The fifth layer is native runner obfuscation. The interpreter itself is flattened by CFG and contains a main dispatcher, sub-dispatchers, selector state, and shared tails.
These layers create a typical protected target: static analysis cannot see the whole algorithm, dynamic trace is noisy, and dumped data cannot run directly unless relocation and context are reconstructed.
rqtx.dat Layout
After the model is clear, the bottom layer is the VM image. rqtx.dat carries the VM data and the protected logic. If this layout cannot be explained, the later init, decode, and runtime recovery have no stable foundation.
The recovered model treats rqtx.dat as a packaged image:
| Region | Purpose |
|---|---|
| opcode map | Deobfuscates instruction opcode values. |
| payload image | Holds VM instruction words and embedded data. |
| relocation/export slots | Connect the packaged image to runtime pointers and bridge state. |
| internal VM blocks | Provide control-flow and handler-level data used by the runner. |
The important observation is that the native runner and the VM image are coupled. A dump of the file is useful only after the loader relationship is understood: which bytes are copied, which pointers are rewritten, and which slots are interpreted as runtime state.
VM Initialization
The initialization stage turns a data file into a runtime object. The useful proof is not just “this function touches the file.” The proof is a sequence:
- A global initialization path obtains or constructs the RQTX provider.
- A handle-like object is used by later calculation stages.
- The VM bridge asks for a blob and a context.
- The context gets filled with register and memory-window state.
- The runner receives the context and begins interpreting payload instructions.
In practice, the init path should be verified from both directions. Static analysis gives the object graph and field offsets. Dynamic instrumentation confirms that the same fields are read and written during a live call. When those two views agree, the layout becomes reliable enough for offline reconstruction.
VM Context
The VM context is the boundary between native code and the virtual machine. It is where VM registers, IP, state slots, memory windows, and temporary buffers become observable.
A useful context model separates fields by behavior:
VmContext registers[...] ip next_ip state_slots[...] memory_windows[...] bridge_input bridge_outputThe field names are not guessed from a single snapshot. They are assigned after repeated calls show stable read/write behavior. For example, a field that advances with every decoded instruction is an IP candidate. A field that is read by memory-like handlers and points into the staged input is a memory-window candidate. A field that receives the final short integer is an output slot candidate.
This is also where dynamic trace becomes useful. It does not need to record every native instruction. It needs to show which context fields change at block boundaries and which changes are correlated with input staging, loop execution, or final output.
Instruction Encoding
The RQTX VM instruction word is not decoded by a plain fixed-format table. The high bits, register fields, immediate fields, current IP, and opcode map all participate.
The recovery model uses four public-facing formats:
I-format
I-format instructions carry register operands and a short immediate. They are useful for identifying arithmetic, memory access, and small control operations.
R-format
R-format instructions carry multiple register fields. These are the best candidates for arithmetic and state-mixing handlers.
19-bit Immediate
The 19-bit immediate form is useful for medium-size constants or offsets. It appears when the VM needs more than the small immediate but does not need a full branch target.
26-bit Branch
The branch form carries a larger control-flow target. It is the main format used when rebuilding the VM-level CFG.
The key point is that instruction format is not enough. The opcode still needs deobfuscation. The same raw shape can mean different things once IP-aware mapping is applied.
Instruction Semantics
Instruction semantics should be recovered from handler behavior, not from names assigned too early.
A stable workflow is:
- Decode a candidate instruction word.
- Identify which handler block the native runner enters.
- Trace context reads and writes around that handler.
- Compare the behavior with a small set of semantic templates: move, add, xor, rotate, load, store, branch, compare, call-like transition.
- Add the handler to the offline runtime only after repeated samples agree.
This is slower than naming everything from pseudo-code, but it avoids a common failure: one misleading Hex-Rays expression can create a wrong handler, and then every later algorithm-level conclusion is built on it.
Runner Execution Model
The runner is an interpreter hidden inside flattened native code. The useful mental model is:
while running: raw_word = load(ip) opcode = decode(raw_word, ip, opcode_map) handler = dispatch(opcode) handler(context, raw_word) ip = context.next_ipThe real implementation is less clean. Dispatch state can be encoded in selector variables, multiple tails can be shared, and IP updates can be split across blocks. Still, the interpreter model gives us a target shape. Every recovered block should eventually fit into decode, dispatch, handler, or next-IP update.
CFG Flattening
CFG flattening makes the native runner look like a dense loop of selector updates. The recovery goal is not to produce beautiful native pseudo-code. It is to strip away the dispatcher skeleton until handler boundaries become visible.
The practical method is:
- collect dynamic block coverage under controlled inputs;
- identify the main dispatcher and sub-dispatcher states;
- remove common tails and selector plumbing from the analysis view;
- group blocks that always appear together as handler candidates;
- compare the grouped behavior with context diffs.
Once this is done, the runner stops looking like one giant function and starts looking like a table of VM handlers.
No-CFG Recovery
The no-CFG view is a cleaned control-flow skeleton. It is not a recompiled program and should not be treated as one. It is an analysis view that keeps semantic blocks and removes dispatcher noise.
The value of this view is speed. It allows the analyst to ask simple questions:
- which block loads the next VM word;
- which block reads the opcode map;
- which block writes output;
- which block updates IP;
- which blocks only move selector state.
That separation is enough to drive the offline runtime implementation.
Deep Runner Localization
Two heuristics are especially useful when the runner is buried behind wrappers.
timestamp-wrapper heuristic
Wrapper code often touches timing, watchdog, or bookkeeping state around the real work. The useful signal is not the timestamp itself. The signal is that a wrapper has a stable before/after shape around a deeper call that consumes the VM context.
effective-call-list ranking
Instead of ranking callees by static size, rank them by runtime effect: context mutation, blob access, IP movement, output write, and repeated presence under live calls. The runner scores highly because it changes the VM state in a dense and repeated way.
Live Body
The live body stage connects the top-level request path with the VM. It receives normalized input, enters the RQTX calculation chain, and eventually produces the short result.
This boundary is important because it prevents two mistakes:
- treating request normalization as the protected algorithm itself;
- treating a low-level VM block as the public API boundary.
The live body gives the offline runtime its external contract: normalized input in, short RQTX value out.
Recovery Workflow
Entry Adaptation
Start with the stable business entry, follow the slot chain, and stop at the body wrapper that actually enters the RQTX calculation stage. Use dynamic hits to prove that the static chain is not a dead path.
CFG Recovery
Use dynamic coverage to identify dispatcher blocks, shared tails, handler groups, and IP update blocks. Do not try to make the flattened native function look natural before handler behavior is known.
Runtime Reconstruction
Rebuild only what has been proven:
- packaged VM image loader;
- opcode map usage;
- context fields with stable behavior;
- instruction decoder;
- handler semantics;
- bridge input and output boundary.
The offline runtime should be allowed to start incomplete. It becomes reliable as more handler tests and live-vector comparisons pass.
Difficult Cases
1. Hex-Rays Pseudo-code Is Not Direct Evidence
Pseudo-code is a navigation aid. For flattened runners, it can merge unrelated selector states or hide real state changes. Use it to find candidates, then verify with traces.
2. Hot-path Instrumentation Can Change Behavior
Heavy instrumentation can slow the runner, alter timing, or trigger watchdog paths. Keep probes narrow and move from native-instruction tracing to block-level or context-diff tracing as early as possible.
3. Watchdog And Re-entry Noise
Repeated calls may include bookkeeping or re-entry paths that do not belong to the algorithm. Separate “called during the request” from “semantically required for the result.”
4. Version Address Drift
Addresses drift between versions. Layout relationships, object roles, and dynamic behavior are more portable than raw addresses.
5. Relocation Differences
A dump that looks byte-identical may still fail if relocation slots or runtime pointers are reconstructed incorrectly. Treat relocation as part of the VM image, not as external noise.
6. Observed Mode Is Not Full Coverage
Observed behavior proves the executed path, not the whole VM. Mark unobserved handlers and branches as incomplete until they are hit or explained by another proof.
Offline Runtime
The offline runtime is the final integration point. It should not be a pile of copied pseudo-code. It should look like a small interpreter:
load_image(rqtx_dat)build_context(input)while context.running: instruction = decode(image, context.ip) execute(instruction, context)return context.outputThe runtime has three jobs:
- reproduce observed behavior;
- make handler semantics testable;
- serve as a version-migration oracle.
It is acceptable for the runtime to contain unknown handlers during early recovery. What matters is that the known handlers are named, tested, and tied back to trace evidence.
Version Migration
Version migration should start from the previous evidence chain:
- find the new stable entry;
- re-follow the slot chain;
- re-prove the body wrapper;
- rebuild the VM init and context layout;
- compare
rqtx.datimage structure; - re-rank runner candidates by dynamic effect;
- port handler semantics only after decode and context behavior match.
This approach avoids the most fragile migration pattern: copying addresses from one version into another and hoping the structure stayed identical.
Review
The main lesson is that RQTX IRVMP recovery is not a single trick. It is a layered reconstruction problem. The useful output is not “we hooked the return value.” The useful output is a model where entry, data image, context, instruction decode, runner behavior, and offline verification all support each other.
That model also makes the next article possible: once the runtime can be observed and replayed, the next step is to lift the VM behavior into a semantic algorithm that can be compared with standard hashing structures.
FAQ
What is RQTX?
RQTX is a protected computation path in the WeChat request chain. In the analyzed path, it takes normalized request-related input and produces a short integer used by the request-side verification flow.
What is the core protection point of WeChat RQTX IRVMP?
The core protection is the combination of a packaged VM image, IP-aware instruction decoding, objectized runtime context, and a CFG-flattened native interpreter.
What does rqtx.dat contain?
The recovered model treats rqtx.dat as a packaged VM image that contains the opcode map, payload image, relocation/export slots, and internal VM blocks.
How can the VM runner be located?
Start from the stable entry, follow the wrapper chain, identify the bridge, then rank candidate callees by runtime effect: context mutation, VM image access, IP movement, and output writes.
Why is Hex-Rays F5 pseudo-code not enough?
Flattened control flow can make pseudo-code merge dispatcher state with real handler logic. It is useful for navigation, but handler semantics must be verified with dynamic traces and context diffs.
How does this method migrate to a newer WeChat version?
Migrate the evidence chain instead of raw addresses. Re-prove the entry, slot chain, VM init, context layout, image structure, runner behavior, and handler decode relationship for the new version.
Share Article
If this article helped you, please share it with others!
Comments
Stay anonymous and keep the discussion technical. Nickname and website are optional.