某短视频平台最新sig3字段算法分析(二)

部署运行你感兴趣的模型镜像

算法分析

紧接着上一篇,我们看下正常运行的结果

trace一下 call_doCommandNative_sig3

private void trace2file() {
    String traceFile = "unidbg-android/src/test/resources/app/trace/trace.txt";
    PrintStream traceStream = null;
    try{
        traceStream = new PrintStream(new FileOutputStream(traceFile), true);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);
}

在tace.txt中搜索f1e7,e7f1发现没有匹配数据,改搜索e7,从匹配的trace文件从最底部往最前查找可疑点会发现

从w1=0xa6接着往上分析

接着双击x21往上找

双击x0往上找

双击0xbffff470往上找

IDA定位也可以看到代码

就一个简单算法,求和,取反,异或对照着直接译出来即可。算法输入参数就是v374,结合trace文件,我们可以打印下v374的0至23位,并找到对应的赋值点并编上号便于后面进行分析

0x41   "str w11, [sp, #0x170]" w11=0x5141 sp=0xbffff470 => w11=0x5141 ①
0x51   "str w11, [sp, #0x170]" w11=0x5141 sp=0xbffff470 => w11=0x5141    
0x27   "strh w8, [sp, #0x172]" w8=0x27 sp=0xbffff470 => w8=0x27 ②
0x0    "strh w8, [sp, #0x172]" w8=0x27 sp=0xbffff470 => w8=0x27
0x60   "str w8, [sp, #0x174]" w8=0x4dfa1c60 sp=0xbffff470 => w8=0x4dfa1c60  ③
0x1c   "str w8, [sp, #0x174]" w8=0x4dfa1c60 sp=0xbffff470 => w8=0x4dfa1c60
0xfa   "str w8, [sp, #0x174]" w8=0x4dfa1c60 sp=0xbffff470 => w8=0x4dfa1c60 
0x4d   "str w8, [sp, #0x174]" w8=0x4dfa1c60 sp=0xbffff470 => w8=0x4dfa1c60 
0x1    "str w10, [sp, #0x178]" w10=0x1 sp=0xbffff470 => w10=0x1 ④
0x0    "str w10, [sp, #0x178]" w10=0x1 sp=0xbffff470 => w10=0x1 
0x0    "str w10, [sp, #0x178]" w10=0x1 sp=0xbffff470 => w10=0x1 
0x0    "str w10, [sp, #0x178]" w10=0x1 sp=0xbffff470 => w10=0x1 
0x61   "str w0, [sp, #0x17c]" w0=0xb03a661 sp=0xbffff470 => w0=0xb03a661 ⑤
0xa6   "str w0, [sp, #0x17c]" w0=0xb03a661 sp=0xbffff470 => w0=0xb03a661  
0x03   "str w0, [sp, #0x17c]" w0=0xb03a661 sp=0xbffff470 => w0=0xb03a661
0x0b   "str w0, [sp, #0x17c]" w0=0xb03a661 sp=0xbffff470 => w0=0xb03a661
0xac   "str w8, [sp, #0x180]" w8=0x676007ac sp=0xbffff470 => w8=0x676007ac ⑥
0x7    "str w8, [sp, #0x180]" w8=0x676007ac sp=0xbffff470 => w8=0x676007ac
0x60   "str w8, [sp, #0x180]" w8=0x676007ac sp=0xbffff470 => w8=0x676007ac
0x67   "str w8, [sp, #0x180]" w8=0x676007ac sp=0xbffff470 => w8=0x676007ac 
0x0    "str w8, [sp, #0x184]" w8=0xe7000d00 sp=0xbffff470 => w8=0xe7000d00 ⑦
0xd    "str w8, [sp, #0x184]" w8=0xe7000d00 sp=0xbffff470 => w8=0xe7000d00
0x0    "str w8, [sp, #0x184]" w8=0xe7000d00 sp=0xbffff470 => w8=0xe7000d00
0xe7   "str w8, [sp, #0x184]" w8=0xe7000d00 sp=0xbffff470 => w8=0xe7000d00

①比较明显就是设置的固定值,我们从②开始分析

可以比较明显的发现和0x404d8220地址有关系,直接trace一下

emulator.traceWrite(0x404d8228,0x404d8228 + 4);

定位到的位置是callJNI_OnLoad的时候操作的,所以也是固定值。

同样的追查流程找下③④也都是固定值,不想追流程其实也可以直接修改入参,这几个标注段基本都不会改变与入参无关。现在我们看下⑤直接定位到IDA赋值位置看一下

点进函数sub_120d8内部没有发现明显特征,转回来看下入参就会发现unk_56188有crc32的明显特征

百度或谷歌搜一下0x04c11db7

确定是CRC32之后我们在看一下另外两个入参

大概率能猜出是数据跟长度,拿出去试算一下

试算结果符合我们的猜测是标准的CRC32,在回过头来看下算法,分析trace会发现

[09:34:01 131][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c11db7 w11=0x0 => w8=0x1
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x2608edb w11=0x1 => w8=0x3
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x130476d w11=0x3 => w8=0x7
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x9823b6 w11=0x7 => w8=0xe
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c11db w11=0xe => w8=0x1d
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x2608ed w11=0x1d => w8=0x3b
[09:34:01 132][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x130476 w11=0x3b => w8=0x76
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x9823b w11=0x76 => w8=0xed
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c11d w11=0xed => w8=0x1db
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x2608e w11=0x1db => w8=0x3b6
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x13047 w11=0x3b6 => w8=0x76d
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x9823 w11=0x76d => w8=0xedb
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c11 w11=0xedb => w8=0x1db7
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x2608 w11=0x1db7 => w8=0x3b6e
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x1304 w11=0x3b6e => w8=0x76dc
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x982 w11=0x76dc => w8=0xedb8
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c1 w11=0xedb8 => w8=0x1db71
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x260 w11=0x1db71 => w8=0x3b6e2
[09:34:01 133][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x130 w11=0x3b6e2 => w8=0x76dc4
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x98 w11=0x76dc4 => w8=0xedb88
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4c w11=0xedb88 => w8=0x1db710
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x26 w11=0x1db710 => w8=0x3b6e20
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x13 w11=0x3b6e20 => w8=0x76dc41
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x9 w11=0x76dc41 => w8=0xedb883
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x4 w11=0xedb883 => w8=0x1db7106
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x2 w11=0x1db7106 => w8=0x3b6e20c
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x1 w11=0x3b6e20c => w8=0x76dc419
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x0 w11=0x76dc419 => w8=0xedb8832
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x0 w11=0xedb8832 => w8=0x1db71064
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x0 w11=0x1db71064 => w8=0x3b6e20c8
[09:34:01 134][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x0 w11=0x3b6e20c8 => w8=0x76dc4190
[09:34:01 135][libkwsgmain.so 0x0120f8] [68791f33] 0x400120f8: "bfi w8, w11, #1, #0x1f" w8=0x0 w11=0x76dc4190 => w8=0xedb88320

通过0x4c11db7 算出0xedb88320,然后采用直接计算法计算CRC32值,下面是直接计算法部分的截图

分析完第一个算法我们接着往后,trace一下入参地址0x404ff000

emulator.traceWrite(0x404ff000,0x404ff000 + 0x30);

结合trace文件定位一下发现是memcpy

我们继续trace地址 0x404d3330

emulator.traceWrite(0x404d3330, 0x404d3330 + 0x30);

根据地址可以定位到函数

再看一下函数体,找到可以看一下的数据可疑点

打个断点看一下

debugger.addBreakPoint(module.base+0x25ad0);

大小端切换搜一下,最后搜0x05040706

搜到的项中有这么一项,应该不是明显的特征值,暂且先记录一下。我们在看一下函数输入参数

debugger.addBreakPoint(module.base + 0x262e8);

pkcs7填充,简单调试下会发现总共调用sub_25980三次,每次16个字节,一般这么玩的也就aes和sm4,sm4在app参数加密中用的比较少,结合前面的搜索结论大概率是aes,如果是aes还需要判断是ecb还是cbc,从入参看并没有看到类似的iv向量之类的,但是我们还是做一个试验看看,输入三组16字节数据

debugger.addBreakPoint(module.base + 0x262e4, new BreakPointCallback() {
    int count = 0;
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        byte[] data = {(byte)0xA8, 0x0D, (byte)0xAA, 0x37, (byte)0xD0, 0x3E, (byte)0xD5, (byte)0xAB, (byte)0xF0, (byte)0x86, (byte)0xA5, 0x65, 0x3F, 0x74, (byte)0xB8, 0x7F,
                (byte)0xA8, 0x0D, (byte)0xAA, 0x37, (byte)0xD0, 0x3E, (byte)0xD5, (byte)0xAB, (byte)0xF0, (byte)0x86, (byte)0xA5, 0x65, 0x3F, 0x74, (byte)0xB8, 0x7F,
                (byte)0xA8, 0x0D, (byte)0xAA, 0x37, (byte)0xD0, 0x3E, (byte)0xD5, (byte)0xAB, (byte)0xF0, (byte)0x86, (byte)0xA5, 0x65, 0x3F, 0x74, (byte)0xB8, 0x7F};
        if (count == 0)
            emulator.getBackend().mem_write(0x404d32d0, data);
        count++;
        return true;
    }
});

判断是ecb应该没错了,看了下其他几个入参并没有类似key的参数,怀疑是白盒aes,如果是白盒aes我们就需要找state块了。我们看一下函数部分代码,主要逻辑部分也就下面两个块

第一部分执行10次,第2部分执行9次,有点类似查表法的逻辑(字节转换 行移位 列混合),
第一部分中sub24f48函数内容像是在变动赋值

我们在这个函数执行前后设置断点打印下,就能明显的看到移位特征了

debugger.addBreakPoint(module.base+0x24f90);
debugger.addBreakPoint(module.base+0x25e24);

a8 d0 f0 3f
0d 3e 86 74
aa d5 a5 b8
37 ab 65 7f

a8 d0 f0 3f
3e 86 74 0d
a5 b8 aa d5
7f 37 ab 65

找到了state接下来就是dfa攻击了,先修改一位试一下

debugger.addBreakPoint(module.base + 0x24fa4, new BreakPointCallback() {
    int count = 1;
    int count2 = 0;
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        byte[] data= {0x1};
        if (count % 9 == 0) {
            data[0] = (byte) getRandomNumber(0, 255);
            emulator.getBackend().mem_write(0x404d32d0 + count2 * 0x10 + getRandomNumber(0, 15), data);
        }
        count++;
        if (count == 11) {
            count = 1;
            count2++;
        }
        return true;
    }
});

debugger.addBreakPoint(module.base + 0x2601c, new BreakPointCallback() {
    int count = 0;
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        byte[] data = emulator.getBackend().mem_read(0x404d32d0 + count * 16, 16);
        printByteArray(data);
        System.out.print("\n");
        count++;
        return true;
    }
});

前后数据对比,很明显第1,8 ,11, 14个字节不同,符合注入特征

现在修改为批量随机注入

debugger.addBreakPoint(module.base + 0x24fa4, new BreakPointCallback() {
    int count = 1;
    int count2 = 0;
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        byte[] data= {0x1};
        if (count % 9 == 0) {
            data[0] = (byte) getRandomNumber(0, 255);
            emulator.getBackend().mem_write(0x404d32d0 + count2 * 0x10 + getRandomNumber(0, 15), data);
        }
        count++;
        if (count == 11) {
            count = 1;
            count2++;
        }
        return true;
    }
});

批量跑完后,将存在文件中的数据用phoenixAES和aes_keyschedule处理下,具体可以网上搜索下用法,这里就不介绍了

算出key我们验证一下

白盒aes验证完了,现在我们继续往后,trace一下入参地址

emulator.traceWrite(0x404d32d0,0x404d32d0 + 0x20);

按前面的分析方法一级一级分析最后会定位到

加上断点看一下

结合IDA可以定位加密函数

查找下函数的内容,会发现特征点

很明显0x6a09e667为sha256特征,搜索一下

在看下函数入参

debugger.addBreakPoint(module.base+0x21780);

大致调试一下可以得出结论a1未知,a2为输出,a3为输入HandsomeBro,a4为长度,a5、a6未知,按sha256试算一下发现明显对不上

不是sha256我们在看下入参未知的那几个参数看看,很明显从a5,a6入参可以找到hmac的影子

我们将hmac的key异或一下0x5c或0x36算出来,拿去试算一下结果,发现还是不匹配,没有办法只能分析源码了,直接看伪C代码不难发现sha256这样的计算块有多个

结合trace代码会发现总共跑了四次

先从第一次sha256的开始分析,入参位置

我们设置断点显示打印一下

debugger.addBreakPoint(module.base+0x21934);

其实就是入参a5,直接看伪c也能分析出来,接着断点看一下第一次计算结果

debugger.addBreakPoint(module.base+0x21b1c);

接下来看是计算结果是不是标准结果,但是这个是没办法直接拿到网上试算的,因为网上试算的都是补全后的计算值,需要我们整个标准的demo试算一下,我这边从网上扒了一个

# 常数初始化
H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
K = [
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
 
# 将输入的文本转换为二进制
def turn_binary(message):
    binary = message.encode('utf8')
    return binary
 
# 填充二进制编码
def padding_binary(binary):
    l = len(binary) * 8
    k = (448 - (l + 8)) % 512
    if k == 0:
        k = 512
    k = k // 8
    M = binary + b'\x80' + b'\x00' * k + l.to_bytes(8, byteorder='big')
    # print(f"M:{M.hex()}")
    return M
 
# X右移n位
def ROTR(x, n):
    x = (x >> n) | (x << 32 - n)
    return x
 
def cycle(M):
    a = H[0]
    b = H[1]
    c = H[2]
    d = H[3]
    e = H[4]
    f = H[5]
    g = H[6]
    h = H[7]
    W = [0] * 64
    for t in range(0, 16):
        W[t] = M[t * 4:t * 4 + 4]
        # 将W记成16进制
        W[t] = int(W[t].hex(), 16)
    for t in range(16, 64):
        # ^是异或运算
        S1 = ROTR(W[t - 2], 17) ^ ROTR(W[t - 2], 19) ^ (W[t - 2] >> 10)
        S0 = ROTR(W[t - 15], 7) ^ ROTR(W[t - 15], 18) ^ (W[t - 15] >> 3)
        # 运算时超过8位16进制数字,为去掉8位前多余的数值进行压缩 & 0xFFFFFFFF
        W[t] = (S1 + W[t - 7] + S0 + W[t - 16]) & 0xFFFFFFFF
    # print(f"W:{W}")
    for t in range(0, 64):
        S1 = ROTR(e, 6) ^ ROTR(e, 11) ^ ROTR(e, 25)
        Ch = (e & f) ^ ((~e) & g)
        S0 = ROTR(a, 2) ^ ROTR(a, 13) ^ ROTR(a, 22)
        Maj = (a & b) ^ (a & c) ^ (b & c)
        T1 = h + S1 + Ch + K[t] + W[t]
        T2 = S0 + Maj
        h = g
        g = f
        f = e
        e = (d + T1) & 0xFFFFFFFF
        d = c
        c = b
        b = a
        a = (T1 + T2) & 0xFFFFFFFF
    # ha = (a, b, c, d, e, f, g, h)
    # for ha in ha:
    #     print(hex(ha))
    # input('按enter继续……')
 
    H[0] = a + H[0] & 0xFFFFFFFF
    H[1] = b + H[1] & 0xFFFFFFFF
    H[2] = c + H[2] & 0xFFFFFFFF
    H[3] = d + H[3] & 0xFFFFFFFF
    H[4] = e + H[4] & 0xFFFFFFFF
    H[5] = f + H[5] & 0xFFFFFFFF
    H[6] = g + H[6] & 0xFFFFFFFF
    H[7] = h + H[7] & 0xFFFFFFFF
 
def encode(M):
    print("————————加密过程————————")
    for i in range(0, len(M) // 64):
        cycle(M[i * 64: i * 64 + 64])
        print(f"第{i + 1}次迭代,H:{H}")
        for sha in H:
            print(f"{hex(sha)}")
 
# 正式开始运行!
print("————————SHA256————————")
#message = input('请输入需要加密的信息:')
byte = [0x5E,0x58,0x58,0x57,...,0x02,0x05,0x4C,0x7A,
        0x5C,0x75,0x64,0x44,...,0x74,0x41,0x02,0x77,
        0x59,0x0E,0x78,0x46,...,0x65,0x43,0x53,0x62,
        0x5B,0x79,0x7C,0x72,...,0x04,0x58,0x5D,0x66]
byte_array = bytearray(byte) 
message = byte_array.decode('utf-8')
print('————————加密准备————————')
# 将文本转换为二进制表示
binary_message = turn_binary(message)
padded_message = padding_binary(binary_message)
# 输出中间结果
print("原始文本:", message)
print("二进制表示:", binary_message)
print("填充后二进制字符串:", padded_message)
# 加密
encode(padded_message)
 
sha256 = ''
print("————————加密验证————————")
print("H十六进制表示:")
for sha in H:
    print(f"{hex(sha)}")
    sha256 = sha256 + sha.to_bytes(4, byteorder='big').hex()
print(f"加密结果:{sha256}")

运行一下看下H值 

对比下就知道能与标准sha256对上,继续分析后面剩下的三次sha256计算,会发现:

第二次sha256:用字符串HandsomeBro进行计算,H值是用的第一次的计算结果

第三次sha256:用a6(key与0x36异或)进行计算,H值使用的标准算法默认值

第四次sha256:用第二次sha256的结果进行计算,H值用的是第三次的结算结果

看上去是很标准的HMAC算法,我们看一下标准的hmac算法

(1)在密钥key后面添加0来创建一个长为B(64字节)的字符串(str)。

(2)将上一步生成的字符串(str)与ipad(0x36)做异或运算,形成结果字符串(istr)。

(3)将数据流data附加到第二步的结果字符串(istr)的末尾。

(4)做sha256运算于第三步生成的数据流(istr)。

(5)将第一步生成的字符串(str)与opad(0x5c)做异或运算,形成结果字符串(ostr)。

(6)再将第四步的结果(istr)附加到第五步的结果字符串(ostr)的末尾。

(7)做sha256运算于第六步生成的数据流(ostr),输出最终结果(out)。

细心对比一下会发现把标准的(2)与(5)的0x36与0x5c对调了,也算是轻微的魔改了下吧。

继续看⑥根据trace定位一下位置

这个肯定是固定值了,跟我们unidbg里修改的时间戳有关系,接着看下⑧

接着往上分析会发现w8是固定值0xd00,w9是求v374前面23个字节之和在取反

至此,所有字段都已经分析完毕了。

算法还原

还原的算法代码我们就不公布了,照着做一遍也不难。

算法验证

这部分就把unidbg的调用接口生成随机字符串跑个几百次,将明文与加密后的字符串存到文件然后用还原的算法调用计算比对就行了

总结

sig3字段的主要算法就是crc32,白盒aes和魔改的hmacsha256,难度跟复杂度都还是有一些的,做的过程中也踩了不少坑。

参考链接

​​​​​https://github.com/SideChannelMarvels/Stark/tree/masterhttps://blog.youkuaiyun.com/GREY_ZERO/article/details/143609840https://blog.youkuaiyun.com/xiaoyuanwuhui/article/details/131259671https://blog.youkuaiyun.com/weixin_73270563/article/details/135634596

QQ技术交流学习群:1101333782

警告:本文章相关代码与分析流程仅用于技术学习与提升,切勿用于非法用途,否则后果自负。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

### 关于iOS App Crash Related to `__NS_sig3` 或 NSException 处理 当涉及到 iOS 应用程序崩溃 (`crash`) 的问题时,尤其是与特定签名机制(如 `__NS_sig3`)相关的情况,通常需要深入理解应用内部的安全逻辑以及异常处理流程。 #### 1. **关于 `__NS_sig3` 和其作用** `__NS_sig3` 是某些移动应用程序中用于安全验证的一种签名字段。它通常是通过复杂的哈希函数生成的字符串,旨在防止未经授权的数据篡改或伪造请求。例如,在快手应用中,该字段可能由以下方式生成: ```python def gen_tokensig(sig, salt=""): v = sig + salt return hashlib.sha256(v.encode(&#39;utf-8&#39;)).hexdigest() ``` 上述代码片段展示了如何基于输入参数 `sig` 和可选盐值 `salt` 来计算 SHA-256 哈希值[^2]。这种实现方法可以用来保护数据传输过程中的完整性。 #### 2. **Crash 可能的原因分析** 如果一个 iOS 应用因 `__NS_sig3` 而发生崩溃,则可能是由于以下几个原因之一引起的: - **非法访问内存**: 如果 Hook 操作不当或者修改了原生库的行为,可能会导致未定义行为并触发运行时错误。 - **不匹配的签名校验失败**: 当服务器端接收到客户端发送过来带有错误签名的消息时,服务端会拒绝响应甚至终止连接尝试;而本地调试环境下也可能因为未能正确模拟官方算法而导致断言条件被触发展现出崩溃现象。 - **异常捕获不足**: 在开发阶段如果没有妥善设置全局范围内的 NSError 或者 try-catch 结构来应对潜在风险事件的话,那么一旦遇到预料之外状况就很容易造成整个进程停止运作。 以下是针对这些情况的一些解决方案建议: #### 解决方案一: 使用 Frida 进行动态分析 Frida 提供了一种强大的工具集允许开发者轻松挂钩任意 API 并实时监控它们之间的交互细节。对于研究像 KSecurity 类这样的组件非常有用处. 示例脚本如下所示: ```javascript Java.perform(function () { var IKSecurityExCls = Java.use("com.xxx.android.security.KSecurity"); IKSecurityExCls.atlasSign.implementation = function (a) { var result = this.atlasSign(a); console.log(a + " >>> atlasSignB " + result); return result; }; }); ``` 此段 JavaScript 将拦截每次调用 `atlasSign()` 方法的动作,并打印原始输入及其对应的输出结果到控制台以便进一步审查.[^1] #### 解决方案: 改善现有的 Python 实现 为了更精确地模仿目标平台上的认证协议,我们还可以调整之前提到过的 python 函数以适应更多场景需求. 改进后的版本如下: ```python import hashlib class SignatureGenerator(object): @staticmethod def generate_token_signature(base_string, additional_salt=&#39;&#39;): combined_value = base_string + additional_salt digest_algorithm_instance = hashlib.sha256(combined_value.encode()) final_hashed_output = digest_algorithm_instance.hexdigest() return final_hashed_output @staticmethod def create_md5_based_signature(input_parameters_dict={}, fixed_suffix=&#39;ca8e86efb32e&#39;): sorted_keys_list = list(dict(sorted(input_parameters_dict.items())).keys()) concatenated_query_strings = &#39;&#39; for current_key in sorted_keys_list[:-3]: concatenated_query_strings += &#39;{}={}&#39;.format(current_key, input_parameters_dict[current_key]) complete_message_to_be_encoded = concatenated_query_strings + fixed_suffix md5_encoder_object = hashlib.md5(complete_message_to_be_encoded.encode()) resulting_checksum = md5_encoder_object.hexdigest() return resulting_checksum ``` 以上类封装了两个主要功能——一个是创建基于SHA-256散列技术的新令牌标记;另一个则是构建MD5摘要形式的标准签名串。这样做的好处在于提高了代码复用率的同时还增强了灵活性便于后续扩展维护工作开展顺利进行下去. 最后值得注意的是,在实际操作过程中还需要特别关注各个具体环节是否存在边界约束违反等问题以免引起不必要的麻烦。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值