kuaidui作业unidbg逆向(2)

2022-02-17  本文已影响0人  ever_hu

kuaidui作业unidbg逆向

nativeInitBaseUtil

在unidbg实现中,调用nativeGetSign之前,需要先调用nativeSetToken,这是因为需要先设置objSpamServer.random_number

image-20220126151954449 image-20220126152038418

nativeSetToken的入参又来自于nativeInitBaseUtil

kuaidui

先分析代码比较少的nativeInitBaseUtil

image-20220126152555193

看看getChallenge函数

image-20220126153147089

很简单,生成随机10位字符串

剩下的函数就比较容易明白它的作用。CRYMd5生成MD5,CRYStringCat字符串拼接,DES_Encryptdes加密,str2hex将输入转为16进制字符串。现在需要分析的其实只有后面两个函数。

unidbg实现

先用unidbg调用nativeInitBaseUtil,之后再结合ida来逆向

public class Kuaidui extends AbstractJni {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    public static String pkgName = "com.kuaiduizuoye.scan";
    public static String apkPath = "unidbg-android/src/test/java/com/kuaidui/kuaidui540.apk";
    public static String soPath = "";

    public Kuaidui() {
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName(pkgName).build();
        Memory memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));
        vm = emulator.createDalvikVM(new File(apkPath));
        vm.setJni(this);
        vm.setVerbose(true);
        new AndroidModule(emulator, vm).register(memory);
        DalvikModule dm = vm.loadLibrary("baseutil", true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "android/content/pm/PackageManager->GET_SIGNATURES:I": {
                return 64;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }

    public void call_init() {
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(vm.resolveClass("android/content/Context").newObject(null)));
        list.add(vm.addLocalObject(new StringObject(vm, "F5D53AD5A66144B57783C7C67611F0F7|0")));
        Number ret = module.callFunction(emulator, 0x1010, list.toArray());
    }

    public static void main(String[] args) {
        Kuaidui test = new Kuaidui();
        test.call_init();
    }
image-20220126153904882

报了个错,跳转过去看看

image-20220126153935089

好像不支持,我们简单的把它加上去试试

image-20220126154052050 image-20220126170756492

出结果了,不过有一点不好的就是,每次运行生成的结果都不一样。这是因为getChallenge函数里面的rand函数导致的。将它hook住,改为固定值。

public void hook_libc() {
    IHookZz hookZz = HookZz.getInstance(emulator);
    hookZz.enable_arm_arm64_b_branch();
    hookZz.wrap(module.findSymbolByName("rand"), new WrapCallback<HookZzArm64RegisterContext>() {
        @Override
        public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
        }

        @Override
        public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
            ctx.setXLong(0, 1L);
        }
    });
}
public static void main(String[] args) {
    Kuaidui test = new Kuaidui();
    test.hook_libc();
    test.call_init();
}

由于随机数的结果一直为1,所以getChallenge的结果为BBBBBBBBBB

逆向

接下来开始逆向DES_Encryptstr2hex

image-20220126154835129

hook看看参数

emulator.attach().addBreakPoint(module.base+0x61f4);
image-20220126154937169 image-20220126155041419

很容易构造出。

blr在函数返回处下断点,c运行到函数返回处,mx0看看返回结果

image-20220126155539906 image-20220126155605178

有了输入,key和输出,在cyberchef验证下

image-20220126155837910

emm,和标准结果差的不是一点半点,有可能是DES-CBC,或者DES经过了魔改。

image-20220126160111449

这里是实现块加密的代码,可以看出就是DES-ECB,没有向量或其他东西。那说明DES很可能经过了魔改。

image-20220126160805050

这部分是密钥生成部分,涉及到的常量有PC_1, MOVE_TIMES, PC_2

image-20220126161310755

比较容易看到的修改就是PC_2有个值被改了。

还有就是块加密部分的常量

image-20220126170237660 image-20220126170203044

实际操作上,直接把这些常量全部拷贝下来就行了,根本不用对比,免得自己看漏了。

image-20220126161829920 image-20220126162404458

另外一个就是byte转bit的时候高位低位是相反的,比如0x8d在标准实现中是10001101,这里则是11010001

在标准DES实现的基础上做出修改后,再调用试试

image-20220126162654331

和unidbg hook到的结果一致。

接下来就是str2hex函数。

image-20220126163012195 image-20220126163041739

str2hex的输入就是DES_Encrypt的结果,输出就是最后的结果。

image-20220126163255158

其实对照着代码也挺容易实现的,就是把一个byte转成bit之后再逆序,然后再转成2个byte,最后变成hex形式。

def byte2hex(data):
    """
        8d
        |
    10001101
        |
    10110001
    /    \
    1011 0001
    |    |
    0b   01
    """
    result = []
    for v9 in data:
        d = f'{v9:08b}'[::-1]
        result.append(int(d[:4], 2))
        result.append(int(d[4:], 2))
    return bytes(result).hex()

把前面的拼接起来验证下

def calc_request_hex(challenge, devid, pkg_sign='2fb53de6d38eff7109f19d68e047123b'):
    """
    Args:
        challenge: length-of-10 random string, charset: a-zA-Z0-9
        devid: cuid
        pkg_sign: md5 of pkg signature, default: sign of v5.4.0
    """
    data = '##'.join(('8&%d*', challenge, pkg_sign, devid))
    data2 = encrypt_hex(data.encode(), b'@fG2SuLA')
    return data2

def encrypt_hex(data, key):
    cryptor = DES(key, padmode=PAD_PKCS5)
    data2 = cryptor.encrypt(data)
    data3 = byte2hex(data2)
    return data3

def test_request_hex():
    challenge = 'B' * 10
    devid = 'F5D53AD5A66144B57783C7C67611F0F7|0'
    req_str = calc_request_hex(challenge, devid)
    print(req_str)
image-20220126164022514

和unidbg结果一致。

nativeSetToken

nativeSetTokennativeInitBaseUtil相比是负责解密数据。

image-20220126164334084

其实主要也就这几个地方。

image-20220126164414998

hex2Str就是str2hex的反向操作,重写一下即可。

def hex2byte(data):
    """
    0b   01
    |    | 
    1011 0001
    \    /
    10110001
        |
    10001101
        |
        8d
    """
    data2 = bytes.fromhex(data)
    result = []
    for idx in range(0, len(data2), 2):
        d = f'{data2[idx]:04b}{data2[idx+1]:04b}'
        result.append(int(d[::-1], 2))
    return bytes(result)

接下来用hook到的真实数据试试。

先将请求数据解密看看

def decrypt_hex(data, key):
    data2 = hex2byte(data)
    cryptor = DES(key, padmode=PAD_PKCS5)
    data3 = cryptor.decrypt(data2)
    return data3

def test_decode2():
    key = b'@fG2SuLA'
    data = '03090b0106000807080e00080201090708060f00040700020d0503070c0606000c01000a080e07050808020d00060b0d070f0f080306060b00000f0d04070d0d02070b0f020a01010c0d030a09080e090b040f02080c0b040c0e0e060c0d0201020f0e02030d0107020d000e0e02090401070505030a0c0605080303040e0803020c020e0d020408010b030e0b090f060302000e0f0902010706040c00080d0e060d000f0805040b0e07000d020b0f07'
    req_str = decrypt_hex(data, key)
    print(req_str)
image-20220126165057321

可以看到和nativeInitBaseUtil中的形式是一样的。

然后是响应数据解密,它的key是原始请求数据的7-12位加上#G4,也就是wobBX#G4

def test_decode():
    key = b'wobBX#G4'
    data = '0a040609080d020b0a03090e010306050d0d050c0c060207070105010b0b01090801000a020d0c0b03030209030e0d0a'
    resp_str = decrypt_hex(data, key)
    print(resp_str)
image-20220126165540276

objSpamServer.random_number映入眼帘。

上一篇下一篇

猜你喜欢

热点阅读