每日优鲜mfsig unidbg逆向分析

每日优鲜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逆向分析)