• 安卓逆向-马蜂窝zzzghostsigh算法还原--魔改的SHA-1


    模拟执行

    unidbg调用的视频已出:unidbg调用马蜂窝zzzghostsigh算法

    	这篇文章主要讲解算法还原
    	app version: 9.3.7
    	加密值:muyang
    	返回值:efa2ecf4644732bd5f2f2fa43fe702708674ee1d
    	
    	本篇知识点:unidbg的hook,算法还原。ida分析。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    算法还原

    1.算法猜想

    猜测这是sha1算法
    1.经过测试发现不管输入多少位,结果都是40位。
    2.并且ida查看到IV有5个.
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    2.具体分析

    在这里插入图片描述

    	打开IDA发现,sub_30548。是一个签名校验函数。因为点进去会看到签名校验的三兄弟。(下图),并且返回值错误的时候,
    会说。Illegal signature(非法的签名)
    
    • 1
    • 2

    在这里插入图片描述

    那么IDA只剩下下面2个函数没有分析了。sub_312E0.
    
    • 1

    在这里插入图片描述

    	将入参修改一下,然后v9是和inputtext,输入的值有关。v13是一个buffer。v10是v9的长度。那么由此可得这个sub_312E0,
    应该就是加密的函数,因为sub_2E1F4,没有和输入值有关的。并且v13是一个buffer,这是C语言常用的一种格式。会将加密值放到		
    一个buffer中。
    
    • 1
    • 2
    • 3

    使用unidbg验证一下我们的猜想;使用HookZz 在函数进入前Hook参数1和参数3,函数出去后Hook 参数2。

        public void hook_312E0(){
            // 获取HookZz对象
            IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
            // hook MDStringOld
            hookZz.wrap(module.base + 0x312E0 + 1, new WrapCallback() { // inline wrap导出函数
                @Override
                // 方法执行前
                public void preCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                    Pointer input = ctx.getPointerArg(0);
                    byte[] inputhex = input.getByteArray(0, ctx.getR2Int());
                    Inspector.inspect(inputhex, "input");
    
                    Pointer out = ctx.getPointerArg(1);
                    ctx.push(out);
                };
    
                @Override
                // 方法执行后
                public void postCall(Emulator emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                    Pointer output = ctx.pop();
                    byte[] outputhex = output.getByteArray(0, 20);
                    Inspector.inspect(outputhex, "output");
                }
            });
        };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    那么我们发现和我们的猜想是一致的。所以只需要关注这个函数。
    
    • 1

    在这里插入图片描述

    进入这个函数发现也有这么几个变量,按H可以切换到16进制。这就是sha-1的魔数。下图是标准的魔数,经过对比
    可以发现,IV的第四个和第五个被改变了。
    
    • 1
    • 2

    在这里插入图片描述

    修改下源代码
    
    • 1
    # sha1-v1
    import struct
    
    bitlen = lambda s: len(s) * 8
    
    
    def ROL4(x, n):
        x &= 0xffffffff
        return ((x << n) | (x >> (32 - n))) & 0xffffffff
    
    
    def madd(*args):
        return sum(args) & 0xffffffff
    
    
    class sha1:
        block_size = 64
        digest_size = 20
    
        def __init__(self, data=b''):
            if data is None:
                self._buffer = b''
            elif isinstance(data, bytes):
                self._buffer = data
            elif isinstance(data, str):
                self._buffer = data.encode('ascii')
            else:
                raise TypeError('object supporting the buffer API required')
    
            self._sign = None
    
        def update(self, content):
            if isinstance(content, bytes):
                self._buffer += content
            elif isinstance(content, str):
                self._buffer += content.encode('ascii')
            else:
                raise TypeError('object supporting the buffer API required')
    
            self._sign = None
    
        def copy(self):
            other = self.__class__.__new__(self.__class__)
            other._buffer = self._buffer
            return other
    
        def hexdigest(self):
            result = self.digest()
            return result.hex()
    
        def digest(self):
            if not self._sign:
                self._sign = self._current()
            return self._sign
    
        def _current(self):
            msg = self._buffer
    
            # standard magic number
            # A = 0x67452301
            # B = 0xEFCDAB89
            # C = 0x98BADCFE
            # D = 0x10325476
            # E = 0xC3D2E1F0
    
            A = 0x67452301
            B = 0xEFCDAB89
            C = 0x98BADCFE
            D = 0x5E4A1F7C
            E = 0x10325476
    
            msg_len = bitlen(msg) & 0xffffffffffffffff
    
            zero_pad = (56 - (len(msg) + 1) % 64) % 64
            msg = msg + b'\x80'
            msg = msg + b'\x00' * zero_pad + struct.pack('>Q', msg_len)
    
            for idx in range(0, len(msg), 64):
                W = list(struct.unpack('>16I', msg[idx:idx + 64])) + [0] * 64
    
                for t in range(16, 80):
                    T = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]
                    W[t] = ROL4(T, 1)
    
                a, b, c, d, e = A, B, C, D, E
    
                # main loop:
                for t in range(0, 80):
                    if 0 <= t <= 19:
                        f = (b & c) | ((~b) & d)
                        k = 0x5A827999
    
                    elif 20 <= t <= 39:
                        f = b ^ c ^ d
                        k = 0x6ED9EBA1
    
                    elif 40 <= t <= 59:
                        f = (b & c) | (b & d) | (c & d)
                        k = 0x8F1BBCDC
    
                    elif 60 <= t <= 79:
                        f = b ^ c ^ d
                        k = 0xCA62C1D6
    
                    S0 = madd(ROL4(a, 5), f, e, k, W[t])
                    S1 = ROL4(b, 30)
    
                    a, b, c, d, e = S0, a, S1, c, d
    
                A = madd(A, a)
                B = madd(B, b)
                C = madd(C, c)
                D = madd(D, d)
                E = madd(E, e)
    
            result = struct.pack('>5I', A, B, C, D, E)
            return result
    
    
    if __name__ == '__main__':
        s = b'muyang'
        s0 = sha1(s).hexdigest()
        print(s0)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    在这里插入图片描述

    发现和马蜂窝的答案(efa2ecf4644732bd5f2f2fa43fe702708674ee1d)不一样。最开始就有标准答案。
    
    • 1

    那么我们该如何找到魔改的地方了?

    	一个哈希算法,可以简单划分成填充和加密两部分,直接Hook加密函数,看它的入参,依此判定填充部分是否发生
    过改变。
    
    • 1
    • 2
        public void hook_3151C(){
            // 获取HookZz对象
            IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz
            // hook MDStringOld
            hookZz.wrap(module.base + 0x3151C + 1, new WrapCallback<HookZzArm32RegisterContext>() { // inline wrap导出函数
                @Override
                // 方法执行前
                public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                    // 类似于Frida args[0]
                    Pointer input = ctx.getPointerArg(0);
                    byte[] inputhex = input.getByteArray(0, 20);
                    Inspector.inspect(inputhex, "IV");
    
                    Pointer text = ctx.getPointerArg(1);
                    byte[] texthex = text.getByteArray(0, 64);
                    Inspector.inspect(texthex, "block");
                    ctx.push(input);
                    ctx.push(text);
                };
    
                @Override
                // 方法执行后
                public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
                    Pointer text = ctx.pop();
                    Pointer IV = ctx.pop();
    
                    byte[] IVhex = IV.getByteArray(0, 20);
                    Inspector.inspect(IVhex, "IV");
    
                    byte[] outputhex = text.getByteArray(0, 64);
                    Inspector.inspect(outputhex, "block out");
    
                }
            });
        };
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述
    Hook结果反映了这样一个问题

    	魔改的是算法本身,因为运算函数的入参是正常的、填充后的明文,所以不存在自定义填充、或者对明文做变换的可能,
    出参即是输出的结果,所以算法并不是在标准流程之后做了一些自定义步骤,它修改的——就是算法本身。
    
    • 1
    • 2

    那么我们进入 sub_3151C 这个函数观察一下。发现了几个常数。

    1518500249
    1859775393
    -1894007588
    -899497514
    
    • 1
    • 2
    • 3
    • 4

    转成16进制表示:

    hex(1518500249) = 0x5a827999
    hex(1859775393) = 0x6ed9eba1
    hex(-1894007588 & 0xFFFFFFFF) = 0x8f1bbcdc
    hex(-899497514 & 0xFFFFFFFF) = 0xca62c1d6
    
    • 1
    • 2
    • 3
    • 4

    这4个数正是标准实现里的数字,每个数字20轮,一共80轮。然后我们统计一下函数里面这几个数的出现次数

    0x5a827999 x 18
    0x6ed9eba1 x 4
    0x8f1bbcdc x 23
    0x5a827999 x 20
    0xca62c1d6 x 20
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一定要注意这个顺序,因为加密的也是时候需要这个顺序
    在这里插入图片描述

    总共85次,比标准的多出现了5次,然后检查下,发现如下重复
    在这里插入图片描述在这里插入图片描述
    去除重复出现的,上面的出现次数修正为

    	0x5a827999 x 16
    	0x6ed9eba1 x 4
    	0x8f1bbcdc x 20
    	0x5a827999 x 20
    	0xca62c1d6 x 20
    
    • 1
    • 2
    • 3
    • 4
    • 5

    正好80次,因此,我们将轮换的代码修改一下:

    for t in range(80):
        if t <= 15:
            K = 0x5a827999
            f = (b & c) ^ (~b & d)
        elif t <= 19:
            K = 0x6ed9eba1
            f = b ^ c ^ d
        elif t <= 39:
            K = 0x8f1bbcdc
            f = (b & c) ^ (b & d) ^ (c & d)
        elif t <= 59:
            K = 0x5a827999
            f = (b & c) ^ (~b & d)
        else:
            K = 0xca62c1d6
            f = b ^ c ^ d
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    efa2ecf4644732bd7a57bff224be72228674ee1d
    这是这段代码的返回值
    efa2ecf4644732bd5f2f2fa43fe702708674ee1d(标准答案)	
    
    虽然不一样,但是感觉已经很接近了对吧。
    我们发现[0:8],[8:16],[32:40]是能够对的上的,但是[16:24],[24:32]就完全不同。这么相近的结果,说明我们在最后的实现和实际有些不同。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    再次看看sub_3151C最后的代码
    在这里插入图片描述
    发现最后是4-2-3-1-0的顺序加密。因此,我们在最后一次轮换中也按这个顺序

    if t == 79:
        a, b, d, c, e = S0, a, S1, c, d
    else:
        a, b, c, d, e = S0, a, S1, c, d
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    最后完成。希望你可以有所收获。

    课程样本 :链接:https://pan.baidu.com/s/1oN-AHME4Oe3v50EGEitZPQ?pwd=adpr 
    		提取码:adpr
    		一起学习:Ays971124
    
    • 1
    • 2
    • 3
  • 相关阅读:
    ipynb转换为pdf、Markdown(.md)
    【Hack The Box】Linux练习-- Frolic
    arthas实用梳理篇
    python知识点_初级(汇总)
    2.Modbus通信协议-软件调试4个工具软件(推荐)
    matlab问题求解答
    windows 10 安装k8s环境 Kubernetes
    tensorflow深度学习模型读取parquet数据进行训练实现
    CloudCompare
    胶囊网络深入理解
  • 原文地址:https://blog.csdn.net/qq_41155858/article/details/126260480