车载微信二维码登录Protobuf分析

前言 搞了个微信协议, 结果这段时间陆续全拉闸了, 没办法了, 自己动手逆向登录吧 接口逆向 在Jadx中搜索/cgi-bin/micromsg-bin/getloginqrcode 可找到: package com.tencent.mm.libwxclient.b; import com.tenc

前言

搞了个微信协议, 结果这段时间陆续全拉闸了, 没办法了, 自己动手逆向登录吧

接口逆向

在Jadx中搜索/cgi-bin/micromsg-bin/getloginqrcode

可找到:

package com.tencent.mm.libwxclient.b;
import com.tencent.mm.protocal.b.du;
import com.tencent.mm.protocal.b.dv;
import com.tencent.mm.protocal.p;
import com.tencent.mm.q.b;

/* loaded from: classes.dex */
public final class b extends com.tencent.mm.q.a<dv> {
    public b() {
        b.a aVar = new b.a();
        aVar.cJK = new du();
        aVar.cJL = new dv();
        aVar.cJI = 502;
        aVar.uri = "/cgi-bin/micromsg-bin/getloginqrcode";
        aVar.cJM = 232;
        aVar.cJN = 1000000232;
        com.tencent.mm.q.b En = aVar.En();
        du duVar = (du) En.cJG.cJP;
        duVar.eJY = com.tencent.mm.platformtools.e.G(com.tencent.mm.libwxclient.logic.account.b.a.czW);
        duVar.eKa = 0;
        duVar.eGY = com.tencent.mm.protocal.b.DEVICE_NAME;
        En.a(p.Wb());
        En.cJJ = 1;
        En.Eq().eEl = com.tencent.mm.libwxclient.logic.account.b.a.czW;
        this.cnM = En;
    }

}

对应微信协议可以发现, duVar是Protobuf结构

hypack := hec.HybridEcdhPackIosEn(502, 0, nil, reqdata)
recvData, err := httpclient.MMtlsPost(D.ShortHost, "/cgi-bin/micromsg-bin/getloginqrcode", hypack, Data.Proxy)

Hook

查找Protobuf特征toByteArray可以发现:

package com.tencent.mm.ag;

/* loaded from: classes.dex */
public class a {
    protected static final int OPCODE_COMPUTESIZE = 1;
    protected static final int OPCODE_PARSEFROM = 2;
    protected static final int OPCODE_POPULATEBUILDERWITHFIELD = 3;
    protected static final int OPCODE_WRITEFIELDS = 0;
    public static net.a.a.a.a.b unknownTagHandler = new net.a.a.a.a.a();
    // 将Protobuf序列化
    public byte[] toByteArray() {
        VQ();
        byte[] bArr = new byte[VR()];
        net.a.a.c.a aVar = new net.a.a.c.a(bArr);
        a(aVar);
        if (aVar.cJk != null) {
            aVar.cJk.write(aVar.hkb);
            aVar.cJk.flush();
        }
        return bArr;
    }

    public static int a(net.a.a.a.a aVar) {
        int i = 0;
        net.a.a.b.a.a aVar2 = aVar.hjZ;
        if (aVar2.bufferPos != aVar2.bufferSize || aVar2.df(false)) {
            aVar2.lastTag = aVar2.readRawVarint32();
            if (aVar2.lastTag == 0) {
                throw net.a.a.b.a.b.aot();
            }
            i = aVar2.lastTag;
        } else {
            aVar2.lastTag = 0;
        }
        aVar.hka = i;
        return net.a.a.b.a.getTagFieldNumber(aVar.hka);
    }

    public a VQ() {
        return this;
    }

    public int b(int i, Object... objArr) {
        throw new Error("Cannot use this method");
    }

    public void a(net.a.a.c.a aVar) {
        b(0, aVar);
    }

    public int VR() {
        try {
            return b(1, new Object[0]);
        } catch (Exception e2) {
            return 0;
        }
    }
    // 将Protobuf反序列化
    public a T(byte[] bArr) {
        b(2, bArr);
        return this;
    }

    public boolean a(net.a.a.a.a aVar, a aVar2, int i) {
        return b(3, aVar, aVar2, Integer.valueOf(i)) == 0;
    }
}
// FridaHook代码
Java.perform(function () {
    console.log("[*] WeChat protobuf hook started...");

    const ProtoBase = Java.use("com.tencent.mm.ag.a");
    const orig_toByteArray = ProtoBase.toByteArray.overload();
    const orig_parseFrom = ProtoBase.T.overload('[B');

    function javaByteArrayToJsArray(javaByteArray) {
        const len = javaByteArray.length;
        const jsArr = new Array(len);
        for (let i = 0; i < len; i++) jsArr[i] = javaByteArray[i] & 0xFF;
        return jsArr;
    }

    function bytesToString(jsArr) {
        if (typeof TextDecoder !== 'undefined') {
            return new TextDecoder('utf-8').decode(new Uint8Array(jsArr));
        }
        // fallback(仅适合 ASCII)
        return jsArr.map(x => String.fromCharCode(x)).join('');
    }
    
    
    // --- Outgoing ---
    ProtoBase.toByteArray.implementation = function () {
        const buffer = orig_toByteArray.call(this);
        const className = this.getClass().getName();
        const len = buffer ? buffer.length : 0;

        if (len > 0) {
            const jsArr = javaByteArrayToJsArray(buffer);
            if (bytesToString(jsArr).includes('com.tencent.mm.')) {
                return buffer;
            }
            console.log("\n[+] Outgoing Message Serialized (" + className + ")");
        console.log("==========================================================");
        console.log("  [+] Data Length: " + len + " bytes");
            const ptrBuf = Memory.alloc(len);
            Memory.writeByteArray(ptrBuf, jsArr);
            console.log(hexdump(ptrBuf, {
                offset: 0,
                length: len,
                header: true,
                ansi: true
            }));
            console.log(jsArr)
        } else {
            console.log("  [-] Empty or null buffer");
        }

        console.log("----------------------------------------------------------");
        return buffer;
    };
    
    // --- Incoming ---
    ProtoBase.T.implementation = function (bArr) {
        const className = this.getClass().getName();
        const len = bArr ? bArr.length : 0;

        

        if (len > 0) {
            const jsArr = javaByteArrayToJsArray(bArr);
            if (bytesToString(jsArr).includes('com.tencent.mm.')) {
                return orig_parseFrom.call(this, bArr);
            }
            console.log("\n[+] Incoming Message to be Parsed (" + className + ")");
            console.log("==========================================================");
            console.log("  [+] Data Length: " + len + " bytes");
            const ptrBuf = Memory.alloc(len);
            Memory.writeByteArray(ptrBuf, jsArr);
            console.log(jsArr)
            console.log(hexdump(ptrBuf, {
                offset: 0,
                length: len,
                header: true,
                ansi: true
            }));
        } else {
            console.log("  [-] Empty or null buffer");
        }

        console.log("----------------------------------------------------------");

        // 调用原始 parseFrom,不要递归
        return orig_parseFrom.call(this, bArr);
    };

    console.log("[*] Hooks installed on com.tencent.mm.by.a");
});

Java.perform(function () {
    let LauncherFloatView = Java.use("com.tencent.mm.ui.LauncherFloatView");
    LauncherFloatView["aaw"].implementation = function () {
        console.log(`LauncherFloatView.aaw is called`);
        let result = this["aaw"]();
        console.log(`LauncherFloatView.aaw result=${result}`);
        return true;
    };
    LauncherFloatView["a"].overload('com.tencent.mm.ui.LauncherFloatView', 'boolean', 'boolean', 'int').implementation = function (launcherFloatView, z, z2, i) {
        console.log(`LauncherFloatView.a is called: launcherFloatView=${launcherFloatView}, z=${z}, z2=${z2}, i=${i}`);
        this["a"](launcherFloatView, z, true, i);
    };
    // 开日志输出
    let n = Java.use("com.tencent.mm.sdk.platformtools.n");
    n["i"].overload('java.lang.String', 'java.lang.String', '[Ljava.lang.Object;').implementation = function (str, str2, objArr) {
        console.log(`n.i is called: str=${str}, str2=${str2}, objArr=${objArr}`);
        this["i"](str, str2, objArr);
    };
    n["d"].overload('java.lang.String', 'java.lang.String', '[Ljava.lang.Object;').implementation = function (str, str2, objArr) {
        console.log(`n.d is called: str=${str}, str2=${str2}, objArr=${objArr}`);
        this["d"](str, str2, objArr);
    };
    n["v"].overload('java.lang.String', 'java.lang.String', '[Ljava.lang.Object;').implementation = function (str, str2, objArr) {
        console.log(`n.v is called: str=${str}, str2=${str2}, objArr=${objArr}`);
        this["v"](str, str2, objArr);
    };
    n["c"].overload('java.lang.String', 'java.lang.String', '[Ljava.lang.Object;').implementation = function (str, str2, objArr) {
        console.log(`n.c is called: str=${str}, str2=${str2}, objArr=${objArr}`);
        this["c"](str, str2, objArr);
    };
    n["g"].overload('java.lang.String', 'java.lang.String', '[Ljava.lang.Object;').implementation = function (str, str2, objArr) {
        console.log(`n.v is called: str=${str}, str2=${str2}, objArr=${objArr}`);
        this["g"](str, str2, objArr);
    };
    LauncherFloatView["a"].overload('com.tencent.mm.ui.LauncherFloatView', 'boolean', 'boolean', 'int').implementation = function (launcherFloatView, z, z2, i) {
        console.log(`LauncherFloatView.a is called: launcherFloatView=${launcherFloatView}, z=${z}, z2=${z2}, i=${i}`);
        this["a"](launcherFloatView, true, true, 0);
    };
    let b = Java.use("com.tencent.mm.sdk.platformtools.b");
    b.DEBUG.value = true
    let a = Java.use("com.tencent.mm.loader.stub.a");
    a.DEBUG.value = true;
})

Protobuf还原

1 {
  1: "\000"
  2: 0
  3: "A58c561f2e7d84f\000"
  4: 553651456
  5: "car-31"
  6: 0
}
2 {
  1: 16
  2: "\335\334 \342\354\021\253U\246\3544i\017\231\300%"
}
3: 0
4: "Xiaomi-M2004J19C"

将序列化后的Protobuf保存, 使用protoc --decode_raw < xxx.bin 还原得到上述结构, 可见增加了一个字段, 且DeviceID改为了Axxxx开头形式

LICENSED UNDER CC BY-NC-SA 4.0
Comment