某招聘app最新版本的逆向

1.先抓包:参数体有2次动态加密,请求头看着也有2个,请求体的以后有时间再说
在这里插入图片描述

2.然后就可以用frida去hook对应的sp和sig的,比如hashmap和jsonObject都可以
3.然后在点两下跟踪到这里了
在这里插入图片描述
4.ida打开对应的so,发现是动态加密,用动态注册脚本去hook一下对应地址
5.
5.跳到0x209a4地址去看下,看着函数不大,静态分析没发现敏感函数
6.我喜欢从下到上分析,一直跟的话就会到下图这里,下图v59就是返回值
在这里插入图片描述
在这里插入图片描述
7.静态分析到这里结束了,因为unidbg比较容易分析算法,所以开始搭一下架子,都不难我就贴出来了

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.Debugger;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.api.PackageInfo;
import com.github.unidbg.linux.android.dvm.api.Signature;
import com.github.unidbg.linux.android.dvm.array.ArrayObject;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.StringArray;
import net.dongliu.apk.parser.bean.CertificateMeta;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class A extends AbstractJni  implements  IOResolver  {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private final DvmClass CheckCodeUtil;

    private final Memory memory;

    private final DalvikModule dvm;
    public A(){
        emulator = AndroidEmulatorBuilder.for64Bit()
                .setProcessName("")
                .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); //设置系统类库解析
        //  emulator.getSyscallHandler().addIOResolver(this);
        vm = emulator.createDalvikVM(new File("apk")); // 创建Android虚拟机
        //vm.setVerbose(true); // 设置是否打印Jni调用细节
        vm.setJni(this);
        dvm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\boss\\apk\\libyzwg.so"), true); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        module = dvm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
        vm.callJNI_OnLoad(emulator,module);
        CheckCodeUtil = vm.resolveClass("com/twl/signer/YZWG");
    }
    public static int randInt(int min, int max) {
        Random rand = new Random();
        return rand.nextInt((max - min) + 1) + min;
    }
    public A(AndroidEmulator emulator, VM vm, Module module, DvmClass checkCodeUtil, Memory memory, DalvikModule dvm) {
        this.emulator = emulator;
        this.vm = vm;
        this.module = module;
        CheckCodeUtil = checkCodeUtil;
        this.memory = memory;
        this.dvm = dvm;
    }
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/content/pm/PackageManager->getPackagesForUid(I)[Ljava/lang/String;":
                return new ArrayObject(new StringObject(vm,vm.getPackageName()));
        }
        return super.callObjectMethod(vm,dvmObject,signature,varArg);
    }
    @Override
    public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature) {
            case "android/content/pm/PackageInfo->signatures:[Landroid/content/pm/Signature;":
                PackageInfo packageInfo = (PackageInfo) dvmObject;
                if (packageInfo.getPackageName().equals(vm.getPackageName())) {
                    CertificateMeta[] metas = vm.getSignatures();
                    if (metas != null) {
                        Signature[] signatures = new Signature[metas.length];
                        for (int i = 0; i < metas.length; i++) {
                            signatures[i] = new Signature(vm, metas[i]);
                        }
                        return new ArrayObject(signatures);
                    }
                }
        }
        throw new UnsupportedOperationException(signature);
    }
    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature) {
            case "java/lang/String->hashCode()I":
                String string = dvmObject.getValue().toString();
                return string.hashCode();
        }
        return super.callIntMethod(vm, dvmObject, signature, varArg);
    }
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "com/twl/signer/YZWG->gContext:Landroid/content/Context;": {
                return vm.resolveClass("android/app/Activity",vm.resolveClass("android/content/ContextWrapper",vm.resolveClass("android/content/Context"))).newObject(null);
            }
        }
        return super.getStaticObjectField(vm,dvmClass,signature);
    }

    public void nativeEncodeRequest(){
        //这里的参数是前面hook java层得到的
        String str1 = "client_info=%7B%22version%22%3A%2212%22%2C%22os%22%3A%22Android%22%2C%22start_time%22%3A%221735141170757%22%2C%22resume_time%22%3A%221735141170758%22%2C%22channel%22%3A%2228%22%2C%22model%22%3A%22Android%7C%7C22127RK46C%22%2C%22dzt%22%3A0%2C%22loc_per%22%3A0%2C%22uniqid%22%3A%222cd19eaa-9e9b-4a38-83a1-1ea5fecf70e6%22%2C%22oaid%22%3A%22NA%22%2C%22oaid_honor%22%3A%22NA%22%2C%22did%22%3A%22DUh8qhSRH0dLEKQrusw_A7mX6SXI4lfP1tb9RFVoOHFoU1JIMGRMRUtRcnVzd19BN21YNlNYSTRsZlAxdGI5c2h1%22%2C%22tinker_id%22%3A%22Prod-arm64-v8a-release-12.190.1219010_1018-11-55-40%22%2C%22is_bg_req%22%3A0%2C%22network%22%3A%22none%22%2C%22operator%22%3A%22UNKNOWN%22%2C%22abi%22%3A1%7D&curidentity=0&data=%5B%7B%22action%22%3A%22f1-tab%22%2C%22time%22%3A1735141987811%7D%5D&req_time=1735141988331&uniqid=2cd19eaa-9e9b-4a38-83a1-1ea5fecf70e6&v=12.190";
        String str2 = "39982a32e92e18603e73ee090e774bc9";
        try {
            byte[] bytes = str1.getBytes("UTF-8");
            DvmObject ret = CheckCodeUtil.callStaticJniMethodObject(emulator, "nativeEncodeRequest([BLjava/lang/String;)Ljava/lang/String;",bytes,str2);
            String strOut = (String)ret.getValue();
            System.out.println(strOut);
        }catch (Exception e){
        }
    }
    public static void main(String[] args) {
        A a = new A();
        a.nativeEncodeRequest();
    }
}

8.这样就完成了,主动调用的话就会出结果,我想的是和frida hook结果对比了一下,是一致的
9.本篇是以学习为目的,所以接着分析,上图有讲v59就是返回值,不确定的话可以打断点看一下,v59是v23返回的
所以分析看一下sub_1CEB8(v23, v19)和sub_29E90(v23, size_4, v56);这2个函数
在这里插入图片描述

10,静态分析看了一下,sub_1CEB8函数什么也没做,在看看sub_29E90,先说参数a1是结果,a2是数据,a3是长度(我是分析完成写的文章,就不想在贴参数图了,有兴趣的自己去分析一下),这里像是对Base64编码的字符集做了一些操作,然后对传进来的a1和a2数据做了一些处理,这里的v23就是a1,就是我们想要的返回值
在这里插入图片描述

11,我照着这个还原了一下python

    def sub_29E90(self,a1, a2, a3):
        v5 = a1  
        v17 = 0
        v23 = []
        i = 0
        while True:
            if v17 > a3 - 2:
                break
            v23 = v5
            v24 = v17
            v23[i * 4 + 0] = aAbcdefghijklmn[(a2[v24] >> 2) & 0x3F]
            # v23[1] 使用位运算和 Base64 字符集映射
            v6 = v24 + 1
            v23[i * 4 + 1] = aAbcdefghijklmn[((a2[v6] >> 4) & 0x3F) | (16 * (a2[v24] & 3))]
            # 获取新的字节 v7 和 v8
            v7 = a2[v6]
            v8 = v24 + 2
            # 更新 v23 数组
            v23[i * 4 + 2] = aAbcdefghijklmn[((a2[v8] >> 6) & 0x3F) | (4 * (v7 & 0xF))]
            v23[i * 4 + 3] = aAbcdefghijklmn[(a2[v8] ^ 0xC0) & a2[v8]]
            # 更新 v17
            v17 = v24 + 3
            i += 1
        return ''.join(v23)

12.接着分析size_4,很明显来源于sub_2E91C(v62, size_4, size_4, v56);函数,这里就很明显了,是对a1和a2得值进行了一系列或与操作,赋值给了a3也就是size_4
在这里插入图片描述
13 照常还原就行,因为我菜,真正分析的时候还是要hook数据进行比对,需要费点时间多次尝试了,直接拿gpt给的还原用,多次改明文尝试a1目前来看是固定的(这里我也好奇,没看见a1是在哪里赋值的,就看见初始化了。有大佬知道的话希望告知一下,其实a1在这里还不能固定,后面再说,记住这里是分析了2E91C这个函数),a2,a3是一样的,a4是长度,所以后面要分析a2了
在这里插入图片描述
在这里插入图片描述

   def sub_2E91C(self,result, a2, a3, a4):
        v4 = 0
        while True:
            v10 = -1033944663
            while v10 != 491288569:
                if v10 == -344237315:
                    # 执行字节操作
                    v5 = (result[257]) & 0xFF
                    v6 = (result[256] + 1) & 0xFF
                    result[256] = v6 & 0xFF
                    v7 = (v5 + (result[v6] & 0xFF)) & 0xFF
                    result[257] = v7
                    v8 = result[v6] & 0xFF
                    result[v6] = result[v7]
                    result[v7] = v8 & 0xFF
                    v9 = result[(result[result[257]] + result[result[256]]) & 0xFF]
                    a3[v4] = ((~v9 & 0x7B | v9 & 0x84) ^ (~a3[v4] & 0x7B | a3[v4] & 0x84))
                    # print(a3.hex())
                    # print(hex(v9))
                    if a3.hex().find('e4b778b3ef05c08093c78d68') > -1:
                        pass
                    v4 += 1
                    if v4 >= a4:
                        v10 = 491288569
                else:
                    if v4 >= a4:
                        v10 = 491288569
                    else:
                        v10 = -344237315

            if v10 == 491288569:
                break
        return result

14.a2的后按x键继续向上找,发现了 sub_1D444函数很可疑,点过去看看,看起来很像
在这里插入图片描述
在这里插入图片描述

15 经过断点hook验证,确实是这个地方对a1做了处理,然后返回的a2就是我们要的结果,这里的话,我就直说了,a1是传进来的明文str1,a2是申请的内存空间存放返回结果,a3是明文长度,a4是最大的压缩长度,先是对a1进行了lz4压缩算法,然后在填充固定值返回的a2,lz4算法后面跟踪发现函数太大打不开了,我改了
ida的max_function的值就会崩溃,所以尝试用python的lz4算法,发现和c运行的结果不一致,可能需要到github找到lz4算法源码进行编译,尝试在运行看看是否一致,因为我的c基础不行,就先不弄了,有大佬有兴趣了可以去尝试
在这里插入图片描述

16.到上面我当时以为就分析结束了,就不管了,后来分析了nativeSignature函数的时候有了意外发现,这个后面再说,篇幅太长了,后面我说快点
17.我们接着来到0x21864这个函数去看加密,先主动调用一下

 public void sign(){
        //这里的参数是前面hook java层得到的
        String str1 = "sp";
        String str2 = "5Ld4s-8FwICTx41oreQzXrOQ8ZW6jB1TNEhFs1f-O6e6w1oxuIMlXFJg7rcP5zsEP7js8mV2Lzs3qmDmaNi4ZTaU0LEBh7Bt1wck0OdTDh59YMIicVU-gnnbTjMBEVJbmtez21QdJxrD6kpMhC4Z9TGaaX03GA1nIq1avlufhEcelG_qO4GykesoPkw8G7U2yso9DLcvzla9P2LuD4F0BlpkAjcpxGJwGkDL9LhR52-AUrDEJI_8sCxe7jIjTiH4ReJkMeoDt4NSEqaUzjDsvHz_U5A8xaoIwbO7Hbj4IY3HFsHBJsoGTXzHs36y2sBv1PYkeO8XuUbuTKnMaWt0SDTCiE4GZ_8dSgYKghqGm3S1gbgtQkSy-pqFtKfLZV1qNvIO1yOROgIeJVE-OVAM12fVe4VAUzGq3NrU7jrIQLhEExom1p761emhP37pyt-vdWZY6InLZKhNrni58339R5uOOt-hcGiD9N1Q7axM1p41iksXsIMmybC2gAtsRBmN25NL56Y6eOMkcA0lWGOin77b3HbG8Ea3_FRjJWWKu41IWnpEuX5VAF4WDndNuNeOqJcn9cGPZ1RvbtIz9SrMrBzklcPLZ0olq_tUWufNOGjEnIHdld6XORVGf-pbOXYGhIaW3DQ73X9rD2NY-KvIENO8SpnS49n9GYr59b2gAQkNz1pF-i9yXA2AD8jPsa48A3ET84cGBO6xaBmNrZVxYYFoMSDIt6ic66-3BFOS7kVeHHhqMFsjcfdDF5VvgypozZpfnfMpRs22MhAHpm2LWuS-5V45I7emCB1zsLDSq2dp";
        //str2="hello";
        System.out.println("长度2:"+str2.length());
        try {
            byte[] bytes = str1.getBytes("UTF-8");
            DvmObject ret = CheckCodeUtil.callStaticJniMethodObject(emulator, "nativeSignature([BLjava/lang/String;)[B",bytes,str2);
            byte[] strOut = (byte[])ret.getValue();
            String s = new String(strOut);
            System.out.println(s);
        }catch (Exception e){
        }
    }

18.返回值看着像md5,虽然长度不是,猜测很可能是拼接出来的,通过静态分析到了v27这里,返回值就是加密,hook对应的入参发现不止一个有1个,发现2个参数拼接起来就是返回值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
19,接着往上分析,sub_1C714(dest, v40)函数,看了一下mx0,好家伙就是我们的str+固定值+str2,接着我就没往下看了,直接md5,果然和想的一样是标准md5加密,到此分析结束。
在这里插入图片描述
20.分析MD5的时候发现用了str2,但是上面那个算法好像没用上感觉有点奇怪,正常应该会用上,改了一下str2的入参(之前一直改str1入参尝试的),果然有一个地方的值就不对了。经过分析发现sub_2E91C就是这里的a1不是之前想的固定值,所以继续回来分析
21。其实这里就发现了有一个sub_2E680函数,通过hook参数发现,s就是盐值+str2,v62我还是没发现从哪里给的值,改了str1和str2的入参v62都是固定的,v53是长度,接下来就是扣2E680的算法了
在这里插入图片描述
22.我这里直接给gpt生成的算法,我也懒的优化了,到这里和之前的上文2E91C的a1固定值替换上就ok了

    def sub_2E680(self,result, a2, a3):
        x = 0
        v5 = ~((x - 1) * x) | 0xFFFFFFFE  # Equivalent of ~((_BYTE)x - 1) * (_BYTE)x | 0xFFFFFFFE
        y = 0
        v10 = y < 10  # Assuming 'y' is some global variable (not defined in the function)
        v6 = 1857882829
        v9 = v5 == -1
        v12 = 0  # Assuming initial value of v12
        v13 = 0  # Assuming initial value of v13
        v4 = 0  # Assuming initial value of v4
        while True:
            while True:
                while True:
                    while v6 > -490188628:
                        if v6 <= 1562097807:
                            if v6 == -490188627:
                                v8 = result[v12] & 0xFF
                                v3 = (a2[(v12 % a3)] + v8 + v13) & 0xFF
                                result[v12] = result[v3] & 0xFF
                                result[v3] = v8
                                v6 = 1644006132
                                v4 = v12 + 1

                            else:
                                v5 = 0
                                v6 = -2137814577
                        elif v6 == 1562097808:
                            v7 = (((x - 1) * x) ^ 0xFFFFFFFE) & ((x - 1) * x) == 0
                            if (y < 10 and v7) or (y < 10) ^ v7:
                                v6 = 1400577677
                            else:
                                v6 = -1905400192
                        elif v6 == 1644006132:
                            v12 = v4
                            v13 = v3
                            if v4 >= 256:
                                v6 = -1783888654
                            else:
                                v6 = -490188627
                        elif ((not v9) ^ (not v10)) & 1 or (v9 and v10):
                            v6 = 1562097808
                        else:
                            v6 = -1905400192
                    if v6 > -1879526958:
                        break
                    if v6 == -2137814577:
                        v11 = v5
                        if v5 >= 256:
                            v6 = -1375567831
                        else:
                            v6 = -1879526957
                    else:
                        v6 = 1562097808
                if v6 != -1879526957:
                    break
                result[v11] = v11
                v6 = -2137814577
                v5 = v11 + 1
            if v6 != -1375567831:
                break
            v4 = 0
            v3 = 0
            result[256] = 0
            v6 = 1644006132
        return result.hex()

23.EncodeRequest算法总结:
第1步:固定值和(入参+盐值)进行运算
第2步lz4压缩算法:
第3步:第一步的值和lz4算法的值进行异或
第4步:对第三步的值进行base64编码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值