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

模拟执行

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

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

算法还原

1.算法猜想

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

在这里插入图片描述

2.具体分析

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

使用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<HookZzArm32RegisterContext>() { // 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");
            }
        });
    };


在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

修改下源代码
# 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)

在这里插入图片描述

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

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

	一个哈希算法,可以简单划分成填充和加密两部分,直接Hook加密函数,看它的入参,依此判定填充部分是否发生
过改变。
    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");

            }
        });
    };


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

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

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

1518500249
1859775393
-1894007588
-899497514

转成16进制表示:

hex(1518500249) = 0x5a827999
hex(1859775393) = 0x6ed9eba1
hex(-1894007588 & 0xFFFFFFFF) = 0x8f1bbcdc
hex(-899497514 & 0xFFFFFFFF) = 0xca62c1d6

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

0x5a827999 x 18
0x6ed9eba1 x 4
0x8f1bbcdc x 23
0x5a827999 x 20
0xca62c1d6 x 20

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

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

	0x5a827999 x 16
	0x6ed9eba1 x 4
	0x8f1bbcdc x 20
	0x5a827999 x 20
	0xca62c1d6 x 20

正好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
efa2ecf4644732bd7a57bff224be72228674ee1d
这是这段代码的返回值
efa2ecf4644732bd5f2f2fa43fe702708674ee1d(标准答案)	

虽然不一样,但是感觉已经很接近了对吧。
我们发现[0:8],[8:16],[32:40]是能够对的上的,但是[16:24],[24:32]就完全不同。这么相近的结果,说明我们在最后的实现和实际有些不同。

再次看看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

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

课程样本 :链接:https://pan.baidu.com/s/1oN-AHME4Oe3v50EGEitZPQ?pwd=adpr 
		提取码:adpr
		一起学习:Ays971124
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
还原jsvmp-某乎_x-zes-96参数算法,首先需要了解该算法的具体逻辑和实现方式。接下来是一份手把手教学,帮助你进行还原。 1. 阅读文档:首先,找到jsvmp-某乎_x-zes-96参数算法的相关文档或说明,并仔细阅读。理解算法的目的、输入、输出和具体实现细节。 2. 分析算法:根据文档中的描述,仔细分析算法的各个步骤和计算逻辑。了解算法使用的数据结构、数学模型和函数等。 3. 实践案例:找到使用该算法的实践案例,或者自己创建一个简单的案例。通过这个案例,可以更好地理解算法的工作方式和参数调整的影响。 4. 调试工具:使用调试工具对代码进行逐行调试。在关键节点上加入断点,观察变量和数据的变化过程,以及函数的执行顺序。通过这种方式,深入了解算法的执行过程。 5. 修参数:通过调试分析,找到算法中的参数和默认设置。尝试更这些参数的数值,观察算法的输出结果的变化。通过不断修参数和分析结果,逐渐掌握算法的运行规律。 6. 反向工程:通过对算法代码的逆向分析,尝试还原参数的计算过程。对研究的代码进行逆向工程,分析其中的数学运算和逻辑判断。通过逆向工程,可以更好地理解参数的计算方式。 7. 文档总结:将还原过程中的关键步骤和分析结果总结起来,形成详细的文档。这份文档可以帮助其他人了解算法还原过程,同时也对自己的学习过程进行总结和回顾。 以上是一个基本的手把手教学,希望能够帮助你还原jsvmp-某乎_x-zes-96参数算法。请注意,由于算法的具体实现和难度因人而异,实际操作过程可能比上述步骤更加复杂和耗时,需要耐心和技术能力的支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

和沐阳学逆向

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值