if函数中如果是0则代表false,是1等于为true
if ( a5 ){
result = CSecFunctProvider::InsertCBCPadding(param, a3, a4, a4);
}
2.白盒aes-cbc模式特征:
首先进行
1.初始化CWAESCipher对象
2.表转换
3.明文和iv进行异或
3.进行填充
4.循环判断是否加密完成
5.块加密(列表)在块这里进行dfa攻击
6.前九轮循环,循环完break
7.开始第10轮,并把加密结果进行返回
//第一个函数 初始化CWAESCipher对象,表转换,把加密结果返回
int __fastcall aes_encrypt1(CSecFunctProvider *param, unsigned __int8 *a2, unsigned int *a3)
{
_DWORD v7[7];
//初始化CWAESCipher对象
CWAESCipher::CWAESCipher((CWAESCipher *)v7);
v7[0] = &off_1ADC0;
//表转换
CWAESCipher_Auth::WBACRAES_SwitchTable((CWAESCipher_Auth *)v7, 1);
//最后加密并返回
return CWAESCipher::WBACRAES128_EncryptCBC((CWAESCipher *)v7, param, a2, a3, 1);
}
//第二个函数 进行填充,然后循环判断是否加密完成,完成后退出
//看函数名有cbc模式,需要有iv,如果是ecb模式则没有
//明文分组1会和iv进行异或,得到密文分组1,再用明文分组2与上一次密文分组进行异或,以此类推
//所以我们看看哪里有异或的可能就是iv,iv在右
int __fastcall CWAESCipher::WBACRAES128_EncryptCBC(
CWAESCipher *this,
CSecFunctProvider *param,
unsigned __int8 *a3,
unsigned int *a4,
bool a5)
{
int result; // r0
int v9; // r3
int v10; // r7
unsigned __int8 *v11; // r1
int i; // r5
unsigned __int8 *v13; // r4
unsigned __int8 *v14; // r3
char v15; // t1
if ( a5 )
{
//进行填充
result = CSecFunctProvider::InsertCBCPadding(param, a3, a4, a4);
if ( result )
return result;
}
else if ( *(_DWORD *)a3 << 28 )
{
return 11;
}
if ( param )
{
v9 = 0;
v10 = *(_DWORD *)a3 >> 4;
do
{
*((_BYTE *)param + v9) ^= *((_BYTE *)this + v9 + 8);//只有这里像iv,第一次循环得到结果是00
++v9;
}
while ( v9 != 16 ); //循环16次,得到结果就是32个0
v11 = (unsigned __int8 *)param;
for ( i = 0; i != v10; ++i )
{
v13 = v11 + 16;
v14 = v11;
if ( i )
{
do
{
v15 = *v14++;
*(v14 - 1) = v15 ^ *(v14 - 17);
}
while ( v14 != v13 );
}
//块加密
result = CWAESCipher::WBACRAES_EncryptOneBlock(this, v11);
v11 = v13;
//判断是否全部加密完成,加密完成跳出循环
if ( result )
return result;
}
}
return 0;
}
// 0x4dcc
在这里看不到函数了,进入汇编查看
int __fastcall CWAESCipher::WBACRAES_EncryptOneBlock(CWAESCipher *this, unsigned __int8 *a2, unsigned __int8 *a3)
{
return (**(int (__fastcall ***)(CWAESCipher *, unsigned __int8 *, unsigned __int8 *, int))this)(this, a2, a3, 10);
}
发现汇编长这样 看到有一个BLX R4,hook这个寄存器的地址得到 0x4dcc
.text:0000582E PUSH {R4,LR}
.text:00005830 LDR R3, [R0]
.text:00005832 LDR R4, [R3]
.text:00005834 MOVS R3, #0xA
.text:00005836 BLX R4
.text:00005838 POP {R4,PC}
第三个函数:主要加密位置
//先是进行了列表,然后开始9轮循环,然后进行最后一轮,赋值给a3,把结果返回
int __fastcall CWAESCipher_Auth::WBACRAES_EncryptOneBlock(
CWAESCipher_Auth *this,
CSecFunctProvider *a2,
unsigned __int8 *a3,
int a4)
{
unsigned __int8 (*v5)[8]; // r3
int i; // r11
int v7; // r1
int v8; // r3
_DWORD *v9; // r6
int j; // r2
int v11; // r10
int v12; // r0
int v13; // r0
_DWORD *v14; // r8
int v15; // r0
int k; // r2
char *v17; // r3
int m; // r9
int v19; // r7
char v20; // r5
char v21; // r1
int v22; // r6
int v23; // r0
unsigned int v24; // r5
int v25; // r1
char v26; // r8
_DWORD *v27; // r12
char v28; // r6
int v29; // r12
_DWORD *v30; // r8
int v31; // r3
int v32; // r12
int v33; // r1
int v34; // r0
int n; // r2
_DWORD *v36; // r7
int v37; // r11
char v38; // r11
char *v39; // r6
int v40; // r11
int ii; // r3
int jj; // r2
int v44; // [sp+0h] [bp-E8h]
int v45; // [sp+4h] [bp-E4h]
int v46; // [sp+10h] [bp-D8h]
_BYTE v50[4]; // [sp+38h] [bp-B0h] BYREF
int v51; // [sp+3Ch] [bp-ACh]
_DWORD s[42]; // [sp+40h] [bp-A8h] BYREF
memset(s, 0, 0x20u);
//根据下面的s操作猜测是state块,可以在这里进行DFA攻击
v46 = CSecFunctProvider::PrepareAESMatrix(a2, (unsigned __int8 *)&word_10, (int)s, v5);// 列表
if ( !v46 )
{
for ( i = 0; ; ++i )
{
if ( i >= a4 )
goto LABEL_39;
if ( i == 9 ) // 前9轮循环,9轮后退出,可以选择在这里进行注入进行攻击,然后主动调用当前函数
break;
v7 = 0;
v8 = 0;
v45 = 4 * i;
do
{
v9 = &s[2 * v8 + 32];
for ( j = 0; j != 4; ++j )
{
v11 = *((_DWORD *)this + 6);
if ( v11 == 1 )
{
v15 = (*((_BYTE *)&unk_18D8E + v7) + (_BYTE)j) & 3;
v51 = roundTables_auth1[256 * (v8 + 4 * (v15 + v45)) + *((unsigned __int8 *)v9 + v15 - 128)];
}
else
{
v12 = (*((_BYTE *)&unk_18D8E + v7) + (_BYTE)j) & 3;
v13 = *((unsigned __int8 *)v9 + v12 - 128) + ((v8 + 4 * (v12 + v45)) << 8);
if ( v11 == 2 )
v14 = &roundTables_auth2_ptr;
else
v14 = &roundTables_auth1_ptr;
v51 = *(_DWORD *)(*v14 + 4 * v13);
}
s[4 * v8 + 16 + j] = v51;
}
++v8;
v7 += 2;
}
while ( v8 != 4 ); // v8 4字节端序
for ( k = 0; k != 4; ++k )
{
v17 = (char *)&s[16] + k;
for ( m = 0; m != 4; ++m )
{
v19 = 0;
v20 = *v17;
v50[1] = v17[16];
v21 = v17[32];
v22 = v20 & 0xF;
v23 = 96 * i + 24 * m;
v50[0] = v20;
v50[2] = v21;
v24 = v20 & 0xF0;
v50[3] = v17[48];
v25 = 6 * k;
do
{
v26 = v50[++v19];
if ( v11 == 2 )
v27 = &xorTables_auth2_ptr;
else
v27 = &xorTables_auth1_ptr;
v28 = *(_BYTE *)(*v27 + (v22 | (16 * (v26 & 0xF))) + ((v23 + v25) << 8));
v29 = v25 + 1;
v44 = v26 & 0xF0 | (v24 >> 4);
v22 = v28 & 0xF;
if ( v11 == 2 )
v30 = &xorTables_auth2_ptr;
else
v30 = &xorTables_auth1_ptr;
v25 += 2;
v24 = (unsigned __int8)(16 * *(_BYTE *)(*v30 + v44 + ((v29 + v23) << 8)));
}
while ( v19 != 3 );
v17 += 4;
*((_BYTE *)&s[2 * k] + m) = v24 | v22;
}
}
}
if ( a4 == 10 )
{
//从这个代码可以看出s一直在做操作,猜测s是state块,然后赋值给a3,猜测a3是加密返回值
s[8] = s[0];
s[9] = s[1];
s[10] = s[2];
s[11] = s[3];
s[12] = s[4];
s[13] = s[5];
s[14] = s[6];
s[15] = s[7];
v31 = 0;
v32 = *((_DWORD *)this + 6);
do
{ // 第10轮
v33 = 0;
v34 = 0;
for ( n = 0; n != 4; ++n )
{
if ( v32 == 1 )
{
v38 = *((_BYTE *)&unk_18D8E + v34);
}
else
{
if ( v32 == 2 )
{
v36 = &finalRoundTable_auth2_ptr;
v37 = (*((_BYTE *)&unk_18D8E + v34) + (_BYTE)v31) & 3;
goto LABEL_37;
}
v38 = *((_BYTE *)&unk_18D8E + v34);
}
v36 = &finalRoundTable_auth1_ptr;
v37 = (v38 + (_BYTE)v31) & 3;
LABEL_37:
v34 += 2;
v39 = (char *)&s[2 * n + 32] + v37;
v40 = n + 4 * v37;
*((_BYTE *)s + v31 + v33) = *(_BYTE *)(*v36 + (unsigned __int8)*(v39 - 96) + (v40 << 8));
v33 += 8;
}
++v31;
}
while ( v31 != 4 );
}
LABEL_39:
for ( ii = 0; ii != 4; ++ii )
{
for ( jj = 0; jj != 4; ++jj )
a3[4 * ii + jj] = *((_BYTE *)&s[2 * jj] + ii);// 结果在a3并返回
}
}
return v46;
}
纯靠猜,我们分析一下哪里是md5的明文,确认s是明文
sprintf(s, "%s%d", a2, v6);
v7 = s;
do
{
v8 = v7++;
v9 = (unsigned __int8)*v8;
}
while ( *v8 );
v674 = &v699;
v672 = v8 - s;
v703 = 8 * (v8 - s);
v704 = (unsigned int)(v8 - s) >> 29;
if ( (unsigned int)(v8 - s) > 0x3F )
{
if ( s != v705 )
{
if ( v705 >= s )
{
v15 = &v706;
v16 = s + 64; #首先s有+64比较像
do
{
v17 = *((_DWORD *)v16 - 1);
v16 -= 4;
v18 = v16 == s;
*(_DWORD *)v15 = v17;
v15 -= 4;
}
while ( !v18 );
}
else
{
do
{
v13 = (char *)&v699 + v9;
v14 = *(_DWORD *)&s[v9]; #s在do while循环里面,并且&s从地址里面一个个不停地取值,而且刚好是循环16次,md5的长度是16个字节,所以猜测s是明文
v9 += 4;
*((_DWORD *)v13 + 6) = v14;
}
while ( v9 != 64 );
}
}
md5返回值就更明显了,v652就是得,循环16次,每次2字节,就是32位
for ( ii = 0; ii != 16; ++ii )
{
v652 = (char *)(v679 + 2 * ii);
v653 = *((unsigned __int8 *)v698 + ii);
sprintf(v652, "%02x", v653);
}
free(s);
return 1;
unidbg主动调用和dfa攻击
package com.cloudy.linglingbang;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.BackendException;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
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.linux.android.dvm.jni.ProxyClassFactory;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.spi.LibraryFile;
import com.github.unidbg.unix.UnixSyscallHandler;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import org.apache.commons.codec.DecoderException;
import unicorn.ArmConst;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import static unicorn.ArmConst.UC_ARM_REG_R0;
public class Cloud extends AbstractJni implements IOResolver {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final DvmClass CheckCodeUtil;
private final Memory memory;
private final DalvikModule dvm;
public Cloud(){
emulator = AndroidEmulatorBuilder.for32Bit()
.setProcessName("com.cloudy.linglingbang")
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
//这个可以对文件进行访问
//emulator.getSyscallHandler().addIOResolver(this);
vm = emulator.createDalvikVM(new File("D:\\lingdian\\java-project\\unidbg-0.9.7\\unidbg-android\\src\\test\\java\\com\\Cloud\\apk\\cloud.apk")); // 创建Android虚拟机
vm.setVerbose(true); // 设置是否打印Jni调用细节
vm.setJni(this);
dvm = vm.loadLibrary(new File("D:\\lingdian\\java-project\\unidbg-0.9.7\\unidbg-android\\src\\test\\java\\com\\Cloud\\apk\\libencrypt.so"), true); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
module = dvm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
vm.callJNI_OnLoad(emulator,module);
CheckCodeUtil = vm.resolveClass("com/bangcle/comapiprotect/CheckCodeUtil");
Debugger attach = emulator.attach();
setDebugger(attach);
}
public static int randInt(int min, int max) {
Random rand = new Random();
return rand.nextInt((max - min) + 1) + min;
}
public Cloud(AndroidEmulator emulator, VM vm, Module module, DvmClass checkCodeUtil, Memory memory, DalvikModule dvm) {
this.emulator = emulator;
this.vm = vm;
this.module = module;
CheckCodeUtil = checkCodeUtil;
this.memory = memory;
this.dvm = dvm;
}
public 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 string = varArg.getObjectArg(0).getValue().toString();
// System.out.println(string);
if("ro.serialno".equals(string)){
return new StringObject(vm,"unknown");
}
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature){
case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":
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 string = varArg.getObjectArg(0).getValue().toString();
//System.out.println(string);
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);
}
FileResult<AndroidFileIO> f1;
//补文件访问,补了这个发现还有堆栈检测,就没补了
public FileResult<AndroidFileIO> getF1(String pathname, int oflags) {
if (f1 == null) {
f1 = FileResult.<AndroidFileIO> success(new ByteArrayFileIO(oflags, pathname, "com.cloudy.linglingbang".getBytes()));
}
return f1;
}
@Override
public FileResult<AndroidFileIO> resolve(Emulator emulator, String pathname, int oflags) {
if ("/proc/self/cmdline".equals(pathname) || ("/proc/" + emulator.getPid() + "/cmdline").equals(pathname)) {
return getF1(pathname, oflags);
}
return null;
}
public 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 decheckcode(String str){
DvmObject ret = CheckCodeUtil.callStaticJniMethodObject(emulator,"decheckcode(Ljava/lang/String;)Ljava/lang/String;",str);
System.out.println("\ncall decheckcode: " + ret.getValue().toString());
}
public void patch(){
//解密的异常分支
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x16604);
byte[] code = new byte[]{(byte) 0x20, (byte) 0xB1};
pointer.write(code);
//把0x13FC6地址的r0寄存器改为1,不改的话会走异常分支,加密的异常分支
UnidbgPointer pointer1 = UnidbgPointer.pointer(emulator, module.base + 0x13FC6);
byte[] code1 = new byte[]{(byte) 0x20, (byte) 0x01}; // MOVS R0, #1 的 Thumb 编码
pointer1.write(code1);
}
public static byte[] hexStringToBytes(String hexString) {
int len = hexString.length();
if (len % 2 != 0) {
throw new IllegalArgumentException("Invalid hex string");
}
byte[] result = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
result[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return result;
}
public static String bytesToHexString(byte... src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
public void call(){
//这种主动调用是调用当前函数的,感觉还是不推荐。如果有些东西,在函数之前进行初始化的就废了
MemoryBlock inBlock=emulator.getMemory().malloc(16,true);
UnidbgPointer inptr = inBlock.getPointer();
MemoryBlock outBlock=emulator.getMemory().malloc(16,true);
UnidbgPointer outPtr = outBlock.getPointer();
MemoryBlock inBlock1=emulator.getMemory().malloc(16,true);
UnidbgPointer inptr1 = inBlock1.getPointer();
byte[] hellos = hexStringToBytes("68656c6c6f");
inptr.write(0,hellos,0,hellos.length);
byte[] args1 = hexStringToBytes("C0AD0140");
inptr1.write(0,args1,0,args1.length);
//参数1是未知,参数2是参数,参数3返回值,参数4未知
module.callFunction(emulator,0x4DCC+1,inptr1,inptr,outPtr,0xa);
String ret=bytesToHexString(outPtr.getByteArray(0,0x10));
System.out.println("white box result:"+ ret);
inBlock.free();
outBlock.free();
}
public String call_checkcode() {
//unidbg主动调用地址
List<Object> args = new ArrayList<>(10);
args.add(vm.getJNIEnv());
args.add(0);
String string = "mobile=17637693656&password=lv946346&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=FnCNm2C3bS&response_type=token";
args.add(vm.addLocalObject(new StringObject(vm, string)));
args.add(2);
args.add(vm.addLocalObject(new StringObject(vm, "1734243230657")));
Number number = module.callFunction(emulator, 0x13A18+1, args.toArray());
String result = vm.getObject(number.intValue()).getValue().toString();
return result;
}
public void jni_call(){
//jni调用java层主动调用,推荐
String str1 = "mobile=17637693632&password=lv946346&client_id=2019041810222516127&client_secret=c5ad2a4290faa3df39683865c2e10310&state=FnCNm2C3bS&response_type=token";
str1="hello";
String str3 = "1734243230657";
DvmObject ret = CheckCodeUtil.callStaticJniMethodObject(emulator, "checkcode(Ljava/lang/String;ILjava/lang/String;)Ljava/lang/String;",str1,1,str3);
String strOut = (String)ret.getValue();
System.out.println("call checkcode: " + strOut);
}
public void setDebugger(Debugger debugger){
//emulator.attach().addBreakPoint(module.base+0x5836);//下断点看跳得地址是哪里
//c是放开debug mr0是读取r0寄存器得值(32位),x0是64位
//emulator.traceRead(0x403b1000,0x403b1010);//看有哪些地方对明文进行读取
debugger.addBreakPoint(module.base+0x5A34, new BreakPointCallback() {
@Override
public 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;
}
});
//进行dfa攻击
debugger.addBreakPoint(module.base+0x4E26+1, new BreakPointCallback() {
int round = 0;
//0xbffff678,这个地址是state块的地址,把debug打上看看state块的地址是多少,记住明文不能变,变了state块地址就会变
UnidbgPointer statePointer = memory.pointer(0xbffff678);
@Override
public boolean onHit(Emulator<?> emulator, long address) {
round += 1;
if (round % 9 == 0){
statePointer.setByte(randInt(0, 15), (byte) randInt(0, 0xff));
}
return true;//返回true 就不会在控制台断住
}
});
//这里是hook,sprintf,得到地址和入参,0x301C需要去so里面找一下
debugger.addBreakPoint(module.base+0x301C+1, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
Pointer input = context.getPointerArg(1);
Pointer input2 = context.getPointerArg(2);
Pointer input3 = context.getPointerArg(3);
Inspector.inspect(input.getByteArray(0, 0x20), "sprintf arg1");
try{
Inspector.inspect(input2.getByteArray(0, 0x20), "sprintf arg2");
}catch (BackendException e){
System.out.println("input2出现异常 hashcode:"+input2.hashCode());
}
try {
Inspector.inspect(input3.getByteArray(0, 0x20), "sprintf arg3");
}catch (BackendException e){
System.out.println("input arg3出现异常 hashcode:"+input3.hashCode());
}
Inspector.inspect(input3.toString().getBytes(StandardCharsets.UTF_8), "input arg3 addr");
Inspector.inspect(context.getLRPointer().toString().getBytes(StandardCharsets.UTF_8), "lr addr");
return true;
}
});
}
public static void main(String[] args) {
Cloud cloud = new Cloud();
cloud.patch();
String res=cloud.call_checkcode();
System.out.println(res);
cloud.decheckcode(res);
}
计算正确秘钥
import phoenixAESwith
# 创建tracefile文件,第一行是正确密文 后面是故障密文
phoenixAES.crack_file('tracefile', [], True, False, 3)
拿到结果了,用aes_keyschedule把主密钥也就是初始秘钥还原出来