某右APP sign-v2算法分析
文章目錄
1、前言
閒來無事,打算鞏固一下以前學的知識,偶然間發現某右sign算法變了,特開一貼用來記錄。(ps:本篇分析的APP版本爲5.4.8)
2、APP簡單分析
使用charles對目標app進行抓包,抓包結果如下:
可以發現有個sign的簽名字段,jadx反編譯app,全局搜索sign=,找到關鍵類如下:
使用frida hook a方法可知sign方法爲加簽方法,該方法爲native方法位於libnet_crypto.so中。firda 代碼如下:
var NetCrypto = Java.use("com.izuiyou.network.NetCrypto");
var String = Java.use("java.lang.String");
NetCrypto.sign.implementation = function(arg1, arg2){
var result = this.sign(arg1, arg2);
console.log(printBytes(arg2));
console.log(arg1, result);
return result;
}
3、SO層分析
使用ida打開libnet_crypto.so, 找到Jni_OnLoad函數,經過處理髮現動態註冊的函數位off_17D010處。
經確認該so對字符串進行了ollvm默認的加密。有以下3種處理方式:
3.1 hook register打印動態註冊的地址。
可以通過hook art.so 的register函數,打印動態註冊的地址。frida 代碼如下:
///<reference path='frida-gum.d.ts' />
// hook register 打印動態註冊的函數地址
function hook_register(){
// libart.so 所有導出函數表
var symbols = Module.enumerateSymbolsSync("libart.so");
var addr_register = null;
for(var i = 0; i < symbols.length; i++){
var symbol = symbols[i];
var method_name = symbol.name;
if(method_name.indexOf("art") >= 0){
if(method_name.indexOf("_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") >= 0){
addr_register = symbol.address;
}
}
}
// 開始hook
if(addr_register){
Interceptor.attach(addr_register, {
onEnter: function(args){
var methods = ptr(args[2]);
var method_count = args[3];
console.log("[RegisterNatives] method_count:", method_count);
for(var i = 0; i < method_count; i++){
var fn_ptr = methods.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var find_module = Process.findModuleByAddress(fn_ptr);
if(i == 0){
console.log("module name", find_module.name);
console.log("module base", find_module.base);
}
console.log("\t method_name:", methods.add(i * Process.pointerSize * 3).readPointer().readCString(), "method_sign:", methods.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString(), "method_fnPtr:", fn_ptr, "method offset:", fn_ptr.sub(find_module.base));
}
}, onLeave(retval){
}
})
}
}
function main(){
hook_register();
}
setImmediate(main);
使用方式
frida -U --no-pause -f package_name -l hook.js
3.2 使用frida打印程序運行時已解密的字符串
APP 在運行時,會自動解密字符串。可以使用frida 對解密後的字符串進行打印,frida代碼如下:
function print_string(addr){
var base_addr = Module.findBaseAddress("libnet_crypto.so");
if(base_addr){
console.log(base_addr.add(addr).readCString());
}
}
使用frida 附加到目標APP,調用 print_string(0x17D0C1) 可得到如下結果:
那麼與其對應的sub_49864+1 就是sign方法的地址。
3.3 使用unicorn還原字符串
被“孤挺花”混淆的字符串可以用unicorn仿真器進行修復,具體原理可參考大佬的博客:https://www.leadroyal.cn/?p=972
4、定位關鍵函數
sub_49864 f5 結果如下:
鄙人定位關鍵函數的方法是使用frida hook 輸入和輸出對比結果來進行定位。最終定位的關鍵函數爲:
sub_63CB8->sub_63C1C->sub_61A70->sub_61DBA
5、還原sign
函數sub_61DBA的cfg圖如下:
看到這個cfg圖, 當時覺得這不白給嗎,什麼混淆都沒有。 腦子一熱一邊動態調試,一遍翻譯arm指令。 花了點時間,翻譯了500多行arm指令瞬間覺得不對勁了,這尼瑪大部分都是指令替換膨脹代碼。 粗略計算了下,該函數大概有5000條指令,這尼瑪什麼時候是頭,頂不住頂不住,爲了頭髮着想,偷了個懶,直接f5 copy僞代碼,修改了一下,發現結果對上了,果斷放棄翻譯arm彙編,有興趣的可以試下翻譯。
f5的僞代碼,有大量類似於:
(~v5 & 0xF92342E3 | v5 & 0x6DCBD1C) ^ (HIDWORD(v4) & 0x6DCBD1C | ~HIDWORD(v4) & 0xF92342E3);
的運算,可以替換成
v5 ^ (HIDWORD(v4)
等形式。
(注:Z爲常量)
(~X & Z | X & ~Z) ^ (Y & ~Z| ~Y & Z); x ^ y
(~X & Z | X & ~Z) = X ^ ~Z = ~X ^ Z
推理過程:
(~X & Z | X & ~Z) ^ (Y & ~Z| ~Y & Z)
= X ^ Z ^ Y ^ Z
= X ^ Y
6、結果驗證
hook sign方法,獲取加密參數:
c運行結果圖:
7、雜談
逆過老版本的某右APP,當時的只有個sign加簽,post提交參數和返回結果還沒有aes加解密(懷念以前不穿衣服的你),整體難度應該算是容易的吧。老版本的sign沒記錯的話就是md5(待加簽的字符串進行變形),新版本的話,有點像是自己魔改的md5,不太確定,但整體感覺上是像。另外,aes加解密這塊好像是比sign難度高點,還沒去看。還發現個hook點可以讓app使用老接口,frida代碼:
var nd3 = Java.use("nd3");
var ia = Java.use("ia$a");
nd3.a.overload('java.lang.String').implementation = function(arg1){
return false;
}
ia.c.implementation = function(){
var result = this.c();
console.log(result);
return 1;
}
感覺該so是練習反混淆的好樣本,cfg不太複雜,想練習的朋友可以試試先trace,然後根據trace手動修復,再寫腳本還原。強烈建議各位膽大信息的大佬,去翻譯核心算法的arm彙編,試過的都說爽(逃)。