美文网首页
每日优鲜mfsig unidbg逆向分析

每日优鲜mfsig unidbg逆向分析

作者: ever_hu | 来源:发表于2021-12-27 14:13 被阅读0次

    每日优鲜mfsig unidbg逆向分析

    so层

    base64

    前面已经分析了Java层的调用,以及unidbg实现,接下来结合unidbg和ida对so层进行逆向。

    ida打开libsign.so,函数窗口搜索Java,可以看到静态注册的Java_cn_missfresh_wsg_SecurityLib_nativeSign,进入函数,修改a1JNIEnv *a1

    image-20211225094228289

    emmm,参数个数好像对不上,先不管这个。现在从结果往前倒推,v15是输出,它由v14赋值,v14v18v19赋值,但是从代码看,这两个好像都没有被作为左值。打开sub_36FC0,返回后F5一下,函数更新

    image-20211225094728040

    所以最终它是由sub_36FC0的第一个参数v17赋值的,进去看看

    image-20211225095448360

    而它绝大部分的参数都被传进了一个函数sub_332F0,值得点进去看看

    image-20211225095833407 image-20211225095857927 image-20211225095937949 image-20211225095952965

    然后就看到了几百行的代码,里面由很多类似log的东西,从中看出似乎用了hmac算法,暂时不清楚摘要算法是什么,也看到了msfn这个熟悉的字符串。

    但是几百行代码,调用的函数少说也有上10个了,层层调用,要从哪里开始分析呢。。

    这时候就要用unidbg了,先在sub_332F0下个断点

    public MissFresh() {
        //...
        emulator.attach().addBreakPoint(module.base + 0x332f0+1);
    }
    
    image-20211225100657360

    结合ida来看,r0就是存结果的地方,不过现在刚进入函数,结果还没存进去。打印其他寄存器看看

    image-20211225100924463

    输入blr,在函数返回的地方下个断点,然后输入c继续执行,然代码运行到函数返回处,这时候打印一下刚刚r0的地址的数据

    image-20211225101422775

    打印图中地址的数据

    image-20211225101454583

    这个就是我们的结果,那么现在我们要对0x402e7000这个地址进行跟踪,看看是谁对它进行了写操作

    public MissFresh() {
        //...
        emulator.traceWrite(0x402e4000L, 0x402e4000L+16L);
    }
    
    image-20211225101958758

    可以看到有2轮写的操作,不过我比较关心第一轮,因为这时候结果已经生成了,第二轮只是简单的移一下位置,在字符串头部添加mfsn

    那么ida跳转到0x37f76,它在sub_37F3C这个函数

    image-20211225102254858

    多熟悉的代码啊,这一看就是base64,点击看看aAbcdefghijklmn

    image-20211225102448422

    自定义的码表,接着下断点看看输入

    emulator.attach().addBreakPoint(module.base + 0x37F3C+1);
    
    image-20211225102639079

    CyberChef验证一下

    image-20211225102933609

    完全没问题,那么接下来就是找base64的输入是怎么来的,从样式来看,前9位是时间戳的前9位,后4位是时间戳的后4位,所以接下来就是找中间的长度为64的输入。

    轮换

    sub_37F3C查看引用,只有一个函数sub_37E5C,进去看看

    image-20211225103334974 image-20211225103529007

    我们已经知道v7就是base64的输入,而它是由a2赋值的。继续对sub_37E5C查找引用,只有一个函数sub_332F0

    image-20211225103932863 image-20211225104006639

    这时候我们已经从数百行代码里找到了最后生成结果的地方,接下来就是继续往前回溯。

    当然,我们也可以根据mfsn这个字符串推测,v179是最后存结果的地址,而它又在sub_37E5C被使用了,进而找到突破口。

    总而言之,我们现在要找v116是怎么生成的。

    image-20211225104757920

    啥也别说了,看看sub_2F8F6

    image-20211225104935825

    下个断点看看

    emulator.attach().addBreakPoint(module.base + 0x2F8F6+1);
    

    运行之后,发现它调用了很多次,哪次才是我想要查看的呢。首先,初始化完成之前的我们肯定不需要查看,初始化之后它被调用了3次,打印输入输出之后发现是第2次。

    image-20211225112922991

    接下来对0x402a10f0进行跟踪,看看谁对它进行了写操作。

    emulator.traceWrite(0x402a10f0L, 0x402a10f0L+32L);
    
    image-20211225113115812

    ida跳转到0x36489,发现是sub_363DC函数。

    image-20211225113333321

    下断点看看输入

    emulator.attach().addBreakPoint(module.base + 0x363DC+1);
    
    image-20211225113516594 image-20211225113537658 image-20211225113548433

    结合代码可以分析出,v14就是"9566",也就是时间戳的后4位,v17"ABCDEFGH",从sub_332F0看出,它是一个定值。

    image-20211225113957105

    结合代码分析得出,它是对输入做一个轮换,然后得出结果。代码实现来验证一下。

    _CONST = b'ABCDEFGH'
    
    def sub_363DC(data, t2):
        msg = bytes((data[i] + t2[i%4] + _CONST[i%8]) & 0xff for i in range(len(data)))
        return msg
    
    if __name__ == '__main__':
        data = bytes.fromhex('088280800810011a40314538444143354432354541304643333341333233313246373130453043343734414645303839354332393634453337353237363444464535313738383838423001')
        print(sub_363DC(data, b'9566').hex())
    
    image-20211225114607586

    完全对上了!

    protobuf

    接下来就是看轮换函数的输入是怎么来的,通过更换时间戳和请求参数,发现输入的前面一段088280800810011a40和后面一小段3001是不会变的。但是它到底是什么呢,是完全固定的无意义的值,还是其他什么东西。先继续往前追溯。

    前面提到,加密疑似用了hmac算法,当时我们不太清楚用了什么摘要算法,不过现在我们从输入中间那段长度位64的16进制字符串,猜测它用了SHA256摘要算法。接下来就是验证它是不是用了SHA256,是不是标准的SHA256

    从前面已经分析出sub_363DCr2,也就是第3个参数,存着输入。

    image-20211225120036326

    所以我们往前追溯v163的调用。

    image-20211225120149338

    进去看看

    image-20211225140100656

    这个函数干了什么,我们可以通过后续的log推测一下

    image-20211225141735844

    似乎是个protobuf序列化,那我们尝试把之前得到的结果进行个反序列化看看。

    def decode(data):
        process = subprocess.Popen(
            ["protoc", "--decode_raw"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
    
        output = error = None
        try:
            output, error = process.communicate(data)
            output = output.decode()
        except OSError:
            pass
        finally:
            if process.poll() != 0:
                process.wait()
        return output
    
    if __name__ == '__main__':
        data = bytes.fromhex('088280800810011a40314538444143354432354541304643333341333233313246373130453043343734414645303839354332393634453337353237363444464535313738383838423001')
        print(decode(data))
    
    image-20211225141936839

    反序列化成功,说明使用了protobuf。而16777218等于0x1000002,这个就是so调用初始化函数的时候传入的值。

    接下来就是编写proto文件

    syntax = "proto3";
    
    message Data {
        int32 initNumber = 1;
        int32 a2 = 2;
        string sign = 3;
        int32 a6 = 6; 
    }
    

    编译生成python文件

    protoc meiriyouxian.proto --python_out .
    

    调用验证

    import meiriyouxian_pb2
    data = meiriyouxian_pb2.Data()
    data.initNumber = 0x1000002
    data.a2 = 1
    data.sign = '1E8DAC5D25EA0FC33A32312F710E0C474AFE0895C2964E3752764DFE5178888B'
    data.a6 = 1
    
    data2 = data.SerializeToString()
    print(data2)
    
    image-20211225143257895

    当然偷懒一点的办法也有,就是直接把前面和后面的字符写死,反正最后变动的只有中间的64个字符。

    hmac

    接下来就是查找疑似HMAC-SHA256的中间值,继续看看sub_348B4,下断点看看输入

    image-20211225143933889 image-20211225143945200

    接下来从sub_348B4的第5个参数继续往前回溯,

    image-20211225144315920 image-20211225144340705

    sub_2E5A4的输出应该是v145,输入是v179sub_37D8C的输出应该是v179,输入应该是v202

    由于sub_2E5A4有被多次调用,选择先在sub_37D8C下个断点看看

    emulator.attach().addBreakPoint(module.base + 0x37D8C+1);
    
    image-20211225145659606 image-20211225145728122

    输入blr在函数返回处下断点,输入c继续执行到函数返回处。查看原r0的值

    image-20211225145851418 image-20211225145907681

    所以sub_37D8C的返回值已经有我们需要的值了,现在要看看它是怎么得出这个结果的。

    image-20211225151921347

    我们已经知道a2是它的输入,所以先看看sub_2FB14

    image-20211225152413418

    看看sub_367F6

    image-20211225153355859

    进入sub_36558看看

    image-20211225153448580 image-20211225153511163

    这些都是SHA256的标志,再看看dword_9E030

    image-20211225153602667

    妥妥的SHA256的K值。

    现在我们已经有很大把握确定它是HMAC-SHA256,接下来就是找它的输入,以及它的key,方便我们验证它是否是标准的实现。

    回到主体函数sub_332F0,继续往前回溯

    image-20211225154019122

    这个似乎是HMACupdate部分,下个断点看看

    image-20211225154159843 image-20211225154251091 image-20211225154308043

    正好是Java层的请求参数,没有再拼接salt或者其他东西。

    继续往前回溯

    image-20211225154423116

    应该是HMACinit函数,进入看看

    image-20211225154536612

    看看sub_2FA30

    image-20211225154623219

    两个熟悉的数字0x360x5C,它们正是HMACmagic number

    下个断点看看

    emulator.attach().addBreakPoint(module.base + 0x2FA30+1);
    
    image-20211225154939099 image-20211225155010828

    这个极有可能就是HMACkey,有了key和输入,在CyberChef上验证一下,不行我们再继续分析。

    image-20211225155305021

    完全正确!说明是标准实现,接下来就是用代码实现一下整个流程。

    总结和代码实现

    sign的生成流程如下:

    1. HMAC-SHA256
    2. protobuf序列化
    3. 轮换函数
    4. 自定义base64
    import binascii
    import hashlib
    import hmac
    
    # 请自行生成
    import meiriyouxian_pb2
    
    _TABLE_RAW = b'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    _TABLE_MISS = b'abcdefghijklmnopqrstuvwxyz+ZYXWVUTSRQPONMLKJIHGFEDCBA/1234567890'
    _TRANS = bytes.maketrans(_TABLE_RAW, _TABLE_MISS)
    _TRANS_INV = bytes.maketrans(_TABLE_MISS, _TABLE_RAW)
    
    _HMAC_KEY = b'PwwGKgCqZAc2PPb31TLnnqPNVFAAdq/X'
    _CONST = b'ABCDEFGH'
    
    
    def b64decode(data):
        left = len(data) % 4
        if left:
            data += '=' * (4 - left)
        data = data.encode().translate(_TRANS_INV)
        msg = binascii.a2b_base64(data)
        return msg
    
    def b64encode(data):
        """
        sub_37F3C
        """
        msg = binascii.b2a_base64(data, newline=False)
        msg = msg.translate(_TRANS)
        msg = msg.decode().rstrip('=')
        return msg
    
    def sub_363DC(data, t2):
        msg = bytes((data[i] + t2[i%4] + _CONST[i%8]) & 0xff for i in range(len(data)))
        return msg
    
    
    def calc_sign(params, ts, body='', init=0x1000002):
        if isinstance(body, str):
            body = body.encode()
        if isinstance(ts, str):
            ts = ts.encode()
        if isinstance(params, (list, tuple, dict)):
            if hasattr(params, 'items'):
                params =  params.items()
            params = ''.join(f'{k}{v}' for k, v in sorted(params, reverse=True)).encode()
        data = params + body
        data2 = hmac.new(_HMAC_KEY, data, hashlib.sha256).hexdigest().upper()
        
        pbuf = meiriyouxian_pb2.Data()
        pbuf.initNumber = init
        pbuf.a2 = 1
        pbuf.sign = data2
        pbuf.a6 = 1
        data3 = pbuf.SerializeToString()
        
        ts1 = ts[:9]
        ts2 = ts[9:]
        data4 = sub_363DC(data3, ts2)
        data5 = ts1 + data4 + ts2
        sign = 'mfsn' + b64encode(data5)
        return sign
    
    def test():
        ts = b'1640187039566'
        query = b'version9.7.0tdkeyJvcyI6ImFuZHJvaWQiLCJ2ZXJzaW9uIjoiMy4xLjkiLCJwYWNrYWdlcyI6ImNuLm1pc3NmcmVzaC5hcHBsaWNhdGlvbiomOS43LjAiLCJwcm9maWxlX3RpbWUiOjI4MywiaW50ZXJ2YWxfdGltZSI6MTQ5NjksInRva2VuX2lkIjoiajViSUs1SmV1bUxzZUVWMVptb3ZxNHNzT0J4OXBCUlJsNk9kbzRlQ01iemZWNWNlUmswSjZYK2lLWE4rVkdJQ3N5S1V0MFByS1lHSE5tMm5iSlZIOHc9PSJ9source_device_id359906070748939sessionandroid0.95648171296963921640187024465screen_width1440screen_height2560realVersionplatformandroidisShow0imeifbc64376480ee60e43e933dae0258d3fdevtka3JZZ1NRVzNZWW9ZMERIVDFvQmJ6MytoVkxsQWJuV1RnLzV2MnpXYVZGUTFqN09zUFIzeFd6WWo3dkNsb0J4MEY2Q1FGeTZhZXpQaA0KdFlZWXAzQkxicmxiTm5rejE0SEt5UE84UVpWOXdWRUxJem0rd0ZiV2QzVks4cFphMmphQWJFYmJrK3dFQXRCL1N6eEtXNmp3eHc9PQ==device_id359906070748939currentLng113.97177currentLat22.540642android_id581e0c22a2843d73android_channel_valuept-lingdu002access_tokenSM_Device_ID2021120821500831e6fbaf8244fa2c94916c1cfe02a8a701cd5c98e2bbb3dc'
        sign = calc_sign(query, ts)
        print(sign)
        assert sign == 'mfsnmtyAmde3nBaBUFN49M+lVLS5Kl5CEJBaI65LJJ90K7pbJ+K5JZcGJJdaJKKKE5FaIJgJGIddK6w2J6KJI6sFEJgDJkGDHk0bDl9IKJg1I6w1FkX5otu1nU'
    
    if __name__ == '__main__':
        test()
    
    image-20211225160432289

    代码仅供把玩。

    相关文章

      网友评论

          本文标题:每日优鲜mfsig unidbg逆向分析

          本文链接:https://www.haomeiwen.com/subject/pyjyqrtx.html