某读 数据包加密分析
flutter逆向
本文仅用于学习记录
app:5oWi6K+7{beihai_deleteIDMuMjEuMAo=
抓包
用小黄鸟抓一下登录包

但是发现抓不到包。改用reqable的增强式抓包(强制捕获所有的流量)成功抓取,发现password字段进行了加密

下面分析一下这个加密过程
反编译apk。结果发现这个app是用flutter混合开发的,怪不得小黄鸟抓不到包。

Flutter框架采用Dart 语言进行开发。
Dart语言标准库中的网络请求机制比较特殊,它不会通过用户设置的Wi-Fi代理来发送请求。如果app的网络请求代码使用了Dart标准库中的网络请求机制,常规的依赖代理的抓包工具就失效了
Dart SDK在Android 平台上,强制只信任系统目录下的证书。Flutter应用在运行时,默认不会信任用户自行安装的证书,只有当这些用户安装的证书被放置在Android系统的 /system/etc/security/cacerts目录中时,Flutter应用才会信任它们。这一规则是通过Dart源码中的 runtime/bin/security_context_linux.cc文件来实现的
除了使用reqable的增强模式,也可以通过hook脚本来绕过flutter网络请求机制
源码中session_verify_cert_chain函数会验证证书链,如果证书验证失败,会返回一个错误。对抗flutter机制的方法就是hook这个函数,将返回值改为true
先反编译libflutter.so,再通过ssl_client字段定位到函数session_verify_cert_chain


复制函数偏移,编写hook代码
function hook_dlopen() {
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var so_name = args[0].readCString();
if (so_name.indexOf("libflutter.so") >= 0) this.call_hook = true;
}, onLeave: function (retval) {
if (this.call_hook) hook_flutter2();
}
})
}
function hook_ssl_verify_result(address){
Interceptor.attach(address, {
onEnter: function (args) {
console.log("Disable SSL validation");
}, onLeave: function (retval) {
console.log("Retval: ", retval);
retval.replace(0x1);
}
})
}
function hook_flutter2() {
var addr = Module.findBaseAddress("libflutter.so");
var funcAddr = addr.add(0x5DC72C);
console.log("Target function address: ", funcAddr.toString());
hook_ssl_verify_result(funcAddr);
}
function main(){
hook_dlopen();
}
setImmediate(main);
hook之后使用小黄鸟成功抓到包

加密分析
既然是flutter开发的
那就祭出一手blutter(https://github.com/worawit/blutter)静态分析工具,分析libapp.so
因为直接反编译的结果没有类名函数名等具体的业务代码信息,如下图

运行blutter

ida反编译libapp.so,导入blutter分析结果

搜索关键字password,发现可疑函数,这个函数的偏移为0x723d00

blutter已经生成了hook libapp的脚本,在output文件夹里

修改一下onLibappLoaded函数,添加一个dump函数就可以用了
function dumpArgs(step, address, bufSize) {
var buf = Memory.readByteArray(address, bufSize)
console.log('Argument ' + step + ' address ' + address.toString() + ' ' + 'buffer: ' + bufSize.toString() + '\n\n Value:\n' +hexdump(buf, {
offset: 0,
length: bufSize,
header: false,
ansi: false
}));
console.log("Trying interpret that arg is pointer")
console.log("=====================================")
try{
console.log(Memory.readCString(ptr(address)));
console.log(ptr(address).readCString());
console.log(hexdump(ptr(address)));
}catch(e){
console.log(e);
}
console.log('')
console.log('----------------------------------------------------')
console.log('')
}
function onLibappLoaded() {
// xxx("remove this line and correct the hook value");
const fn_addr = 0x723d00; //此处修改为你想hook的函数地址
Interceptor.attach(libapp.add(fn_addr), {
onEnter: function () {
init(this.context);
let objPtr = getArg(this.context, 0);
const [tptr, cls, values] = getTaggedObjectValue(objPtr);
console.log(`${cls.name}@${tptr.toString().slice(2)} =`, JSON.stringify(values, null, 2));
},
onLeave: function (retval) {
dumpArgs(0, retval, 500);
}
});
}运行,通过结果可以发现就是这个函数无疑

来看看这个函数的内容

进入这个取哈希的函数,发现存在sha256和sha1两种

分别整个sha256和sha1的脚本跑一下
和app的加密结果对比发现代码里跑的是sha256

总结一下,password字段的加密方式就是简单使用了sha256哈希计算并进行 Base64 编码