初试Flutter逆向

因为某些原因,需要逆向出某软件的 sign 生成算法。一开始没注意是 Flutter 写的,解压出libapp.so,打开 ghidra 就是一通搜索,无果看了一眼Exported Functions ,发现不太对劲,都是_kDartIsolateSnapshotInstructions一类的函数,搜了一下才知道原来是 Flutter 写的。

问题何在  

因为对 Flutter 不熟,以下内容来自于我非常浅薄的理解,可能有不准确之处。

Flutter 软件大部分逻辑都是写在 dart 里的,而 dart 代码在实际运行中都是在一个虚拟机内运行的,相当于天然就带了一层保护。

目前逆向工具也不多,比较好用的也就reFlutter,它会重新打包 apk,然后自动生成一份dump.dart文件,里面有所有函数相对_kDartIsolateSnapshotInstructions的偏移,还有一些静态变量(不确定)等,例如:

Library:'dart:core' Class: Object {
  Random _hashCodeRnd@0150898 = sentinel ;
 
  Function 'get:_hashCodeRnd@0150898': static static-getter. () => Random { 
		Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000003598
  }
 
  Function '_objectHashCode@0150898': static. (dynamic) => int { 
		Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000317148       
  }
  ...
}

此外还会自动将所有的请求都转发到设置的BurpSuite Proxy IP上,也不需要配置证书什么的,还是挺方便的,但是我没有找到不启用这个功能的方法,导致我其实不需要抓包也得强行打开 burp,否则所有请求都会超时。

具体分析  

抓包可以发现 sign 八成是 md5,因此在dump.dart里搜关键词,直接就找到了关键内容:

Library:'package:******/utils/md5.dart' Class: :: {
  String* appSecret = ********-****-****-****-************;
 
  Function 'generateMD5': static. (dynamic) => dynamic { 
			Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000002939d4
  }
}

Library:'package:******/utils/md5.dart', {
  Function 'generateMD5': static. (dynamic) => dynamic { 
			Code Offset: _kDartIsolateSnapshotInstructions + 0x00000000002939d4
  }
}

一上来就是一个赤裸裸的appSecret,想必和时间戳什么的拼接以下就是 sign 了,于是直接定位到标准库里真正计算的函数:

Function 'updateHash':. (dynamic, dynamic) => void { 
	Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000294160
}

然后frida hook 一下,打印出具体参数:

function hook_md5() {
    // 获取地址
    let kDartIsolateSnapshotInstructions = Module.findExportByName("libapp.so", "_kDartIsolateSnapshotInstructions")
    let updateHash = kDartIsolateSnapshotInstructions.add(0x0000000000294160); // 加上偏移量

    Interceptor.attach(updateHash, {
        // 打印参数
        onEnter(args) {
            try {
                console.log("updateHash arg\n", hexdump(args[0]), "\n");  
            } catch (error) {
                // 不知道为什么直接打印args会报错,也没有深究
                console.log("updateHash arg\n", args[0], "\n");
            }
        }, 
        // 打印返回值
        onLeave : function(retval){ 
            try {
                console.log("updateHash return\n", hexdump(retval), "\n");  
            } catch (error) {
                console.log("updateHash return\n", retval, "\n");
            }
        }
    })
}

setImmediate(() => {
    hook_md5();
})

结果完全就是md5(timestamp+appSecret),非常朴素。

打印结果

参考  

基本上完全是从Flutter APP逆向实践-初级篇学的,尤其是脚本,非常感谢。