flutter逆向——分析某软件响应解密逻辑

查看 96|回复 10
作者:yibaoshutiao   
前言
现在用flutter做跨端开发的越来越多,许多小(瑟瑟)软件也是更新技术栈了,但是关于flutter逆向的帖子还是太少了,大多停留在去除ssl校验这块,很少看到逆向出完整算法的流程。我在处理这个软件的时候也是走了很多弯路,总结一下希望能让更多人避坑吧。
过程
首先对软件进行抓包,能抓到内容,响应体为json,data字段为base64编码的加密数据,那就开始逆向了
linux下安装blutter,注意选个顺手的目录
[Shell] 纯文本查看 复制代码apt install python3-pyelftools python3-requests git cmake ninja-build     build-essential pkg-config libicu-dev libcapstone-dev
git clone --depth=1 https://github.com/worawit/blutter.git
然后把软件的libapp.so和libflutter.so放到输入目录,启动!
[Shell] 纯文本查看 复制代码python3 blutter.py ./input ./output
这里提一嘴,我在vps上可以直接开始处理,但是vps内存cpu不行会中断,在windows上用wsl处理会报错,我的解决办法是把vps上的目录拷贝过来运行
处理完成后进入output/asm,找一下代码,大部分目录都是第三方库,很容易识别,最后定位到output/asm/**/**_tools/net/http_resp_interceptor.dart
进入文件,直接搜索aes(),定位到aesDecryptEx函数。然后直接把代码扔给ai分析……没用啊,ai看不懂。不过ai给出了一点线索:过程中用到了响应体的前多少个字节。
大致观察代码,开头将数据进行base64解码,然后使用了一个硬编码字符串,接着进行了三次(数组处理+sha256),最后生成key和iv进行aes解密。这里放个片段。
[Asm] 纯文本查看 复制代码0x86d790: r0 = _GrowableList._ofEfficientLengthIterable()
  0x86d790: bl  #0x4a24bc  ; [dart:core] _GrowableList::_GrowableList._ofEfficientLengthIterable
0x86d794: stur  x0, [fp, #-0x18]
0x86d798: ldur  x16, [fp, #-0x10]
0x86d79c: stp   x16, x0, [SP]
0x86d7a0: r0 = addAll()
  0x86d7a0: bl  #0x4a18c8  ; [dart:core] _GrowableList::addAll
......
0x86d7c0: r16 = Instance__Sha256
  0x86d7c0: add   x16, PP, #0x11, lsl #12  ; [pp+0x11d50] Obj!_Sha256@b008f1
  0x86d7c4: ldr   x16, [x16, #0xd50]
0x86d7c8: stp   x0, x16, [SP]
0x86d7cc: r0 = convert()
  0x86d7cc: bl  #0xb8cb38  ; [package:crypto/src/hash.dart] Hash::convert
汇编代码看了一下午没看懂,直接开始hook
使用frIDA加载 output/blutter_frida.js,我这里用的是算法助手hook应用加载的脚本
具体hook哪些函数?我选择了_GrowableList和sha256的相关函数,比如addAll,Hash::convert。hook到的部分数据如下
[Plain Text] 纯文本查看 复制代码添加到list 前64 GrowableList@7800184dd9 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
添加到list 前64 GrowableList@7800184d49 = [183172,0,null,22,99,51,100,49,49,48,97,102,52,54,54,97]
添加到list 前64 GrowableList@7800184679 = [182276,0,null,67,99,51,100,49,49,48,97,102,52,54,54,97,48,53,56,100,55,98,97,99,54,48,55,48,98,57,53,50,99,99,53,101,251,194,50,252,255,34,20,84]
null
Unhandle class id: 46, TypeArguments
GrowableList@7800184d49 = [183172,0,null,22,99,51,100,49,49,48,97,102,52,54,54,97,48,53,56,100,55,98,97,99,54,48,null,null,186628,0,{"key":"Unhandle class id: 46, TypeArguments"},22,{"key":[99,51,100,49,49,48,97,102,52,54,54,97,48,53,56,100,55,98,97,99,54,48]},0,0,0,182276,0]
Digest@78001853a9 = {"off_8!_Uint8List@7800185339":[195,244,44,76,243,155,70,199,153,50,236,216,234,173,116,75,159,83,118,236,19,183,117,108,103,124,93,7,70,81,153,228]}
可以看到并没有预期的输入数据,而且sha256接受的是字节数组,这里的混合类型肯定不行。当时在这里卡了一天,后来想到不如去package:crypto/src/hash.dart看看改了什么。
在hash.dart中有如下代码
[Asm] 纯文本查看 复制代码0xb8cb74: r0 = _Sha256Sink()
  0xb8cb74: bl  #0xb8d06c  ; Allocate_Sha256SinkStub -> _Sha256Sink (size=0x34)
0xb8cb78: stur  x0, [fp, #-0x10]
0xb8cb7c: ldur  x16, [fp, #-8]
0xb8cb80: stp   x16, x0, [SP]
0xb8cb84: r0 = _Sha256Sink()
  0xb8cb84: bl  #0xb8cf48  ; [package:crypto/src/sha256.dart] _Sha256Sink::_Sha256Sink
0xb8cb88: r0 = _ByteAdapterSink()
  0xb8cb88: bl  #0xb8cf3c  ; Allocate_ByteAdapterSinkStub -> _ByteAdapterSink (size=0xc)
......
0xb8cc04: r0 = add()
  0xb8cc04: bl  #0x4fe4a4  ; [dart:convert] _ByteAdapterSink::add
转到sha256.dart发现了核心函数updateHash,hook后发现这个函数接受一个Uint32数组,询问ai得知sha256接受一个字节数组后会创建一个缓冲区,字节数组输入后按长度分割,不足则填充到64字节,那么hook这个函数的输入再转为Uint8就可以得到真实传入的数据形式了
写了个简单的js脚本转换之后对照每一步sha256返回的签名数组,最终解密逻辑如下。
[JavaScript] 纯文本查看 复制代码base64解码后的数据(data),固定字符串转字节数组(base)
hash1 = sha256(base+data.slice(0,12))
hash2 = sha256(hash1.slice(8,24)+base.slice(0,22))
hash3 = sha256(base.slice(22)+body,hash1.slice(8,24))
key = hash2.slice(0,8)+hash3.slice(8,24)+hash2.slice(-8)
iv = hash3.slice(0,4)+hash2.slice(12,20)+hash3.slice(-4)
番外
到这里最麻烦的地方就处理完了,但是这个app还有图片加密和请求头签名。
图片解密位于output\asm\**\**_tools\image\image_data_handle\image_crypto.dart,ai分析是使用固定字符串异或,但是长度为100。写了个脚本测试下发现解密后的图片还是损坏。抓包后和app缓存的已解密图片对比,原来只有前100个字节是加密的,后面都一样。
请求头加密使用了hmac,不过密钥是硬编码的。依旧是传入对象而不是字节数组进行hmac。大概看了一下调用链,顺便询问了ai,得知传入的对象被jsonEncode处理了,直接hook这个函数,得到完整的字符串,验证之后与抓包得到的结果一致。但是请求头不加签名依旧能返回数据,不是很理解。

数组, 代码

yibaoshutiao
OP
  


正己 发表于 2025-7-26 17:26
加一些图会更好哦
请求头不加签名依旧能返回数据说明后端验证不是很严谨

主要是我逆向的手段比较呆板,把frida脚本的hook地址改下,然后软件载入脚本,之后就直接启动应用了,应用初始化时就会请求数据。我直接上了hook,所以确实没啥图能放的,也基本没抓包
laoser   

很实用的
qq114168   


laoser 发表于 2025-7-25 13:11
很实用的

你这个小尾巴怎么弄的
sujlily   

牛人一枚
光彩影   

大佬厉害
zywe   

`许多小(瑟瑟)软件也是更新技术栈`这个目标驱动有点东西
xiaohan231   

过来学习一下
4everlove   

抓包后和app缓存的已解密图片对比,原来只有前100个字节是加密的,学到一个新的加密思路
635144044   

来学习一下
您需要登录后才可以回帖 登录 | 立即注册

返回顶部