某菠萝小说trace还原sign(一)

感谢好兄弟Lane 提供讲解服务 此文章目的为复现。

目标:nonce=89BD9D8D-69A6-474E-8F46-7CC8796ED151&timestamp=1731047246145&devicetoken=910D166A-736E-3231-8B21-8D12DFD75F16&sign=9A2DFE01283DF2583E06643E1AAB7216

这个中的sign值

一、unidbg搭架子

package com.fan.qing;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.Arm64RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
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.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.utils.Inspector;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class qing2 extends AbstractJni implements IOResolver {
    @Override
    public FileResult resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("pathName:" + pathname);
        if (pathname.equals("/proc/self/maps")) {
            return FileResult.success(new SimpleFileIO(oflags, new File("apks/FCZ/maps"), pathname));
        }
        return null;
    }

    public static AndroidEmulator emulator;
    public static Memory memory;
    public static VM vm;
    public static DalvikModule dm;
    public static Module module;
    public void traceCode(){
        // 持久化写入 tracecode.txt
        String traceFile = "apks/qing/tracecode.txt";
        PrintStream traceStream;
        try {
            traceStream = new PrintStream(new FileOutputStream(traceFile), true);
            emulator.traceCode(module.base,module.base+module.size).setRedirect(traceStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public qing2() {
        // 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
        emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("vz.com").build();

        // 2.获取内存对象(可以操作内存)
        memory = emulator.getMemory();

        // 3.设置安卓sdk版本(只支持19、23)
        memory.setLibraryResolver(new AndroidResolver(23));

        // 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样)
        vm = emulator.createDalvikVM(new File("apks/qing/xiaoshuo.apk"));
        vm.setJni(this); // 后续设置补环境
        vm.setVerbose(true); //是否展示调用过程的细节

        emulator.getSyscallHandler().addIOResolver(this);

        // 5.加载so文件
        DalvikModule dm = vm.loadLibrary("sfdata", true);

        dm.callJNI_OnLoad(emulator);

        // 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
        module = dm.getModule();

        // append 函数
        emulator.attach().addBreakPoint(module.base + 0x7A980, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                Arm64RegisterContext rc = emulator.getContext();
                System.out.println("[0x7A980] ->"+rc.getLRPointer());
//                Arm64RegisterContext rc = emulator.getContext();
                UnidbgPointer ptr1 = rc.getPointerArg(0);
                UnidbgPointer ptr2 = rc.getPointerArg(1);
                int len = rc.getIntArg(2);

                byte[] sourcebarr = ptr2.getByteArray(0,len);
                String source = new String(sourcebarr);
                System.out.println("sss: " + Arrays.toString(sourcebarr));
                System.out.println("ssss: " + source);
//                traceCode();
                return true;
            }
        });
        emulator.attach().addBreakPoint(module.base + 0x166A0, new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                Arm64RegisterContext rc = emulator.getContext();

//                traceCode();
                return true;
            }
        });

        emulator.attach().addBreakPoint(module.findSymbolByName("memcpy").getAddress(), new BreakPointCallback() {
            @Override
            public boolean onHit(Emulator<?> emulator, long address) {
                System.out.println("memcp hit");
                Arm64RegisterContext rc = emulator.getContext();
                UnidbgPointer ptr1 = rc.getPointerArg(0);
                UnidbgPointer ptr2 = rc.getPointerArg(1);
                int len = rc.getIntArg(2);

                byte[] sourcebarr = ptr2.getByteArray(0,len);
                String source = new String(sourcebarr);
                System.out.println("memcp: lr:" + rc.getLRPointer() + " 目标数组->  " + ptr1 + "  数据源->" + ptr2 + "  len->" + len);
                System.out.println("memcp: sourcebarr" + Arrays.toString(sourcebarr));
                System.out.println("memcp: source =" + source);

                if (len == 160){
                    System.out.println("start trace");
                }
                return true;
            }
        });

    }
    public static String stringToHexString(String s) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            int ch = s.charAt(i);
            String s4 = Integer.toHexString(ch);
            str.append(s4);
        }
        return str.toString();
    }
    public String getSFsecurity(){
//        traceCode();// 0x3631323762616131
        Logger.getLogger(DalvikVM64.class).setLevel(Level.DEBUG);
        List<Object> list = new ArrayList<>(10);
        list.add(vm.getJNIEnv()); // 第一个参数是env
        list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到
        Object custom = null;
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(custom);// context
        list.add(vm.addLocalObject(context));
        list.add(vm.addLocalObject(new StringObject(vm, "910D166A-736E-3231-8B21-8D12DFD75F16")));
        int intValue = 0x3;
        list.add(intValue);
        Number number = module.callFunction(emulator, 0x2728C, list.toArray());
        String result = vm.getObject(number.intValue()).getValue().toString();
        Inspector.inspect(result.getBytes(),"aaa");
        System.out.println(result);
        return result;
    }

    public static void main(String[] args) {
        qing2 result = new qing2();
        result.getSFsecurity();
    }
}

二、从trace还原算法

此样本中的ollvm混淆很恶心,所以才选择在汇编中还原算法,在做算法还原之前之前我们已经固定了输出结果。

首先我们上面提供了traceCode函数,我们可以去trace 得到一份汇编指令流程代码。并使用010去打开他。

接着我们可以观察我们的sign是32位 那么当然首选肯定是md5呀(猜测)。

1.初步分析

借用龙哥的文章的话:”在 trace 日志里搜索 K 的表的第一个数 0xd76aa478,对于 MD5,我们一般不通过 IV 来确认是否是它,因为 SHA1 等算法也沿用了 MD5 的 IV,不具备唯一标识性。“

我们看到了两组结果而且他们的位置很接近,我们可以想到是md5 将填充后的数据分成多个 512 位(64 字节)的块。对于 1024 比特的数据,共有 2 个数据块。

对每个数据块进行四轮计算,每轮包含 16 次操作,共 64 次操作。每次操作的形式为:

a = b + ((a + F(b, c, d) + X[k] + T[i]) <<< s)

md5算法部分源码:

FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */
#define FF(a,b,c,d,x,s,ac) \
          { \
          a += F(b,c,d) + x + ac; \
          a = ROTATE_LEFT(a,s); \
          a += b; \
          }

// 公式:
          a += F(b,c,d) + x + ac;
          a = a + F(b,c,d) + x + ac;
		  ac = 0xd76aa478
		  a = 0x67452301
		  F(b,c,d) = 0x98badcfe
		  a+F(b,c,d) = 0xffffffff

接下来 我们从trace中看到了

[libsfdata.so 0x173b4] [0801030b] 0x400173b4: "add w8, w8, w3" w8=0x50373735 w3=0xd76aa478 => w8=0x27a1dbad这条指令,所以 0x50373735 = a + F(b,c,d) + x , a = 0x67452301 ,F(b,c,d) = 0x98badcfe,所以x = 0x50373736;

提示:负数通过模 232 运算被映射到 0 到 232−1 的范围内,最终结果为 1346855990(即 0x50373736)。

接下来跟0x50373736 这个值:

是从这个0xbfffecc0 这里加载的 所以我们去看看这个0xbfffecc0是什么。

unidbg中下断点:

第一次

m0xbfffecc0

>-----------------------------------------------------------------------------<
[21:53:47 035]unidbg@0xbfffecc0, md5=80af3d1b53c5917b13544c40006ca36e, hex=363737505143385033365242333132373643383845365039365142373236364f4442364e32363034394e373436364e4f335131324d34374f423350355039313610f045400000000010000000000000000900000000000000010000000000000020f04540000000000000000000000000
size: 112
0000: 36 37 37 50 51 43 38 50 33 36 52 42 33 31 32 37    677PQC8P36RB3127
0010: 36 43 38 38 45 36 50 39 36 51 42 37 32 36 36 4F    6C88E6P96QB7266O
0020: 44 42 36 4E 32 36 30 34 39 4E 37 34 36 36 4E 4F    DB6N26049N7466NO
0030: 33 51 31 32 4D 34 37 4F 42 33 50 35 50 39 31 36    3Q12M47OB3P5P916
0040: 10 F0 45 40 00 00 00 00 10 00 00 00 00 00 00 00    ..E@............
0050: 09 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00    ................
0060: 20 F0 45 40 00 00 00 00 00 00 00 00 00 00 00 00     .E@............
^-----------------------------------------------------------------------------^

第二次:

m0xbfffecc0

>-----------------------------------------------------------------------------<
[21:54:30 977]unidbg@0xbfffecc0, md5=2f359eff5924c3506707d2b015696619, hex=39344230374e4e4232524f33425044433736523350514a4a4d4b354f42485041485157515180000000000000000000000000000000000000280300000000000010f045400000000010000000000000000900000000000000010000000000000020f04540000000000000000000000000
size: 112
0000: 39 34 42 30 37 4E 4E 42 32 52 4F 33 42 50 44 43    94B07NNB2RO3BPDC
0010: 37 36 52 33 50 51 4A 4A 4D 4B 35 4F 42 48 50 41    76R3PQJJMK5OBHPA
0020: 48 51 57 51 51 80 00 00 00 00 00 00 00 00 00 00    HQWQQ...........
0030: 00 00 00 00 00 00 00 00 28 03 00 00 00 00 00 00    ........(.......
0040: 10 F0 45 40 00 00 00 00 10 00 00 00 00 00 00 00    ..E@............
0050: 09 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00    ................
0060: 20 F0 45 40 00 00 00 00 00 00 00 00 00 00 00 00     .E@............
^-----------------------------------------------------------------------------^

28 03就是数据的长度

根据 MD5 填充规则,数据的长度会被附加在填充后的数据的末尾,以 64 位(8 字节)表示。

28 03 是以小端序(Little-Endian)表示的长度值。将其转换为十进制:

0x0328 = 808

因此,数据的原始长度为 808 比特(即 101 字节)。

验证:677PQC8P36RB31276C88E6P96QB7266ODB6N26049N7466NO3Q12M47OB3P5P91694B07NNB2RO3BPDC76R3PQJJMK5OBHPAHQWQQ md5 后确实是sign 9A2DFE01283DF2583E06643E1AAB7216

2.继续分析

那么这一串值是怎么来的继续010走起:

现在我们是找到sign的入参了但是入参二点明文我们并不知道如何来的。

接下来我们可以使用龙哥的DataSearch( 内存数据搜索工具)去寻找

0000: 36 37 37 50 51 43 38 50 33 36 52 42 33 31 32 37 677PQC8P36RB3127 明文前16个字节第一次生成的位置:

匹配到

Search matches: RW@0x40453090

继续traceWrite emulator.traceWrite(0x40453090L,0x40453090L + 15);先看前16个字节是如何生成的~

[22:07:29 065] Memory WRITE at 0x40453090, data size = 1, data value = 0x36, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453091, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453092, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453093, data size = 1, data value = 0x3d, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453094, data size = 1, data value = 0x3e, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453095, data size = 1, data value = 0x43, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453096, data size = 1, data value = 0x38, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453097, data size = 1, data value = 0x3d, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453098, data size = 1, data value = 0x33, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x40453099, data size = 1, data value = 0x36, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309a, data size = 1, data value = 0x3f, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309b, data size = 1, data value = 0x42, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 065] Memory WRITE at 0x4045309c, data size = 1, data value = 0x33, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309d, data size = 1, data value = 0x31, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309e, data size = 1, data value = 0x32, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309f, data size = 1, data value = 0x37, PC=RX@0x4002b1cc[libsfdata.so]0x2b1cc, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453093, data size = 1, data value = 0x50, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453094, data size = 1, data value = 0x51, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x40453097, data size = 1, data value = 0x50, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc
[22:07:29 066] Memory WRITE at 0x4045309a, data size = 1, data value = 0x52, PC=RX@0x4002bd5c[libsfdata.so]0x2bd5c, LR=RX@0x4002a9dc[libsfdata.so]0x2a9dc

前16个字节的位置在0x2b1cc这个地方,010看一下

恰好是101个字节也就是我们的md5明文的入参长度。

全部复制到cyberChef

是这么个玩意,也就是说我们的md5入参是由26049;7466;3QJJ:K5<BH=.HQW>>12:47<B3=5=91694/07;;/2?<3/=DC76?3=677=>C8=36?B31276C88E6=-6>B7266<D/6; 变化而来的。

我们现在的公式可以这样写:

另 x2 = 26049;7466;3QJJ:K5<BH=.HQW>>12:47<B3=5=91694/07;;/2?<3/=DC76?3=677=>C8=36?B31276C88E6=-6>B7266<D/6;

另 x1 = 677PQC8P36RB31276C88E6P96QB7266ODB6N26049N7466NO3Q12M47OB3P5P91694B07NNB2RO3BPDC76R3PQJJMK5OBHPAHQWQQ

sign = MD5(x1); x1 = 某算法1(x2);

那么这个x2是什么呢我们接着分析:

下篇文章接着分析ing

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值