Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 59 additions & 30 deletions ADAPTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Also, PR welcome!

## Prerequisites

### Windows

You need to locate the folder which contains `WeChatAppEx.exe`, for newer versions, it will be something like

```
Expand All @@ -17,25 +19,38 @@ where the 19339 indicates the version number.

Use IDA to open `flue.dll` inside this folder (for much older versions, open `WeChatAppEx.exe`)

**Please wait for the loading patiently, or the strings/cross reference views will be incomplete and you cannot locate the offset.**
### Linux

Depending on your distribution's packaging policy, the path may vary.

```bash
readlink /proc/$(pidof WeChatAppEx | awk '{print $1}')/exe
```

Analyze `WeChatAppEx` using IDA.

> [!WARNING]
> Do not continue with any actions until the indicator in the lower-left corner of IDA displays `AU: idle`.

## LoadStartHookOffset

**(Starting from version 18891)** Please search `OnLoadStart` (camel-case) and find the x-ref function that references a string contains `applet_index_container.cc`. Possibly `sub_18xxxxxxx+E6`
### Windows (>=18891)

Please search `OnLoadStart` (camel-case) and find the x-ref function that references a string contains `applet_index_container.cc`. Possibly `sub_18xxxxxxx+E6`

![OnLoadStartHook.Extra.1](./screenshots/adaptation/onload_start_hook.extra.1.png)

> If you cannot find something similar to the picture below, and you can only see something like `.rdata:000000018AB7A3E4 db 4Fh ; O` , this means the loading is not complete, please wait.
If you cannot find something similar to the picture below, and you can only see something like `.rdata:000000018AB7A3E4 db 4Fh ; O` , this means the loading is not complete, please wait.

![OnLoadStartHook.Extra.2](./screenshots/adaptation/onload_start_hook.extra.2.png)

> To show the Pseudocode like below, press F5
To show the Pseudocode like below, press F5

![OnLoadStartHook.Extra.3](./screenshots/adaptation/onload_start_hook.extra.3.png)

If you found `sub_1825B50C0` (as shown in the above example) matches the pattern in the picture above, then you can fill in

```
```json
{
"Version": xxx,
"LoadStartHookOffset": "0x25B50C0",
Expand All @@ -46,7 +61,7 @@ Scroll down to the bottom of the pseudocode of this function and you will find s

(version 19339 example)

```cpp
```c
if ( (_BYTE)a2 )
result = sub_182B02350(*(_QWORD *)(*(_QWORD *)(a1 + 56) + 1376LL), *(_QWORD *)(*(_QWORD *)(a1 + 80) + 56LL));
if ( ((unsigned __int64)v19 ^ v27) != _security_cookie )
Expand All @@ -57,17 +72,11 @@ Scroll down to the bottom of the pseudocode of this function and you will find s
return result;
```

You will find the magic number `1376LL`, this is the first param of the SceneOffsets

```json
"SceneOffsets": [1376, ?, ?]
```

Double click and navigate to the function which contains that number ( `sub_182B02350` in the example above)
We are interested in functions that are executed only when `a2` is true, so please go to `sub_182B02350`.

You will find something like

```cpp
```c
result = *(_QWORD *)(a1 + 8);
if ( *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(result + 1312) + 16LL) + 456LL) != 1101 && *(_BYTE *)(a1 + 41) != 1 )
goto LABEL_32;
Expand All @@ -80,24 +89,47 @@ result = *(_QWORD *)(a1 + 8);
ws:__localhost:9421 = (char *)&ws:__localhost:9421_1;
```

The number `1312` and `456` is what we need for the rest of SceneOffsets
The magic number `1101` is what we need. We need to tell the script how to access `1101` through multiple layers of pointer offsets and dereferencing. Please refer to the code in `hook.js`:

```javascript
const miniappConfigPtr = a1
.add(sceneOffsets[0])
.readPointer()
.add(sceneOffsets[1])
.readPointer();
const miniappScenePtr = miniappConfigPtr
.add(sceneOffsets[2])
.readPointer()
.add(sceneOffsets[3])
.readPointer()
.add(sceneOffsets[4])
.readPointer()
.add(sceneOffsets[5]);
```

Based on the pattern of the first argument passed to `sub_182B02350`, the `result`, and the `if` statement block, overall `(*(_QWORD *)(a1 + 56) + 1376LL) -> result = *(_QWORD *)(a1 + 8); -> (*(_QWORD *)(*(_QWORD *)(result + 1312) + 16LL) + 456LL)`, the answer is:

```json
"SceneOffsets": [1376, 1312, 456]
"SceneOffsets": [56, 1376, 8, 1312, 16, 456]
```

> **(For old version 14199 as an example)** Locate the `AppletIndexContainer::OnLoadStart` function by searching `[perf] AppletIndexContainer::OnLoadStart`
### Linux, Windows (>=14199, Old Version)

Locate the `AppletIndexContainer::OnLoadStart` function by searching `[Perf] AppletIndexContainer::OnLoadStart`
in strings.
>
> ![OnLoadStartHook.1](./screenshots/adaptation/onload_start_hook.1.png)
>
> Hit `x`, the only x-ref function address is the offset.
>
> ![OnLoadStartHook.2](./screenshots/adaptation/onload_start_hook.2.png)**Note:
>

![OnLoadStartHook.1](./screenshots/adaptation/onload_start_hook.1.png)

Hit `x`, the only x-ref function address is the offset.

![OnLoadStartHook.2](./screenshots/adaptation/onload_start_hook.2.png)

How to find SceneOffsets? Please refer to the section above.

> [!NOTE]
> Also, check the struct offset in these two marked functions.
> These offsets are being used in the `onLoadStartHook` function in [frida/hook.js](frida/hook.js)
>
>
> ![OnLoadStartHook.3](./screenshots/adaptation/onload_start_hook.3.png)

## CDPFilterHookOffset
Expand All @@ -116,15 +148,15 @@ function called in the x-refed function `sub_181DB82D0`.

![CDPFilterHook.3](./screenshots/adaptation/cdp_filter_hook.3.png)

```
```json
"CDPFilterHookOffset": "0x24839E0",
```

## Save the config

Save `addresses.xxxxx.json` in frida/config with the params you found above where `xxxxx` is the new version

```
```json
{
"Version": xxxxx,
"LoadStartHookOffset": "0x25B5DD0",
Expand All @@ -151,6 +183,3 @@ is the target function.


![ResourceCacheHook.2](./screenshots/adaptation/resource_cache_hook.2.png)



2 changes: 1 addition & 1 deletion frida/config/linux/addresses.14978.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Version": 14978,
"LoadStartHookOffset": "0x8320180",
"CDPFilterHookOffset": "0xBCB4C40",
"CDPFilterHookOffset": "0xBCAAFD0",
"SceneOffsets": [56, 1360, 8, 1312, 16, 488]
}
78 changes: 20 additions & 58 deletions frida/hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ const getPlatform = () => {
return Process.platform;
}

const isArmDarwin = () => {
return Process.platform === "darwin" && Process.arch === "arm64";
}

const getMainModule = (version) => {
const osPlatform = getPlatform();
if (osPlatform === 'windows') {
Expand All @@ -25,47 +21,23 @@ const patchCDPFilter = (base, config) => {
// xref: SendToClientFilter OR devtools_message_filter_applet_webview.cc
const offset = config.CDPFilterHookOffset;
Interceptor.attach(base.add(offset), {
onEnter(args) {
if (!isArmDarwin()) {
// x64 windows/linux: save args[0] for use in onLeave
send(
`[patch] CDP filter on enter, original value of input: ${args[0].readPointer()}`,
);
this.inputValue = args[0];
}
},
onLeave(retval) {
if (!isArmDarwin()) {
// x64 windows/linux
const inputValue = this.inputValue.readPointer();
if (inputValue.isNull() || inputValue.add(8).isNull()) {
// there's a chance the value could be null
// return here to avoid crash
return;
}

send(
`[patch] CDP filter on leave, patch input, now value: ${inputValue}; ` +
`*(input + 8) = ${inputValue.add(8).readU32()}`,
);
if (inputValue.add(8).readU32() == 6) {
inputValue.add(8).writeU32(0x0);
}
} else {
// arm64 darwin, caller checks retval+8 == 6, patch it to 0
if (retval.isNull()) return;
try {
const val = retval.add(8).readU32();
send(`[patch] CDP filter on leave (mac), retval+8 = ${val}`);
if (val === 6) {
retval.add(8).writeU32(0x0);
send("[patch] CDP filter patched (mac)");
}
} catch (e) {
send(`[patch] CDP filter error: ${e}`);
onLeave(retval_) {
// see https://github.com/evi0s/WMPFDebugger/pull/262
const retval = getPlatform() == 'windows'
? retval_.readPointer()
: retval_;
if (retval.isNull()) return;
try {
const val = retval.add(8).readU32();
send(`[patch] CDP filter on leave, retval+8 = ${val}`);
if (val === 6) {
retval.add(8).writeU32(0x0);
send("[patch] CDP filter patched");
}
} catch (e) {
send(`[patch] CDP filter error: ${e}`);
}
},
}
});
};

Expand Down Expand Up @@ -118,26 +90,16 @@ const patchOnLoadStart = (base, config) => {
// xref: AppletIndexContainer::OnLoadStart
Interceptor.attach(base.add(config.LoadStartHookOffset), {
onEnter(args) {
// arm64 (darwin): x0=this, x1=debug_flag
// x64 (windows/linux): rcx=this, rdx=debug_flag
const thisPtr = isArmDarwin() ? this.context.x0 : this.context.rcx;
send(
`[inteceptor] AppletIndexContainer::OnLoadStart onEnter, ` +
`indexContainer.this: ${thisPtr}`,
`indexContainer.this: ${args[0]}`,
);
if (isArmDarwin()) {
// arm64 darwin: set x1 to 1
if ((this.context.x1.toInt32() & 0xff) !== 1) {
this.context.x1 = ptr(1);
}
} else {
// x64 windows/linux: set dl to 1
if ((this.context.rdx & 0xff) !== 1) {
this.context.rdx = (this.context.rdx & ~0xff) | 0x1;
}
// write debug_flag to 0x1
if (args[1].and(0xff).toInt32() !== 1) {
args[1] = args[1].and(ptr("0xffffffffffffff00")).or(1);
}
// handle onLoad scene
hookOnLoadScene(thisPtr, config.SceneOffsets);
hookOnLoadScene(args[0], config.SceneOffsets);
},
onLeave(retval) {
// do nothing
Expand Down