DFA還原白盒AES密鑰

本期內容是關于某app模擬登錄的,涉及的知識點比較多,有unidbg補環境及輔助還原算法,ida中的md5以及白盒aes,fart脫殼,frida反調試

本章所有樣本及資料均上傳到了123云盤

llb資料官方版下載丨最新版下載丨綠色版下載丨APP下載-123云盤

目錄

首先抓包

fart脫殼

加密位置定位

frida反調試

unidbg搭架子

補環境

還原算法

DFA還原白盒AES密鑰

小坑

md5

完整算法

本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請聯系作者立即刪除!

總結

最后

首先抓包

image-20240301191136716

看login請求,表單和響應都是大長串,猜測是對稱加密算法或者是非對稱,對稱常見的有des和aes,非對稱常見的有rsa.

fart脫殼

正常流程下應該是拖到jadx中反編譯一下,但是目標app使用了梆梆企業加固

image-20240301191604629

我換了一部由fart脫殼機定制的pixel 4后成功脫殼,后續我會把脫殼的dex放到網盤里,所以對脫殼不了解的可以略過脫殼這個步驟

寒冰的fart脫殼機github地址:GitHub - hanbinglengyue/FART: ART環境下自動化脫殼方案

把脫下來的dex文件pull到電腦上

image-20240301193322261

對比下脫殼前后的反編譯結果

image-20240301200805772

加密位置定位

接下來是定位加密位置了

嘗試搜索"sd"

image-20240301200940767

框中的可能性比較大,其他幾個類名都是android aliyun google tencent這種系統文件或者第三方廠商的,框中的包含類名以及retrofit框架

這個是目標字段的可能性很大,點進去看看,然后查找用例

image-20240301201431561

右下角框中的有一個decrypt函數,應該是響應的解密邏輯,那上面的應該是加密函數了

image-20240301201639119

點進去然后復制frida片段

function call(){Java.perform(function (){let CheckCodeUtils = Java.use("com.cloudy.linglingbang.model.request.retrofit2.CheckCodeUtils");
CheckCodeUtils["encrypt"].implementation = function (str, i) {console.log(`CheckCodeUtils.encrypt is called: str=${str}, i=${i}`);let result = this["encrypt"](str, i);console.log(`CheckCodeUtils.encrypt result=${result}`);return result;
};
})
}

frida反調試

frida注入 frida -UF -l hook.js

以attach方式啟動frida后報錯無法附加進程,這里我們使用spwan方式啟動即可

image-20240301202447875

換成spwan方式后還是報錯了,應該還有檢測frida-server

image-20240301203133715

換成葫蘆娃形式的試試

image-20240301203415751

image-20240301203520112

成功了,接下來就是發個包看看有沒有結果

image-20240301195916886

對比下發現結果差不多就是hook的結果把+改成空格就是sd的值了

image-20240301203709699

接著分析jadx中的函數,checkcode點進去

image-20240301203807990

可以看到目標函數返回null,和hook的結果不一樣,并且jadx給出了警告,不知道是脫殼脫的不全還是jadx的問題,后續可以用jeb試試,jeb的反編譯能力比jadx強

同時可以看到下面有兩個native函數,checkcode,和decheckcode,嘗試hook checkcode函數

image-20240301204253358

同樣有結果

這兩個native函數加載自libencrypt.so

image-20240301204420922

這里我選擇32位的so,拖到ida32中搜索java,發現是靜態注冊(如果是動態注冊還可以hook libart.so來找導出函數)

image-20240301204635687

unidbg搭架子

接下來是unidbg模擬執行

搭架子

package com;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.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.StringObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;import java.io.File;
import java.util.ArrayList;
import java.util.List;public class demo2 extends AbstractJni {private final AndroidEmulator emulator;private final VM vm;private final Module module;private final Memory memory;demo2(){// 創建模擬器實例,進程名建議依照實際進程名填寫,可以規避針對進程名的校驗emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.cloudy.linglingbang").build();// 獲取模擬器的內存操作接口memory = emulator.getMemory();// 設置系統類庫解析memory.setLibraryResolver(new AndroidResolver(23));// 創建Android虛擬機,傳入APK,Unidbg可以替我們做部分簽名校驗的工作vm = emulator.createDalvikVM(new File("unidbg-android/apks/llb/llb.apk"));// 設置JNIvm.setJni(this);// 打印日志vm.setVerbose(true);// 加載目標SODalvikModule dm = vm.loadLibrary(new File("unidbg-android/apks/llb/libencrypt.so"), true);//獲取本SO模塊的句柄,后續需要用它module = dm.getModule();// 調用JNI OnLoaddm.callJNI_OnLoad(emulator);};public String callByAddress(){// args listList<Object> list = new ArrayList<>(5);// jnienvlist.add(vm.getJNIEnv());// jclazzlist.add(0);// str1list.add(vm.addLocalObject(new StringObject(vm, "mobile=13535535353&password=fjfjfjffk&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token")));// intlist.add(2);// str2list.add(vm.addLocalObject(new StringObject(vm, "1709100421650")));Number number = module.callFunction(emulator, 0x13A19, list.toArray());String result = vm.getObject(number.intValue()).getValue().toString();System.out.println("======encrypt:"+result);return result;};public static void main(String[] args) {demo2 llb = new demo2();
//		llb.callByAddress();}
}

補環境

運行報錯,currentActivityThread?通常用于一些需要獲取全局上下文或執行一些與應用程序狀態相關的操作的場景

image-20240301205841135

補上,這里沒什么好說的,孰能生巧

@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature){case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{return vm.resolveClass("android/app/ActivityThread").newObject(null);}}return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);}

接著運行,SystemProperties中的get像是在獲取系統的某個屬性

image-20240301210325785

case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{String arg = varArg.getObjectArg(0).getValue().toString();System.out.println("SystemProperties get arg:"+arg);}

image-20240301210629362

獲取手機序列號的

image-20240301210845607

adb shell getprop ro.serialno

image-20240301210921431

完整的補上

case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{String arg = varArg.getObjectArg(0).getValue().toString();System.out.println("SystemProperties get arg:"+arg);if(arg.equals("ro.serialno")){return new StringObject(vm, "9B131FFBA001Y5");}}

后面的環境不說了,大概也是這樣的流程,遇到不會的就google一下或者問問ai,我這里就直接貼一下代碼了

package com;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;public class demo2 extends AbstractJni {private final AndroidEmulator emulator;private final VM vm;private final Module module;private final Memory memory;demo2(){// 創建模擬器實例,進程名建議依照實際進程名填寫,可以規避針對進程名的校驗emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.cloudy.linglingbang").build();// 獲取模擬器的內存操作接口memory = emulator.getMemory();// 設置系統類庫解析memory.setLibraryResolver(new AndroidResolver(23));// 創建Android虛擬機,傳入APK,Unidbg可以替我們做部分簽名校驗的工作vm = emulator.createDalvikVM(new File("unidbg-android/apks/llb/llb.apk"));// 設置JNIvm.setJni(this);// 打印日志vm.setVerbose(true);// 加載目標SODalvikModule dm = vm.loadLibrary(new File("unidbg-android/apks/llb/libencrypt.so"), true);//獲取本SO模塊的句柄,后續需要用它module = dm.getModule();// 調用JNI OnLoaddm.callJNI_OnLoad(emulator);};public String callByAddress(){// args listList<Object> list = new ArrayList<>(5);// jnienvlist.add(vm.getJNIEnv());// jclazzlist.add(0);// str1list.add(vm.addLocalObject(new StringObject(vm, "mobile=13535535353&password=fjfjfjffk&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token")));// intlist.add(2);// str2list.add(vm.addLocalObject(new StringObject(vm, "1709100421650")));Number number = module.callFunction(emulator, 0x13A19, list.toArray());String result = vm.getObject(number.intValue()).getValue().toString();System.out.println("======encrypt:"+result);return result;};public static void main(String[] args) {demo2 llb = new demo2();llb.callByAddress();}@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature){case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{return vm.resolveClass("android/app/ActivityThread").newObject(null);}case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{String arg = varArg.getObjectArg(0).getValue().toString();System.out.println("SystemProperties get arg:"+arg);if(arg.equals("ro.serialno")){return new StringObject(vm, "9B131FFBA001Y5");}}}return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);}@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {switch (signature){case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":{
//                System.out.println("22222");return vm.resolveClass("android/app/ContextImpl").newObject(null);}case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;": {return vm.resolveClass("android/content/pm/PackageManager").newObject(null);}case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{String arg = varArg.getObjectArg(0).getValue().toString();
//                System.out.println("getSystemService arg:"+arg);return vm.resolveClass("android.net.wifi").newObject(signature);}case "android/net/wifi->getConnectionInfo()Landroid/net/wifi/WifiInfo;":{return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);}case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":{return new StringObject(vm, "02:00:00:00:00:00");}}return super.callObjectMethod(vm, dvmObject, signature, varArg);}@Overridepublic DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {switch (signature){case "android/os/Build->MODEL:Ljava/lang/String;":{return new StringObject(vm, "Pixel 4 XL");}case "android/os/Build->MANUFACTURER:Ljava/lang/String;":{return new StringObject(vm, "Google");}case "android/os/Build$VERSION->SDK:Ljava/lang/String;":{return new StringObject(vm, "29");}}return super.getStaticObjectField(vm, dvmClass, signature);}
}

再次運行下,出結果了

image-20240301211314753

但是怎么驗證結果是否正確呢,我這里想著是把結果拿去解密看看,代碼如下

package com;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;public class demo2 extends AbstractJni {private final AndroidEmulator emulator;private final VM vm;private final Module module;private final Memory memory;demo2(){// 創建模擬器實例,進程名建議依照實際進程名填寫,可以規避針對進程名的校驗emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.cloudy.linglingbang").build();// 獲取模擬器的內存操作接口memory = emulator.getMemory();// 設置系統類庫解析memory.setLibraryResolver(new AndroidResolver(23));// 創建Android虛擬機,傳入APK,Unidbg可以替我們做部分簽名校驗的工作vm = emulator.createDalvikVM(new File("unidbg-android/apks/llb/llb.apk"));// 設置JNIvm.setJni(this);// 打印日志vm.setVerbose(true);// 加載目標SODalvikModule dm = vm.loadLibrary(new File("unidbg-android/apks/llb/libencrypt.so"), true);//獲取本SO模塊的句柄,后續需要用它module = dm.getModule();// 調用JNI OnLoaddm.callJNI_OnLoad(emulator);};public String callByAddress(){// args listList<Object> list = new ArrayList<>(5);// jnienvlist.add(vm.getJNIEnv());// jclazzlist.add(0);// str1list.add(vm.addLocalObject(new StringObject(vm, "mobile=13535535353&password=fjfjfjffk&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token")));// intlist.add(2);// str2list.add(vm.addLocalObject(new StringObject(vm, "1709100421650")));Number number = module.callFunction(emulator, 0x13A19, list.toArray());String result = vm.getObject(number.intValue()).getValue().toString();System.out.println("======encrypt:"+result);return result;};public static void main(String[] args) {demo2 llb = new demo2();llb.callByAddress();llb.decrtpy("Mhub8kSp2n38SHF4COj57zjesFrzCIB2JiH76iCwZZffL3Y4+1/fq1uEDKKWe4yAwiacSVxXNSq1sWN5TwtfHaVgxpOREVGT2+qZEZFkvjP1GaxPCPP2jwuy4x3GvPgHl2NhG2kpsfcXHHQK9HJ5iBdtO44QdDO0vtgqU9MGGb+3q+HJwKlgfWJZj24t8HOSypJNigdCXbUEC6HGEhZhAhMX+Za1lffLlxUouhVh8rzKyESEF97li1h1vTbEf6TJyMbbdEpxh355FbxV9wZgorCa93rDfu+bsVLDbQaAF1TcacxnokoS/yv92hYaqzwzSX3UdH5oQutjW6A4gH1Zk/1Yb3k+IHofvc6Lfm+cxrLHLDtsus9SM/4+2oqsE7tsbgUny37/PQXtUJEOwebDtpz5oYxPgEIbLKIHvptVKwh4=");}@Overridepublic DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {switch (signature){case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":{return vm.resolveClass("android/app/ActivityThread").newObject(null);}case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{String arg = varArg.getObjectArg(0).getValue().toString();System.out.println("SystemProperties get arg:"+arg);if(arg.equals("ro.serialno")){return new StringObject(vm, "9B131FFBA001Y5");}}}return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);}@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {switch (signature){case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":{
//                System.out.println("22222");return vm.resolveClass("android/app/ContextImpl").newObject(null);}case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;": {return vm.resolveClass("android/content/pm/PackageManager").newObject(null);}case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":{String arg = varArg.getObjectArg(0).getValue().toString();
//                System.out.println("getSystemService arg:"+arg);return vm.resolveClass("android.net.wifi").newObject(signature);}case "android/net/wifi->getConnectionInfo()Landroid/net/wifi/WifiInfo;":{return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);}case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":{return new StringObject(vm, "02:00:00:00:00:00");}}return super.callObjectMethod(vm, dvmObject, signature, varArg);}@Overridepublic DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {switch (signature){case "android/os/Build->MODEL:Ljava/lang/String;":{return new StringObject(vm, "Pixel 4 XL");}case "android/os/Build->MANUFACTURER:Ljava/lang/String;":{return new StringObject(vm, "Google");}case "android/os/Build$VERSION->SDK:Ljava/lang/String;":{return new StringObject(vm, "29");}}return super.getStaticObjectField(vm, dvmClass, signature);}public void decrtpy(String str){// args listList<Object> list = new ArrayList<>(5);// jnienvlist.add(vm.getJNIEnv());// jclazzlist.add(0);// strlist.add(vm.addLocalObject(new StringObject(vm, str)));// intNumber number = module.callFunction(emulator, 0x165E1, list.toArray());String result = vm.getObject(number.intValue()).getValue().toString();System.out.println("======decrypt:"+result);}
}

運行結果如下,好像不太正常

image-20240301211546300

從ida中的decheckcode點進去看看

image-20240301213154479

放回的結果異常,說明走了異常的分支,看樣子像是返回的是26行的值,判斷!v6的值是否為真,v6來自上面的sub_138AC函數,點進去看看

image-20240301213454413

中間的sub_ED04是一個很大的函數,看這像是檢測某種環境

image-20240301213630891

往下滑可以看到像是md5的64輪運算,和結尾解密得到的32位數據對應上了,所以說程序大概率是走了這個分支后直接返回了數據

image-20240301213635052

如果是這樣的話就好辦了

image-20240301213904273

直接在v6的地方取反就好了,看下此次的匯編代碼

image-20240301214001551

是一個條件跳轉,CBNZ意思是如果r0寄存器的值不為0就跳到loc_16610處,取反的指令就是CBZ(少了個N not),為0就跳

拿到hex轉arm網站上看看指令,20 B9對應的是cbnz r0, #0xc

image-20240301214423558

所以我們需要的就是cbz r0, #0xc

image-20240301214520185

把20 B9改成20 B1就可以了,比較原始的方式就是用ida或者010editor改,unidbg也提供了patct的方式直接在程序執行前改機器碼

ida和010editor改的方式就不說了,網上有教程,unidbg中這樣改

public void patch(){UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x16604);byte[] code = new byte[]{(byte) 0x20, (byte) 0xB1};//直接用硬編碼改原so的代碼:  4FF00109pointer.write(code);}

在調用callByAddress函數之前調用patch就可以了

image-20240301215035864

解密結果也是出來了,可以看到有手機號,密碼還有一些設備信息

還原算法

接下來就是unidbg輔助還原算法了

前面在加密函數的位置看到了aes字眼,所有猜測使用了aes加密

image-20240301215500397

還原aes加密需要確認密鑰 加密模式(ecb cbc等等) 是否有iv,填充方式,接下來就是漫長的猜測驗證再猜測的過程了,利用unidbg可以console debugger的優點,可以非常方便的還原算法

由于加密函數快3000多行,我這里就說大概得關鍵位置了,如果寫的太細內容就太多了

image-20240301220533659

結合著ida靜態分析和unidbg動態調試可以猜測2884行應該是進行aes加密的,并且后續進行了base64編碼

image-20240301220808339

點進去發現來到了.bss段, .bss段是用來存放程序中未初始化的全局變量的一塊內存區域

看下此次的匯編代碼

image-20240301220929018

BLX R3 意思是跳轉到寄存器?R3?中存儲的地址處執行,所以在unidbg中0x163FE下斷,看看R3寄存器的地址

debugger.addBreakPoint(module.base+0x163FE);

image-20240301221300745

斷在0x163FE處了,前面的0x400是加上了unidbg的基地址,可以看到R3的地址減去基地址也就是后面的地址是0x5a35,再減去thumb的地址加1也就是0x5a34

ida中按G跳轉到0x5a34

image-20240301221547257

可以看到aes的具體邏輯就在這里面的幾個函數中,最后的WBACRAES128_EncryptCBC貌似是在說white box aes128 cbc模式

如果是這樣的話,由于白盒aes11個輪秘鑰嵌在程序里,很難直接提取出,需要用dfa(差分故障攻擊)獲取到第10輪的秘鑰,再利用aes_keyschedule這個模塊還原出主密鑰

WBACRAES128_EncryptCBC點進去

image-20240301222610632

可以看到首先對明文進行了填充,往下滑

image-20240301222847886

WBACRAES_EncryptOneBlock視乎是運算的主體,點進去看看

image-20240301222954843

這里因為我每個地址都下斷看了下參數值,實際操作過程需要一步步驗證才能走到這

再點進去

image-20240301223140975

這里ida f5出來的看不太懂,看看匯編視圖

image-20240301223338118

可以看到結尾跳轉到R4寄存器指向的地址,unidbg中下斷看下

debugger.addBreakPoint(module.base+0x5836);

image-20240301223518940

所以最終會跳到0x4dcc位置處,為什么要-1上面也說過了,跳到0x4dcc去看看

image-20240301224003849

這里會判斷i=9的時候跳出循環,PrepareAESMatrix中Matrix是矩陣的意思,所有這個函數應該是對state數據進行矩陣運算

image-20240301224108139

aes的1-9輪和第10輪不一樣,第十輪少了一個列混淆運算

為了方便分析秘鑰,我讓unidbg在aes輸入明文的地方修改寄存器的值,這樣加密的結果就是16字節的,如果直接修改unidbg的入參的話,由于后續會拼上環境參數二導致參數太長

debugger.addBreakPoint(module.base+0x5A34, new BreakPointCallback() {@Overridepublic boolean onHit(Emulator<?> emulator, long address) {String fakeInput = "hello";int length = fakeInput.length();MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));// 修改r0為指向新字符串的新指針emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);return true;}});

接下來在aes加密結束后的結果是多少

    debugger.addBreakPoint(module.base+0x4DCC, new BreakPointCallback() {RegisterContext context = emulator.getContext();@Overridepublic boolean onHit(Emulator<?> emulator, long address) {emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {//onleave@Overridepublic boolean onHit(Emulator<?> emulator, long address) {return false;}});return true;}});

由于這個WBACRAES_EncryptOneBlock函數結束的時候寄存器中的地址已經不是原先的用來存返回值的地址了,所有需要提前hook看一下入參時目標參數的地址,代函數執行完直接打印這個地址就是結果了,這里是0xbffff50c m0xbffff50c可以直接看內存中的值

image-20240301225229367

所以正確的密文是57b0d60b1873ad7de3aa2f5c1e4b3ff6

接下來進行dfa攻擊(差分故障攻擊),這里需要熟悉aes算法的細節,我這里就不介紹了,感興趣的去龍哥的知識星球學習一下

故障注入的時機是倒數兩次列混淆之間,也就是第八輪以及第九輪運算中兩次列混淆之間的時機

image-20240301230244942

這里的s應該就是state塊

debugger.addBreakPoint(module.base+0x4E2A, new BreakPointCallback() {int round = 0;UnidbgPointer statePointer = memory.pointer(0xbffff458);@Overridepublic boolean onHit(Emulator<?> emulator, long address) {round += 1;System.out.println("round:"+round);if (round % 9 == 0){statePointer.setByte(randInt(0, 15), (byte) randInt(0, 0xff));}return true;//返回true 就不會在控制臺斷住}
});

DFA還原白盒AES密鑰

接下來就是取多次故障密文了

import?phoenixAES
with?open('tracefile',?'wb') as t:??# 第一行是正確密文 后面是故障密文t.write("""57b0d60b1873ad7de3aa2f5c1e4b3ff6
57b0d6a41873737de3892f5c2a4b3ff6
5720d60baf73ad7de3aa2f9b1e4b02f6
57b0f20b18f3ad7daeaa2f5c1e4b3f86
8db0d60b1873ad2fe3aa365c1eab3ff6
e2b0d60b1873ad5be3aafa5c1e1b3ff6
57b04e0b1812ad7d89aa2f5c1e4b3fa7
57d1d60b3773ad7de3aa2f8b1e4b2ff6
bcb0d60b1873ad21e3aa155c1e3d3ff6
57b0bb0b1885ad7d4aaa2f5c1e4b3f29
3ab0d60b1873ad67e3aac65c1e193ff6
57b0d6531873af7de3302f5c964b3ff6""".encode('utf8'))
phoenixAES.crack_file('tracefile', [],?True,?False,?3)

image-20240301231448670

拿到結果了

最后用aes_keyschedule把主密鑰也就是初始秘鑰還原出來了,F6F472F595B511EA9237685B35A8F866

image-20240301231607021

把剛開始的密文拿到CyberChef嘗試解一下,因為cbc模式需要iv,所以先用ecb模式,cbc模式比ecb模式多的就是cbc模式需要每個明文分組先和上個分組的密文塊進行異或,由于第一組沒有上個分組的密文塊,所以需要一個初始化向量IV

image-20240302135202423

上面符號WBACRAES128_EncryptCBC說的是cbc加密模式,但這個符號不一定可信,如果使用的是cbc模式,解出來的結果就是明文塊和iv異或的值(矩陣異或)

image-20240302135745654

后面全是0,如果是cbc模式下,明文塊和iv異或了,由于是矩陣異或,如果填充方式是pkcs7,就意味著iv的后面幾位是68656c6c6f填充后的

68656c6c6f0b0b0b0b0b0b0b0b0b0b0b后面幾位,也就是0b0b0b0b0b0b0b0b0b0b0b,如果這樣的話明文一變填充的數據也變了,可能是01-0f中的任何一個,這樣iv的值也不固定,顯然在這種情況下就太復雜了.

image-20240302135949572

所以我認為應該是ecb模式下使用了Zero Padding模式,全部填充0直到一個分組長度

為了驗證猜想,在InsertCBCPadding函數結束時打印處理過的state塊,unidbg中下斷

debugger.addBreakPoint(module.base+0x58A0); //m0x40321000

改變輸入后發現后面也還是0,也就驗證了采用的是Zero Padding模式,并不是常見的pkcs7模式

image-20240302141241131

由于CyberChef中默認是pkcs7填充,所以把模式調成nopadding,這樣解密出來的結果就是未填充的一個分組長度了,也驗證了上面的Zero Padding模式

image-20240302141627145

這也就是說上面的cbc模式也是錯誤的,而是ecb模式

嘗試加密一下明文和密文對比下

image-20240302142229008

正常的密文是Mhub8kSp2n38SHF4COj57zjesFrzCIB2JiH76iCwZZffL3Y4+1/fq1uEDKKWe4yAwiacSVxXNSq1sWN5TwtfHaVgxpOREVGT2+qZEZFkvjP1GaxPCPP2jwuy4x3GvPgHl2NhG2kpsfcXHHQK9HJ5iBdtO44QdDO0vtgqU9MGGb+3q+HJwKlgfWJZj24t8HOSypJNigdCXbUEC6HGEhZhAhH9QOWkbD6iDkO4mpB0xjvRurFugh+t9P3AeXJeHdhF+MnCXXj3BGlfUgi2qCvoWxYajx2sUcZkXpNbAFbj7VaAlG2ytQnO/L0aZr+SlzTxb90PoLU2VBp98GXNt0ozObSaCwO41UlmZPcKZrr9sxf32nwmoEmUwoTXe14aks2nj72zo5kz8GXyfzh2f6mddZQ==

image-20240302142653707

對比一下,除了正常密文前面多了個M,以及開頭有一段相同的,后面都不一樣,于是我嘗試能不能解密一下

image-20240302165238312

可以看到只解密出了前16個字節,到這里我就感覺有點不太對了,一般來說開發人員不會亂寫,如果后續他維護起來也比較麻煩,除非是那種故意寫出來迷惑逆向人員的,但前面的aes算法他又暴露了出來,所以我感覺上面的推論可能有點問題,也就是說可能真的是cbc模式.如果是ecb模式下由于分組加密,每個分組單獨加密,互不關聯,能解第一組的話按理后面的也能解.但如果是cbc模式下每個明文分組先和上個分組的密文塊進行異或,直接放到ecb模式下肯定解不出來,那為什么可以解出來第一組呢?我們先看加密模式下,第一個分組下明文和iv異或后進行后續加密,如果只解密第一組則不需要在cbc模式下,ecb就可以,并且解密出來的結果是明文和iv異或的結果,也就是說明文和iv異或后還是明文,a異或b得到a,只有一種情況,b全為0,也就是說iv是00000000000000000000000000000000

image-20240302170520902

看看結果完全正常,也就是說上面的推論有問題,我們再來仔細看看上面的推論

image-20240302171310044

我們否定了pkcs7填充方式,上面用了兩個如果,并不能否定cbc模式,如果是cbc模式下的zero padding模式再來看看,解密結果是68656c6c6f0000000000000000000000,這種情況下68656c6c6f(hello的hex形式)zero padding后是68656c6c6f0000000000000000000000,再和iv 00000000000000000000000000000000異或后還是它本身68656c6c6f0000000000000000000000,這樣的話就說的通了.所以正確的加密模式應該是aes128-cbc模式-zero _padding填充

key為F6F472F595B511EA9237685B35A8F866,iv為00000000000000000000000000000000

小坑

這里有個坑,當我把明文用上面的加密模式加密一遍,發現結果不對,CyberChef中默認是pkcs7填充,如果能完全解密就說明就是pkcs7填充,可是我們上面的推論也每錯啊!!!別急,聽我細說.

我把之前的修改r0為指向新字符串的新指針注釋掉,采用原始的明文進行填充,這是填充前,304字節剛好19輪

image-20240302195053533

InsertCBCPadding執行后

image-20240302195343422

末尾填充了3個03,這正是pkcs7的填充模式,那為什么上面用hello的明文填充后后面是0呢,這個我也不太清楚這個修改r0寄存器指向新指針的操作,看下面的代碼

debugger.addBreakPoint(module.base+0x5A34, new BreakPointCallback() {@Overridepublic boolean onHit(Emulator<?> emulator, long address) {String fakeInput = "hello";int length = fakeInput.length();MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));// 修改r0為指向新字符串的新指針emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);return true;}
});

這里我用python aes存算計算了如果使用zero padding模式加密得到的結果也正是最開始的密文57b0d60b1873ad7de3aa2f5c1e4b3ff6

image-20240302195825492

說明上面的推論都沒有錯,只不過是修改r0為指向新字符串的新指針后經過InsertCBCPadding并沒有完成pkcs7填充,但是正常的明文是經過了pkcs7填充的,這里我也不清楚是為什么,但肯定和這個修改r0為指向新字符串的新指針有很大關系.

所以正確的加密模式應該是aes128-cbc模式-pkcs7填充

key為F6F472F595B511EA9237685B35A8F866,iv為00000000000000000000000000000000

寫到這里我本來想把上面的錯誤推論刪掉,但是想了想,并不是只有得到正確的結果才會讓人進步,所以我保留了,相信每個讀者逆向的時候都會有自己的思路,我想我把自己的思路比較完整的寫出來了.

md5

再來看上面的明文塊

mobile=13535535353&password=fjfjfjffk&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token&ostype=ios&imei=unknown&mac=02:00:00:00:00:00&model=Pixel 4 XL&sdk=29&serviceTime=1709100421650&mod=Google&checkcode=6be9743e9f528df4cd9465a97cb645a1

前面幾個應該是可以固定的,后面有個checkcode,按單詞的意思就是檢查代碼,中文翻譯過來可以理解為驗簽,防止aes被人破解的情況下如果明文被篡改需要把這個值一并改掉,否則不給通過.

接下來重點看看這個checkcode,32位首先猜md5,上面的圖中也看到了疑似md5的64輪運算

image-20240302172240301

這里我先對明文加密了一下,但是不確定是否有鹽值

6be9743e9f528df4cd9465a97cb645a1 明文中的結果
7cb645a19f528df4cd9465a96be9743e md5后的結果

這樣一對比好像中間一串是一樣的,拆分看看

6be9743e 9f528df4 cd9465a9 7cb645a1
7cb645a1 9f528df4 cd9465a9 6be9743e

明眼人都能看出來前4個字節和后4個字節調換了順序,這樣的話也不需要去ida中看代碼了,直接就得到了結果,這確實有點運氣的成分在,但是運氣也是實力的一部分啊!

完整算法

替換你自己的mobile和password即可,友情提醒,本文章中所有內容僅供學習交流使用,不用于其他任何目的,請勿對目標app發生大規模請求,否則后果自負!!!

本文章未經許可禁止轉載,禁止任何修改后二次傳播,擅自使用本文講解的技術而導致的任何意外,作者均不負責,若有侵權,請聯系作者立即刪除!
import base64
from Crypto.Cipher import AES
import requests
import hashlib
from Crypto.Util.Padding import unpaddef __pkcs7padding(plaintext):block_size = 16text_length = len(plaintext)bytes_length = len(plaintext.encode('utf-8'))len_plaintext = text_length if (bytes_length == text_length) else bytes_lengthreturn plaintext + chr(block_size - len_plaintext % block_size) * (block_size - len_plaintext % block_size)
def aes_encrypt(mobile,password):_str = f'mobile={mobile}&password={password}&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=eu4acofTmb&response_type=token&ostype=ios&imei=unknown&mac=02:00:00:00:00:00&model=Pixel 4 XL&sdk=29&serviceTime=1709100421650&mod=Google'checkcode = hashlib.md5(_str.encode()).hexdigest()swapped_string = checkcode[24:] + checkcode[8:24] + checkcode[:8]plaintext = _str+'&checkcode='+swapped_stringkey = bytes.fromhex('F6F472F595B511EA9237685B35A8F866')iv = bytes.fromhex('00000000000000000000000000000000')aes = AES.new(key, AES.MODE_CBC, iv)content_padding = __pkcs7padding(plaintext)  # 處理明文, 填充方式encrypt_bytes = aes.encrypt(content_padding.encode('utf-8'))  # 加密return 'M' + str(base64.b64encode(encrypt_bytes), encoding='utf-8')  # 重新編碼
def decrypt(text):ciphertext = base64.b64decode(text)key = bytes.fromhex('F6F472F595B511EA9237685B35A8F866')iv = bytes.fromhex('00000000000000000000000000000000')cipher = AES.new(key, AES.MODE_CBC, iv)plaintext = cipher.decrypt(ciphertext)decrypted_data = unpad(plaintext, AES.block_size, style='pkcs7')return decrypted_data.decode("utf-8")
def login():headers = {"channel": "yingyongbao","platformNo": "Android","appVersionCode": "1481","version": "V8.0.14","imei": "a-759f0c27ef7fe3b6","imsi": "unknown","deviceModel": "Pixel 4","deviceBrand": "google","deviceType": "Android","accessChannel": "1",# "oauthConsumerKey": "2019041810222516127","timestamp": "1709100421649","nonce": "PCpLXbXts7","Content-Type": "application/x-www-form-urlencoded; charset=utf-8","Host": "api.00bang.cn","User-Agent": "okhttp/4.9.0"}url = "https://api.00bang.cn/llb/oauth/llb/ucenter/login"mobile = ''  # 換成你自己的password = '' # 換成你自己的sd = aes_encrypt(mobile,password)print(sd)data = {"sd": sd}response = requests.post(url, headers=headers, data=data,verify=False)print('加密結果:',response.text)print(response)print('解密結果',decrypt(response.json()['sd'][1:]))
if __name__ == '__main__':login()

總結

1由于本節涉及知識點重多,有很多講解不到位的地方還請在評論區指出!

2本章所涉及的材料都上傳在網盤了,https://www.123pan.com/s/4O7Zjv-6MFBd.html,剛興趣的自行還原驗證,相信對你的安卓逆向水平一定會有提升!

3js逆向轉安卓逆向,如有講解錯誤的還請多多包涵!

4技術交流+v lyaoyao__i(兩個杠)

最后

微信公眾號:爬蟲爬呀爬

qrcode_for_gh_c637bce93320_258

如果你覺得這篇文章對你有幫助,不妨請作者喝一杯咖啡吧!

img

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/715152.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/715152.shtml
英文地址,請注明出處:http://en.pswp.cn/news/715152.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

0048__Unix傳奇

Unix傳奇 &#xff08;上篇&#xff09;_unix傳奇(上篇)-CSDN博客 Unix傳奇 &#xff08;下篇&#xff09;-CSDN博客 Unix現狀與未來——CSDN對我的采訪_nuix郵件系統行業地位-CSDN博客

win11安裝nodejs

一、下載安裝包 鏈接: https://pan.baidu.com/s/1_df8s1UlgNNaewWrWgI59A?pwdpsjm 提取碼: psjm 二、安裝步驟 1.雙擊安裝包 2.Next> 3.勾選之后&#xff0c;Next> 4.點擊Change&#xff0c;選擇你要安裝的路徑&#xff0c;然后Next> 5.點擊Install安裝 二、…

學生云服務器騰訊云_騰訊云學生學生_騰訊云學生云主機

2024年騰訊云學生服務器優惠活動「云校園」&#xff0c;學生服務器優惠價格&#xff1a;輕量應用服務器2核2G學生價30元3個月、58元6個月、112元一年&#xff0c;輕量應用服務器4核8G配置191.1元3個月、352.8元6個月、646.8元一年&#xff0c;CVM云服務器2核4G配置842.4元一年&…

基于擴散模型的圖像編輯:首篇綜述

AIGC 大模型最火熱的任務之一——基于 Diffusion Model 的圖像編輯(editing)領域的首篇綜述。長達 26 頁&#xff0c;涵蓋 297 篇文獻&#xff01;本文全面研究圖像編輯前沿方法&#xff0c;并根據技術路線精煉地劃分為 3 個大類、14 個子類&#xff0c;通過表格列明每個方法的…

查詢緩存-緩存更新-緩存穿透-緩存雪崩-緩存擊穿

1.查詢緩存 1.2.出現的原因 用戶高并發訪問帶來的服務器讀寫的壓力 1.3.解決方法 添加緩存 2.緩存更新 2.1.出現的原因 出現數據不一致的問題 2.2.解決方法 操作數據庫的時候 更新數據庫刪除緩存 查詢數據的時候設置過期時間 3.緩存穿透 3.1.出現的原因 在高并發訪…

LeetCode 熱題 100 | 圖論(一)

目錄 1 200. 島嶼數量 2 994. 腐爛的橘子 2.1 智障遍歷法 2.2 仿層序遍歷法 菜鳥做題&#xff0c;語言是 C 1 200. 島嶼數量 解題思路&#xff1a; 遍歷二維數組&#xff0c;尋找 “1”&#xff08;若找到則島嶼數量 1&#xff09;尋找與當前 “1” 直接或間接連接在…

Java輸入輸出流詳細解析

Java I/O&#xff08;輸入/輸出&#xff09;主要被用來處理輸入數據和輸出結果。 在Java中&#xff0c;輸入/輸出操作被當作流&#xff08;Stream&#xff09;進行處理。流是一個連續的數據流入或數據流出的通道。流操作在Java中主要可以分為兩種類型&#xff1a;字節流和字符…

基于ssm疫情期間高校防控系統+vue論文

摘 要 傳統信息的管理大部分依賴于管理人員的手工登記與管理&#xff0c;然而&#xff0c;隨著近些年信息技術的迅猛發展&#xff0c;讓許多比較老套的信息管理模式進行了更新迭代&#xff0c;學生信息因為其管理內容繁雜&#xff0c;管理數量繁多導致手工進行處理不能滿足廣大…

‘conda‘ 不是內部或外部命令,也不是可運行的程序 或批處理文件

如果你在運行 conda 命令時收到了 ‘conda’ 不是內部或外部命令&#xff0c;也不是可運行的程序或批處理文件。 的錯誤消息&#xff0c;這可能意味著 Anaconda 并沒有正確地添加到你的系統路徑中。 1.你可以嘗試手動添加 Anaconda 到系統路徑中。以下是在 Windows 系統上添加…

19.2 DeepMetricFi:基于深度度量學習改進Wi-Fi指紋定位

P. Chen and S. Zhang, "DeepMetricFi: Improving Wi-Fi Fingerprinting Localization by Deep Metric Learning," in IEEE Internet of Things Journal, vol. 11, no. 4, pp. 6961-6971, 15 Feb.15, 2024, doi: 10.1109/JIOT.2023.3315289. 摘要 Wi-Fi RSSI指紋定位…

C++內存泄漏:原因、預防、定位

內存泄漏是 C 中常見的問題之一&#xff0c;可能導致程序運行時資源消耗過大、性能下降&#xff0c;甚至程序崩潰。 內存泄漏的原因 1. 未釋放動態分配的內存 在 C 中&#xff0c;通過 new 操作符分配的內存需要手動使用 delete 操作符進行釋放。如果忘記或者由于某種原因未…

調用“每日詩詞”在你的頁面添加一句詩

概述 前幾天瀏覽網站的時候看到頁面上有句詩&#xff0c;打開調試看了下調用的是“每日詩詞”的SDK。本文基于此SDK實現你的頁面添加一句詩。 實現效果 實現 1. 引入SDK <script src"https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset"utf-…

mysql服務治理

一、性能監控指標和解決方案 1.QPS 一臺 MySQL 數據庫&#xff0c;大致處理能力的極限是&#xff0c;每秒一萬條左右的簡單 SQL&#xff0c;這里的“簡單 SQL”&#xff0c;指的是類似于主鍵查詢這種不需要遍歷很多條記錄的 SQL。 根據服務器的配置高低&#xff0c;可能低端…

【BUUCTF web】通關 2.0

&#x1f36c; 博主介紹&#x1f468;?&#x1f393; 博主介紹&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高興認識大家~ ?主攻領域&#xff1a;【滲透領域】【應急響應】 【Java】 【VulnHub靶場復現】【面試分析】 &#x1f389;點贊?評論?收藏 …

MAC-鍵盤command快捷鍵、設置windows快捷鍵

在 Windows PC 專用鍵盤上&#xff0c;請用 Alt 鍵代替 Option 鍵&#xff0c;用 Ctrl 鍵或 Windows 標志鍵代替 Command 鍵。 Mac 鍵盤快捷鍵 - 官方 Apple 支持 (中國) 設置windows快捷鍵 使用mac外接適用于windows的鍵盤時&#xff0c;如何設置快捷鍵&#xff1f;_mac外…

2024年2月國內如何快速注冊OnlyFans最新小白教學

前言 onlyface軟件是一個創立于2016年的訂閱式社交媒體平臺&#xff0c;創作者可以在自己的賬號發布原創的照片或視頻&#xff0c;并將其設置成付費模式&#xff0c;若用戶想查看則需要每月交費訂閱。 需要注意的是&#xff0c;網絡上可能存在非法或不道德的應用程序&#xff…

Java:性能優化細節31-45

Java&#xff1a;性能優化細節31-45 31、合理使用java.util.Vector 在使用java.util.Vector時&#xff0c;需要注意其性能特性和最佳實踐&#xff0c;以確保應用程序運行高效。Vector是一個同步的集合類&#xff0c;提供了動態數組的實現。由于它是線程安全的&#xff0c;所以…

獲取當前數據 上下移動

點擊按鈕 上下移動 當前數據 代碼 // 出國境管理 登記備案人員列表 <template><a-row><a-col span"24"><a-card :class"style[a-table-wrapper]"><!-- 出國境 登記備案人員列表 --><a-table:rowKey"records >…

淘寶開放平臺獲取商家訂單數據API接口接入流程

taobao.custom 自定義API操作 接口概述&#xff1a;通過此API可以調用淘寶開放平臺的API&#xff0c;通過技術對接&#xff0c;您可以輕松實現無賬號調用官方接口。進入測試&#xff01; 公共參數 名稱類型必須描述keyString是調用key&#xff08;必須以GET方式拼接在URL中&…

通過修改host文件來訪問GitHub

前言&#xff1a; 由于國內環境的原因&#xff0c;導致我們無法流暢的訪問GitHub&#xff0c;。 但是我們可以采取修改host文件來實現流暢訪問。 缺點&#xff1a;需要不定時的刷新修改。 操作流程 一、查詢IP地址 以下地址可以查詢ip地址 http://ip.tool.chinaz.com/ htt…