zt3ff3n 发表于 2021-12-30 17:55

每日优鲜mfsig unidbg逆向分析

每日优鲜mfsig unidbg逆向分析
so层

base64


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

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

image-20211225094228289

emmm,参数个数好像对不上,先不管这个。现在从结果往前倒推,v15是输出,它由v14赋值,v14由v18或v19赋值,但是从代码看,这两个好像都没有被作为左值。打开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 + t2 + _CONST) & 0xff for i in range(len(data)))    return msgif __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_363DC的r2,也就是第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 outputif __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_pb2data = meiriyouxian_pb2.Data()data.initNumber = 0x1000002data.a2 = 1data.sign = '1E8DAC5D25EA0FC33A32312F710E0C474AFE0895C2964E3752764DFE5178888B'data.a6 = 1data2 = 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,输入是v179;sub_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

这个似乎是HMAC的update部分,下个断点看看

image-20211225154159843


image-20211225154251091


image-20211225154308043

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

继续往前回溯

image-20211225154423116

应该是HMAC的init函数,进入看看

image-20211225154536612

看看sub_2FA30

image-20211225154623219

两个熟悉的数字0x36和0x5C,它们正是HMAC的magic number。

下个断点看看
emulator.attach().addBreakPoint(module.base + 0x2FA30+1);
image-20211225154939099


image-20211225155010828

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

image-20211225155305021

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


sign的生成流程如下:
HMAC-SHA256protobuf序列化轮换函数自定义base64
import binasciiimport hashlibimport 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 msgdef b64encode(data):    """    sub_37F3C    """    msg = binascii.b2a_base64(data, newline=False)    msg = msg.translate(_TRANS)    msg = msg.decode().rstrip('=')    return msgdef sub_363DC(data, t2):    msg = bytes((data + t2 + _CONST) & 0xff for i in range(len(data)))    return msgdef 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    data4 = sub_363DC(data3, ts2)    data5 = ts1 + data4 + ts2    sign = 'mfsn' + b64encode(data5)    return signdef 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

代码仅供把玩。
页: [1]
查看完整版本: 每日优鲜mfsig unidbg逆向分析