Wechat applet is encrypted and stored on the PC side. If you open it directly, you can’t see any useful information. Only after decryption can you see the specific contents of the package. In this paper, nodeJS is used to realize the decryption algorithm, mainly involving the use of crypto, Commander and Chalk packages.

Where is the source of the small program

The small program opened on the PC will be cached to the default location of the local wechat file, which can be viewed through the wechat PC => More => Settings:

Enter the /WeChat Files/WeChat Files/Applet folder in the default location, you can see a series of Files with the prefix wx in this directory (the file name is actually the appID of the Applet), these are the Applet we have opened:

In the folder of one of the small programs, we see a folder named a string of numbers. Click on this folder to see an __APP__. Wxapkg file, which is the corresponding code for the applet:

However, when we opened the file, we found this:

WTF this can be seen 🔨. Obviously, the file is encrypted and needs to be decrypted to see what we want to see.

How are PC applets encrypted

Here is a reference to the PC side wXAPkg decryption code written by a big guy with Go language. To tidy things up, the encryption process looks like this:

The plaintext code is first split at byte 1024, the first half is encrypted using AES in CBC mode, and the second half is xOR directly. Finally, concatenate the encrypted two sections and write the fixed string “V1MMWX” at the beginning.

So we open the __app__. wxapkg file to see the encrypted code, and if we want to restore it, we need to step back from the back.

Decrypt the train of thought

pretreatment

We use Node.js to write a decoding program. According to the above encryption process, we first read the encrypted file, remove the first 6 bytes of the fixed string. Since the bits before and after AES encryption are the same as the xOR data, we can obtain the encrypted header 1024 bytes and the encrypted tail:

const fs = require('fs').promises; . const buf = await fs.readFile(pkgsrc); Const bufHead = buf.slice(6, 1024 + 6); const bufTail = buf.slice(1024 + 6);Copy the code

The encrypted header

To get the 1024 bytes of plaintext, we need to know the initial vector IV for AES encryption, as well as a 32-bit key. Given that the 16-byte initial vector iv is a string: “The iv: 16 bytes”, we then need to calculate the 32-bit key exported by the PBKDF2 algorithm.

Pbkdf2 (Password-based Key Function) is a Function for generating keys that uses a pseudo-random Function that takes the original Password and SALT as inputs and obtains keys through continuous iteration. In the Crypto library, pbkdf2 looks like this:

const crypto = require('crypto'); . crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)Copy the code

The parameters are: original password, salt value, iteration times, key length, hash algorithm, callback function. Given that salt was “saltiest”, the original password was the wechat applet ID (i.e., the folder name starting with Wx), the number of iterations was 1000 and the hash algorithm was SHA1. Therefore, we can write code to calculate the key:

Crypto. Pbkdf2 (wxid, salt, 1000, 32, 'sha1', (err, dk) => {if (err) {// error} // dk = calculated key})Copy the code

Now that we have the key and the initial vector IV, we can begin to decrypt the ciphertext. The AES encryption algorithm is an asymmetric encryption algorithm. The key is divided into a public key and a private key known only to the user. Anyone can use the public key to encrypt, but only the user with the private key can decrypt the plaintext.

The encryption algorithm used by small programs is THE AES in Cipher Block Chaining (CBC) mode. That is, during encryption, plaintext is divided into blocks, each Block is xor with the ciphertext of the previous Block, and the public key is used for encryption to obtain the ciphertext of each Block. For the first plaintext, since it does not have the previous plaintext, it performs xor with the initial vector IV, followed by public key encryption. In the implementation, we only need to call the decryption function provided by Crypto.

We know that the AES algorithm has AES128, AES192, and AES256 depending on the key length. Recall that our key is 32 bytes, meaning 256 bits, so obviously we should use AES256. To sum up, we can write the decryption code:

const decipher = crypto.createDecipheriv('aes-256-cbc', dk, iv);
const originalHead = Buffer.alloc(1024, decipher.update(bufHead));
Copy the code

OriginalHead is the first 1024 bytes of plaintext. We can print it out and see:

HMM… That’s kind of interesting.

The encrypted tail part

This part is pretty easy. Due to the exclusive or operation is a reflexive, so simply determine the applet id digits for xor xorKey, put it with the ciphertext or, you can get the original:

const xorKey = wxid.length < 2 ? 0x66 : wxid.charCodeAt(wxid.length - 2);
const tail = [];
for(let i = 0; i < bufTail.length; ++i){
    tail.push(xorKey ^ bufTail[i]);
}
const originalTail = Buffer.from(tail);
Copy the code

By concatenating the plaintext in the header and the plaintext in the tail, and writing it to the file in binary form, we can get the final plaintext.

Bit more beautiful

According to the above description, we can encapsulate our entire decryption process into a black box:

commander

We can use the COMMANDER library to let programs read the ids and ciphertext packages of applets directly from the command line. Commander is a nodeJS command-line interface solution that allows you to easily define your own CLI commands. For example, for this string of code:

const program = require('commander'); . Description ('decry < wxID > < SRC > [DST]'). Action ((wxID, SRC, DST) => {WXMD (wxid, SRC, dst); }) program.version('1.0.0').usage("decry < wxID > < SRC > [DST]").parse(process.argv);Copy the code

I define a “decry [DST]” command where Angle brackets represent required parameters and square brackets represent optional parameters. Description is the description of the command, and action is the execution of the command. After executing the code on the console using Node, you see the following interface:

So we can follow the prompts, input parameters for decryption. The Chinese documentation for commander.js is available here.

chalk

To give our console a touch of color, we can use chalk. Js to beautify the output. The basic use of chalk is also simple:

const chalk = require('chalk'); . The console. The log (chalk. Green (' green '))Copy the code

So we can fill in the black and white console with a touch of green, for the giant panda dream:

In addition, we can use es6’s string tag template to make chalk easier. Please refer to chalk’s official documentation for details.

The source code

The code has been posted to Github and Gitee for your reference

Github address: github.com/maotoumao/w…

Gitee address: gitee.com/maotoumao/w…

If the fruit small 🔥 companion feel have meaning to think that to sprout new point a praise 8️ discount note a whole live ah