
样本地址: aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzQ5MjY0My9oaXN0b3J5X3YxMTA4
抓包分析
首先来抓一下登录 login 接口,这接口最明显

首先来看一下请求参数,经过测试发现: oauth_nonce, oauth_signature, zzzghostsigh 这三个参数是变化的,所以需要处理的也就是这三个参数
java 层分析
首先进行搜索 oauth_nonce

从这儿可以看出直接就是随机数,直接可以本地生成
先来看一下 zzzghostsigh 这个参数

这个参数由 ghostSigh 这个函数生成,不断往里面跟,发现是由 xPreAuthencode 这个 native 函数生成的
同理: oauth_signature 是由 xAuthencode 这个 native 函数生成的
zzzghostsigh 参数的生成
先来搭建一个 unidbg 框架,进行模拟调用
package com.mfw;
import com.github.unidbg.linux.android.dvm.AbstractJni;import com.github.unidbg.AndroidEmulator;import com.github.unidbg.Module;import com.github.unidbg.linux.android.AndroidEmulatorBuilder;import com.github.unidbg.linux.android.AndroidResolver;import com.github.unidbg.linux.android.dvm.*;import com.github.unidbg.memory.Memory;import java.io.File;import java.util.ArrayList;import java.util.List;import com.github.unidbg.arm.backend.Backend;import com.github.unidbg.pointer.UnidbgPointer;import keystone.Keystone;import keystone.KeystoneArchitecture;import keystone.KeystoneMode;
public class Mfw extends AbstractJni{ private final AndroidEmulator emulator; private final VM vm; private final Module module;
Mfw() { emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.mfw.roadbook").build(); // 创建模拟器实例 Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 vm = emulator.createDalvikVM(new File("apks/mafengwo/mfw11.3.0.apk")); // 创建Android虚拟机 DalvikModule dm = vm.loadLibrary(new File("apks/mafengwo/libmfw.so"), true); // 加载so到虚拟内存 module = dm.getModule(); //获取本SO模块的句柄 vm.setJni(this); vm.setVerbose(true); dm.callJNI_OnLoad(emulator); };
public String xPreAuthencode(){ List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); // 第一个参数是env list.add(0); // 第二个参数,实例方法是jobject,静态方法是jclazz,直接填0,一般用不到。 DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);// context list.add(vm.addLocalObject(context)); list.add(vm.addLocalObject(new StringObject(vm, "PUT&https%3A%2F%2Fmapi.mafengwo.cn%2Frest%2Fapp%2Fuser%2Flogin%2F&_mfwencode%3DeyJjb250ZW50IjoiWGwvbEhjM00rb24rdjRmOW5GZUU4T0xEUGlTRkFaZ0J2Yk5jVDV4ZU41aVcyZ0xUNXJQb2FRRlE3UlY1dXprZWhReHI4VDRvZ1F0QTZwTzI3UWZ5amdjR1o1bkpWKzRxL0ovaEZPNG5namNcdTAwM2QiLCJrZXkiOiJWbVU3ZUowNk1IUnZONldoQVp0VzZWVS9xTFFqZExUTHByb3BrZS93MkxMb1lpUFBRNW0vTE94Q2RoUG1RVHd0Q3NFdTQxWXh4NHpqdVkwVjJTNEM1YVhJWlRRY0JXSzh4cDkwL1M1Vm1oYVZhaTVvV2pqc3h1a1RZL3ZxQjYwTGlZaXNsSE1hcVNHeUY2eXUwaHcxcFFMa2IvQnRWa0c0L1g3ejJKOU9WSGtCTnVFZVVsZUt6ZXV6eWVNdFEwSThhN3dOWFhSbjF2UEwxSVdodXoveW1ZNTZxSjBROEJMUTFWSWdseFUybG9GREtGa0RFdDZNdTZ6RG1UT2swT3RQR25tZzJhSlVmZ3BHWmNDQU50VWVGeXdsbUJhYTVGeENzS21CcVJRSXJ4ODRIZzFhVHpBd21QdytRMlQ2WExZdWppOThPWlQ5Wldsbm5MNnAzYTc4ZVFcdTAwM2RcdTAwM2QiLCJ2ZXJzaW9uIjoxfQ%253D%253D%26after_style%3Ddefault%26android_oaid%3D00000000-0000-0000-0000-000000000000%26app_code%3Dcom.mfw.roadbook%26app_ver%3D11.3.0%26app_version_code%3D1108%26brand%3Dgoogle%26channel_id%3DVivo%26dev_ver%3DD2504.0%26device_id%3D1a225b1ca66cb7fd%26device_type%3Dandroid%26hardware_model%3DPixel%25202%26has_notch%3D0%26mfwsdk_ver%3D20140507%26o_coord%3Dwgs%26oauth_consumer_key%3D5%26oauth_nonce%3Dd0cb3997-2965-4a93-b22d-4f51f00b4c6d%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1770890982%26oauth_version%3D1.0%26open_udid%3D1a225b1ca66cb7fd%26put_style%3Ddefault%26screen_height%3D1794%26screen_scale%3D2.88%26screen_width%3D1080%26shumeng_id%3DDUE13wluwiFe0hoViIxIC9zGaccdbGJuC100RFVFMTN3bHV3aUZlMGhvVmlJeElDOXpHYWNjZGJHSnVDMTAwc2h1%26sys_ver%3D10%26time_offset%3D480%26x_auth_mode%3Dclient_auth"))); list.add(vm.addLocalObject(new StringObject(vm, "com.mfw.roadbook")));
Number number = module.callFunction(emulator, 0x396c8, list.toArray()); String result = vm.getObject(number.intValue()).getValue().toString(); return result; }
public static void main(String[] args) throws Exception { Mfw test = new Mfw(); System.out.println(test.xPreAuthencode()); }}但是当我进行调用的时候,出现了错误

这里面又 packageInfo,signature,我怀疑有可能是签名校验的问题,先把 so 拖到 ida 中进行分析
首先跳转到函数 0x396c8

在这儿发现了问题,他会对 v7 的值进行判断,非法签名, sub_3C9C4 应该就是签名校验的函数了
进行之后,发现它确实是在对包名进行校验,可以尝试在 unidbg 中进行 patch

patch 代码如下:
public void patch(){ UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base+0x3C9C4); // 修正模式为 LittleEndian Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian); String s = "MOV W0, #1; RET"; byte[] machineCode = keystone.assemble(s).getMachineCode(); assert pointer != null; pointer.write(machineCode);}patch 过后,发现直接就能够得到结果了,还不需要补环境啊

接着来进行算法分析
来到 0x39964

v6 这个之前分析过, sub_3C9C4 就是一个签名校验函数,我通过 patch 返回的正确值,过掉的这个签名校验函数
v8 是输入的明文,v9 是明文的长度
接着通过 loc_3DEC4 对数据进行加密,所以这个位置毫无疑问的就是很重要的地方,需要去重点分析
在 ida 中,loc_3DEC4 是一个自动生成的代码位置的标签,命名规则:
loc:前缀表示 “location”(位置/标签)3DEC4:十六进制地址偏移量 在 IDA 中,其他的常见命名方式如下:loc_代码位置sub_函数/子程序off_数据偏移量dword_/qword_数据变量unk_未知类型
跳进去之后有点看不懂,看一下汇编是咋样的

首先标准的函数入口,开辟内存空间,分配栈帧
接着加载 #xmmword_C0C30@PAGE 这里面的常量

然后下面 W8 寄存器通过逻辑左移赋值等操作,得到 0x10325467,在之前学习算法的时候知道,标准的 hash 算法,他的常数是 0x67452301,这个不太一样啊,很有可能是进行魔改了
接着往下看,知道他会跳转到 loc_3DF38,将常数加载到内存,接着就应该把明文进行扩展了
跳转到 loc_3DF38,看一下进行了哪些操作

CMN W9, #8 比较 W9+8,更新标志位
B.CC loc_3DF50 当进位标志位为 0 时发生跳转,如果没有溢出就跳转到 loc_3DF50 这个位置,如果溢出了,就递增 w8
跳转到 loc_3DF50,看一下相关的代码

使用 unidbg 来 hook 一下 x9 寄存器的值,w9 是 x9 寄存器的低四位
long baseAddr = module.base;emulator.attach().addBreakPoint(baseAddr+0x3DF54L, new BreakPointCallback() { @Override public boolean onHit(Emulator<?> emulator, long address) { int x9 = emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X9).intValue(); System.out.printf("获取到x9寄存器的值为:0x%02x\n", x9);
return true; }});发现每隔 64 次就不跳转到 loc_3DF84

而是跳转到 sub_3E1D0 这个子函数,去看一下这个函数到底再做什么操作?

这里的 a1 有 5 个元素,看起来跟 sha1 算法的 5 个常数对应,a2 貌似很少有被用到,同时 hook 一下这两个参数吧
hook a2,发现就是明文

hook a1,他是 sha1 算法的常量

按照小端排序,得到:
0x674523010xEFCDAB890x98BADCEF0x5E4A1F7C0x10325476而我们正常的 sha1 算法常数是:
0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0对比得知第四,第五个常数发生了变化
在之前学习算法的过程中,知道了 SHA1 算法常见的魔改点有常量,k 值,线性函数等
k 值呢?一般是下面这几个
0x5A8279990x6ED9EBA10x8F1BBCDC0xCA62C1D6但是我发现他跟正常的 sha1 算法用到的 k 值不太对
0x5A827999 在运算中出现了16次即0到15轮0x6ED9EBA1 在运算中出现了4次即16到19轮0x8F1BBCDC 在运算中出现了20次即20到39轮0x5A827999 在运算中出现了20次即40到59轮0xCA62C1D6 在运算中出现了20次即60到79轮我改了之后,他的部分值是对的,但是还有一部分值错误了,我加密的值是 0xnonce
但是发现算法模拟的跟 unidbg 生成的值不太一样啊
8a625bcf aced1023 8e8481d4 ba3325bd 5f825702 算法模拟的8a625bcf aced1023 f4a3e33f 5413c452 5f825702 unidbg生成的值也就是 c 和 d 的值不一样,那需要看一下这个地方到底做了什么事情

看 a[3],它放的是循环左移 30 位后的值,也就是原来 d 的值
看 a[2],它貌似并没有进行累加,还是上一次的循环左移 30 的结果,说明还是上一次的 c
改一下:
# sha1-v1import 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 t <= 15: f = (b & c) | ((~b) & d) k = 0x5a827999
elif t <= 19: f = b ^ c ^ d k = 0x6ED9EBA1
elif t <= 39: f = (b & c) | (b & d) | (c & d) k = 0x8F1BBCDC
elif t<=59: f = (b & c) | ((~b) & d) k = 0x5a827999
elif 60 <= t <= 79: f = b ^ c ^ d k = 0xCA62C1D6
S0 = madd(ROL4(a, 5), f, e, k, W[t]) S1 = ROL4(b, 30) if t ==79: a, b, d, c, e = S0, a, S1, c, d else: 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'0xnonce' s0 = sha1(s).hexdigest() print(s0) # 8a625bcfaced1023f4a3e33f5413c4525f825702这样结果就正常了
oauth_signature 的生成
调用函数
public String xAuthencode(){ List<Object> list = new ArrayList<>(10); list.add(vm.getJNIEnv()); list.add(0); DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null); list.add(vm.addLocalObject(context)); list.add(vm.addLocalObject(new StringObject(vm, "0xnonce"))); list.add(vm.addLocalObject(new StringObject(vm,""))); list.add(vm.addLocalObject(new StringObject(vm, "com.mfw.roadbook"))); list.add(vm.addLocalObject(DvmBoolean.valueOf(vm,true))); Number number = module.callFunction(emulator, 0x3998c, list.toArray()); String result = vm.getObject(number.intValue()).getValue().toString(); return result;}生成的值: NxLJNp+YzBa171KlJhiAKLhCQFg=
在之前我抓包的时候,下面就曾明晃晃的写了个 hmac-sha1,看来这个算法很可能跟 hmac-sha1 算法有关

到最后返回的值中,v32 是由 sub_3B694 这个子函数生成的
对这个地方 hook 一下,但是我发现我好像并没有发现有什么东西

好,那就换一种方式,在上面这些子函数中,都会用到一个 v38 的变量
从最开始的 sub_3A3F8 这个子函数开始看
mx0
>-----------------------------------------------------------------------------<[08:56:39 774]x0=unidbg@0xe4fff5c8, md5=2ca4bbdfa8fdf4c47ec1254be4233306, hex=0000000000000038cf5b628a2310edac3fe3a3f452c413540257825f000200000000000030786e6f6e63658000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000380000000080f6ffe400000000size: 1120000: 00 00 00 00 00 00 00 38 CF 5B 62 8A 23 10 ED AC .......8.[b.#...0010: 3F E3 A3 F4 52 C4 13 54 02 57 82 5F 00 02 00 00 ?...R..T.W._....0020: 00 00 00 00 30 78 6E 6F 6E 63 65 80 00 00 00 00 ....0xnonce.....0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0060: 00 00 00 38 00 00 00 00 80 F6 FF E4 00 00 00 00 ...8............^-----------------------------------------------------------------------------^mx1
>-----------------------------------------------------------------------------<[08:56:44 127]x1=RW@0x12453060, md5=8c8ee55660f1152f1e4ace04ed1e6f9a, hex=35633035333235643561653830623736633164323263663031656438363061652600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000size: 1120000: 35 63 30 35 33 32 35 64 35 61 65 38 30 62 37 36 5c05325d5ae80b760010: 63 31 64 32 32 63 66 30 31 65 64 38 36 30 61 65 c1d22cf01ed860ae0020: 26 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 &...............0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................^-----------------------------------------------------------------------------^参数 1 貌似是个不知名的字符串,不太确定是个啥
参数 2,看起来像密钥,因为 hmac-sha1 最开始进行初始化的时候就是对密钥进行处理
参数 3 是 0x21,可能是密钥的长度
继续往下面看,看一下 sub_3B168 这个子函数
mx0
>-----------------------------------------------------------------------------<[09:00:22 645]x0=unidbg@0xe4fff5c8, md5=57f1eabc91cb2d387ae6fc63977fa0d6, hex=0123456789abcdeffedcba9876543210f0e1d2c352c4135400000000000000000000000030786e6f6e63658000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000380000000080f6ffe400000000size: 1120000: 01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10 .#Eg........vT2.0010: F0 E1 D2 C3 52 C4 13 54 00 00 00 00 00 00 00 00 ....R..T........0020: 00 00 00 00 30 78 6E 6F 6E 63 65 80 00 00 00 00 ....0xnonce.....0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................0060: 00 00 00 38 00 00 00 00 80 F6 FF E4 00 00 00 00 ...8............^-----------------------------------------------------------------------------^mx1
>-----------------------------------------------------------------------------<[09:00:26 863]x1=unidbg@0xe4fff500, md5=53b7bc5d455a3879769da28f2284bac4, hex=03550603050403520357530e0654010055075204045550060753520e0006575310363636363636363636363636363636363636363636363636363636363636360020131200000000000000000000000020ddcd5c000000003a99d11e0000000016493f1f000000004016feff00000000size: 1120000: 03 55 06 03 05 04 03 52 03 57 53 0E 06 54 01 00 .U.....R.WS..T..0010: 55 07 52 04 04 55 50 06 07 53 52 0E 00 06 57 53 U.R..UP..SR...WS0020: 10 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 .6666666666666660030: 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 66666666666666660040: 00 20 13 12 00 00 00 00 00 00 00 00 00 00 00 00 . ..............0050: 20 DD CD 5C 00 00 00 00 3A 99 D1 1E 00 00 00 00 ..\....:.......0060: 16 49 3F 1F 00 00 00 00 40 16 FE FF 00 00 00 00 .I?.....@.......^-----------------------------------------------------------------------------^参数 1 看起来跟我输入的明文有关,可能是填充过后的值
参数 2 就是 hmac 标准的样式了,将密钥与 0x36,0x5c 进行异或操作,那我同样将这段与 0x36 异或回去,就能够得到密钥了
得到的刚好就是 5c05325d5ae80b76c1d22cf01ed860ae&
那可以尝试着来进行加密一下
37 打头的,那上面 sub_3B694 加密出来看不懂的那个字符串就是 hmac-sha1 加密的结果啊
好,继续往下面看
为什么得到的值,跟我们 unidbg 模拟出来的值不一样
往 sub_3B694 中看一下,发现刚好是在做 base64 的操作,尝试着进行编码一下

正好就能够得到加密值了
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 188 天
部分内容可能已过时
评论
还没有评论,来写第一条吧。