白乐天

道阻且长,行则将至。

某幸白盒AES

App信息

包名:com.lucky.luckyclient

过检测

root检测:使用狐妖面具隐藏列表实现绕过

360加固:使用脱壳网站脱壳 56.al

frida检测:使用小工具

逆向目标

com.luckincoffee.safeboxlib.CryptoHelper类下的localAESWork()方法

分析

localAESWork()这个方法被通类下的c方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized String c(String str) {
c cVar = this.f93097a;
if (cVar == null) {
throw new RuntimeException(StubApp.getString2("32267"));
}
String str2 = "";
String a10 = cVar.a();
if (TextUtils.isEmpty(a10)) {
return "";
}
try {
str2 = new String(Base64.encode(localAESWork(str.getBytes(), 2, Base64.decode(a10.replace(org.objectweb.asm.signature.b.f127225c, org.objectweb.asm.signature.b.f127224b).replace('_', '/').getBytes(), 2)), 2));
} catch (Exception e10) {
e10.printStackTrace();
}
return str2.replace(org.objectweb.asm.signature.b.f127224b, org.objectweb.asm.signature.b.f127225c).replace('/', '_');
}

单独看与localAESWork有关的,如下

1
2
3
4
5
6
7
8
9
10
11
12
str2 = new String(
Base64.encode(
localAESWork(str.getBytes(), 2,
Base64.decode(a10.replace("-", "+").replace('_', '/').getBytes(), 2)
),
2)
);

// 对原始字符串str进行AES加密
// 将a10中的"-"替换为"+"、"_"替换为"/",然后解码为base64字节数组
// 对加密结果进行Base64编码
// Base64的第二个参数2代表URL安全的标准Base64

获取a10的值

它是一个接口,我们找到接口的实现类,然后进行hook

1
2
3
4
5
6
7
8
function get_a10(){
Java.perform(function(){
let a = Java.use("com.lucky.lib.http2.b$a").$new();
let result = a.a();
console.log("a10:",result);
return result;
})
}

主动调用,输出

1
a10: gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=

定位

先使用get_quic_code脚本获取jinCode

我这里是

1
{"size":40,"offset":{"jniCode":24,"quickCode":32,"accessFlags":4}}

然后通过如下脚本打印函数id(artmethodid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function hook(){
Java.perform(function(){
Java.enumerateClassLoadersSync().forEach(classLoader=>{
try{
if(classLoader.loadClass("com.alibaba.android.arouter.launcher.a")){
Java.classFactory.loader = classLoader;
let CryptoHelper = Java.use("com.luckincoffee.safeboxlib.CryptoHelper");
console.log(CryptoHelper.$l.find("localAESWork"));
}
}
catch(a){

}
})
})
}
hook()

输出

m代表method,s代表static,后面是artmethodid

1
m:s0x78c6261350

artmethodid加上jniCode就是函数的地址

1
console.log(ptr(0x78c6261350).add(24).readPointer());

输出

1
0x7868699ed8

根据这个地址就可以获取函数的地址和偏移量了

1
console.log(Process.findModuleByAddress(ptr(0x7868699ed8)).name);             console.log(ptr(0x7868699ed8).sub(Process.findModuleByAddress(ptr(ptr(0x7868699ed8))).base));

输出

1
2
libcryptoDD.so
0x41ed8

抓个包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
utl:
https://capi.lkcoffee.com/resource/m/user/login

headers={
"x-lk-akv": "5.3.15",
"x-lk-mid": "",
"x-lk-sid": "",
"x-business-language": "zh-CN",
"x-business-regioncode": "CN",
"event_id": "1741972196535",
"x-lk-csid": "38e7641d-7f62-44e9-bb24-2beca40b68bc",
"cookie": "uid=b4e31b9d-d7ab-40a9-9b64-14e52f633b911741972183784",
"content-type": "application/x-www-form-urlencoded",
"content-length": "704",
"accept-encoding": "gzip",
"user-agent": "okhttp/4.9.3"
}

body={
"sign": "997653005130164991918737828941799149694",
"q": "veIdVUqstntHvdzxURbvo9_8_soZQht-Vv8TOV4GNTFKY6eV5ccjTgjpUT5CJprcq7Cm5fuzbAvdlfrSQP4ygoeGnUubcH0XonTCzfIs4RYqF7kOdlbEDxFl84iVqKnFQX0Bg2K7cuqYRTR1YkyBckbtvaPOFk-F9jt8cNUe_PFp-oiwzzex5tcqLefG2ZmVBuWJlarfJP4h-XtNmdYpWm5k4_4jhldfVngv29YLYv8GO7m4RQCdDgoydMbu2adjsKsEKy_oWLC2a50RpV_9Yd-AcAGU4OLg3vzL9HqBNttDo6M_bylZJmvYurt3CTn4cvrX3wu0aGPMYr2rBpEEtT062cD7qL7F2mLfqeH8ijsggM0_eqqqk4m2S1dOqky_WXSVbluyFcUQBPUernSXKvLnsLCLgDUVsVeTdpXvt_UP8ia6R6IHhDXkqzjBiCWQqAAOjwfd5shrgNPRNP_h7NpjwwCfK5zJ3g1T9j084UnGoiGQo2cpvBhMWH8RTXAKI7i8K1ZgvGkzHiYAE5zN35WnsNd7ma-61nrItQzMo-TIDr-IIFlv199ErBcCs1Yl",
"uid": "b4e31b9d-d7ab-40a9-9b64-14e52f633b911741972183784",
"t": "1741972256316",
"cid": "210101"
}

response:
57l6aGIjM8ySP5qUW5cmXG_swqXZ3-CDvlz4K-CW5JHHIAiIXoiAtXFef1i898pFnXG2UaYYFPgo4B-QISWmwxGVgzf63I98g8s1TCjJ2qHAI2q4UI-IP1nRHfLevhZ8Kgx6gyMYgsoibWQJ_IU1cONmes8MQqWG3BlsfA7TAVesjF0N3KaEEfHH0RONmDSU3hSp9D9T3nl-xsHSpLBlPQPQRsLBEHzAgEeKO5EueBVpw8cCq3WnWYGT_Ai2TxBpFIImOPBAMnR-T3lHx0D4Pp-iF9p7c_xa8Ek3IhwVx5m8lpQCuAta8PG1ffVGX7Pgr82woPcekBWvF9qFV4kiabebYKsMQHmAbnNfKxLsSEs=

Unidbg初始环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class Test extends AbstractJni {
private final AndroidEmulator emulator;
private String process = "";
private final Memory memory;
private final VM vm;
private DalvikModule dm;
private Module cryptoDDmodule;


public Test() {
emulator = AndroidEmulatorBuilder
.for64Bit() // for32Bit()
.setProcessName(process)
.setRootDir(new File("target/rootfs"))
.addBackendFactory(new Unicorn2Factory(true))
.build();
memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("apktest/ruixingcoffee_5.2.65.apk"));
vm.setJni(this);
vm.setVerbose(true);
new AndroidModule(emulator,vm).register(memory);
dm = vm.loadLibrary("cryptoDD",true);
dm.callJNI_OnLoad(emulator);
cryptoDDmodule = dm.getModule();
}

public void localAESWork() {
String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");
ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojia".getBytes(),
2,
decode_a10);
String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
System.out.println(b64encoded);
}

public static void main(String[] args) {
Test test = new Test();
test.localAESWork();
}
}

运行一下,报错了,根据unidbg的日志提示,如下,是写入内存失败了

可以通过trace断在报错的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void localAESWork() {
String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");

// 添加trace
emulator.traceCode(cryptoDDmodule.base,cryptoDDmodule.base+cryptoDDmodule.size);

ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojia".getBytes(),
2,
decode_a10);
String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
System.out.println(b64encoded);
}

看输出日志

1
2
3
4
5
[16:56:56 473][libcryptoDD.so 0x17e64] [11de47f9] 0x12017e64: "ldr x17, [x16, #0xfb8]" x16=0x1204b000 => x17=0x121bbac0
[16:56:56 473][libcryptoDD.so 0x17e68] [10e23e91] 0x12017e68: "add x16, x16, #0xfb8" x16=0x1204b000 => x16=0x1204bfb8
[16:56:56 473][libcryptoDD.so 0x17e6c] [20021fd6] 0x12017e6c: "br x17" x17=0x121bbac0Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.github.unidbg.linux.android.dvm.array.ByteArray.getValue()" because "byteValues" is null
at com.luckincoffee.safeboxlib.Test.localAESWork(Test.java:62)
at com.luckincoffee.safeboxlib.Test.main(Test.java:68)

最后是在0x17e6c这个地方停了下来

到IDA里面去看

是一个free函数,Unidbg无法处理这个函数

我们用HookZz来处理,同时把trace关掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void localAESWork() {
// 修复free函数
HookZz hookZz = HookZz.getInstance(emulator);
Module module = memory.findModule("libc.so");
hookZz.replace(module.findSymbolByName("free").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long info) {
RegisterContext rc = emulator.getContext();
return HookStatus.RET(emulator, rc.getLRPointer().toIntPeer());
}
});

String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");
// 关trace
// emulator.traceCode(cryptoDDmodule.base,cryptoDDmodule.base+cryptoDDmodule.size);

ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojia".getBytes(),
2,
decode_a10);
String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
System.out.println(b64encoded);
}

运行一下,输出结果

1
sroyS1xvlvLB1iUSzRC2yw==

加密方式

ECB

将明文分割为固定大小的块(例如 AES 的 128 位块),每个块独立加密。

相同明文块加密后生成相同密文块,没有块之间的关联性。

例如:如果两个明文块内容相同,加密后的密文块也相同。

如下

CBC

每个明文块在加密前会与前一个密文块进行异或(XOR)操作(第一个块使用初始化向量 IV)。

块之间存在链式依赖,相同明文块加密后生成不同密文块

例如:即使两个明文块相同,加密后的密文块也不同。

确定加密方式

我们可以传入相同的字符的明文,然后看输出有没有重复

将加密结果转为hex编码,然后搜索看有没有重复的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void localAESWork() {
// 修复free函数
HookZz hookZz = HookZz.getInstance(emulator);
Module module = memory.findModule("libc.so");
hookZz.replace(module.findSymbolByName("free").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long info) {
RegisterContext rc = emulator.getContext();
return HookStatus.RET(emulator, rc.getLRPointer().toIntPeer());
}
});

String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");


ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaaxiaojiaa".getBytes(),
2,
decode_a10);
System.out.println(Hex.encodeHex(byteValues.getValue()));
// String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
// System.out.println(b64encoded);
}

如下,有重复的

可以确定加密模式是ECB模式了

trace分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void localAESWork() {
// 修复free函数
HookZz hookZz = HookZz.getInstance(emulator);
Module module = memory.findModule("libc.so");
hookZz.replace(module.findSymbolByName("free").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long info) {
RegisterContext rc = emulator.getContext();
return HookStatus.RET(emulator, rc.getLRPointer().toIntPeer());
}
});

String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");

// trace
emulator.traceCode(cryptoDDmodule.base,cryptoDDmodule.base+cryptoDDmodule.size);
try {
PrintStream traceStream = new PrintStream(Files.newOutputStream(Paths.get("trace.log")),true);
traceHook.setRedirect(traceStream);
} catch (IOException e) {

}

ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojiaa".getBytes(),
2,
decode_a10);
System.out.println(Hex.encodeHex(byteValues.getValue()));
// String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
// System.out.println(b64encoded);
}

运行之后,trace日志结果保存在trace.log文件中

1
c9cbd740c87749c660690b89498d077d

根据输出结果到trace中搜索,比如c9cbd740

找到一系列与输出对应的值,在0xe4fff3c0地址处,在IDA中跳转到他们任意的偏移地址

在IDA中跳转到他们任意的偏移地址,然后反汇编,可以看到处于sub_189C4函数里

AES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 密钥扩展 => 11轮密钥
* 轮密钥加 有异或操作,而且只有异或操作
*
* 循环
* 字节代换 => 简单字符替换
* 行位移 => 只有位移,没有异或
* 列混淆 => 比较复杂,但是不需要key
* 轮密钥加
*
* 字节代换
* 行位移
* 轮密钥加
*
* 轮密钥加
* 字节代换
* 行位移
* 列混淆
* */

调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void localAESWork() {
// 修复free函数
HookZz hookZz = HookZz.getInstance(emulator);
Module module = memory.findModule("libc.so");
hookZz.replace(module.findSymbolByName("free").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long info) {
RegisterContext rc = emulator.getContext();
return HookStatus.RET(emulator, rc.getLRPointer().toIntPeer());
}
});

String a10 = "gUG562L2Vf2lTAaXhg63pkkJ+lDaV2IQcqdsfGGuNDs=";
byte[] decode_a10 = Base64.getDecoder().decode(a10.replace('-', '+').replace('_', '/').getBytes());
DvmClass CryptoHelperClass = vm.resolveClass("com.luckincoffee.safeboxlib.CryptoHelper");

// 下断点
Debugger attach = emulator.attach();
attach.addBreakPoint(cryptoDDmodule.base + 0x189C4);


ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojiaa".getBytes(),
2,
decode_a10);
System.out.println(Hex.encodeHex(byteValues.getValue()));
// String b64encoded = Base64.getEncoder().encodeToString(byteValues.getValue());
// System.out.println(b64encoded);
}

通过mx0查看x0寄存器的内容

这就是我们传入的明文,后面是填充的内容

地址:0xe4fff3d0

通过mx1查看x1寄存器的内容

这是一块空内存,应该就是存放返回值的

地址:0xe4fff3c0

AES流程分析

对任意的AES,找到它加密流程中的四个步骤

字节代换

行位移

列混淆

轮秘钥加

sub_189C4这个函数应该是整个AES的函数,看它里面调用了哪些函数

sub_219CC()

首先看sub_219CC()这个函数

进入它的函数内部,看到都是异或操作

那么就可能是轮秘钥加的步骤了

sub_21BEC()

接着往下看,看到一个sub_21BEC()函数

它里面有如下的操作

看大佬的手写AES代码,猜测这里可能是字节代换的操作

继续往下看

sub_223E0()

它进行如下操作

猜测可能是行位移

接着往下看

sub_22810()

看着像是列混淆

hook这几个地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 密钥扩展 => 11轮密钥
* 轮密钥加 有异或操作,而且只有异或操作
*
* 循环 9次
* 字节代换 => 简单字符替换
* 行位移 => 只有位移,没有异或
* 列混淆 => 比较复杂,但是不需要key
* 轮密钥加
*
* 字节代换
* 行位移
* 轮密钥加
*
* 轮密钥加 => 11
* 字节代换 => 10
* 行位移 => 10
* 列混淆 => 9次
* */

打印这几个函数的参数,返回值,调用次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Debugger attach = emulator.attach();
int[] counts = {0,0,0,0};
int[] offset = {
0x219CC,
0x21BEC,
0x223E0,
0x22810
};

for (int i = 0; i < 4; i++) {
int finalI = i;
int finalI1 = i;
int finalI2 = i;
attach.addBreakPoint(cryptoDDmodule.base + offset[i], new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
UnidbgPointer lr = context.getLRPointer();
UnidbgPointer pointerArg = context.getPointerArg(0);
byte[] buf = new byte[16];
pointerArg.read(0, buf, 0, 16);
counts[finalI]++;
System.out.println(Hex.encodeHex(buf));
attach.addBreakPoint(lr.peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer pointerArg = context.getPointerArg(0);
pointerArg.read(0, buf, 0, 16);
System.out.println(Hex.encodeHex(buf));
System.out.printf("====0x%x====%d====\n",offset[finalI2],counts[finalI1]);
return true;
}
});
return true;
}
});
}

输出

1
2
3
4
0x219cc:调用33次
0x21bec:调用10次
0x223e0:调用10次
0x22810:调用9次

这里面0x219cc调用了33次不太正常

sub_219CC()

重新看sub_219CC(),把sub_219CC()单独拿出来分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
Debugger attach = emulator.attach();
int[] counts = {0,0,0,0};
int[] offset = {
0x21BEC,
0x223E0,
0x22810
};

for (int i = 0; i < 3; i++) {
int finalI = i;
int finalI1 = i;
int finalI2 = i;
attach.addBreakPoint(cryptoDDmodule.base + offset[i], new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
UnidbgPointer lr = context.getLRPointer();
UnidbgPointer pointerArg = context.getPointerArg(0);
byte[] buf = new byte[16];
pointerArg.read(0, buf, 0, 16);
counts[finalI]++;
System.out.println(Hex.encodeHex(buf));
attach.addBreakPoint(lr.peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer pointerArg = context.getPointerArg(0);
pointerArg.read(0, buf, 0, 16);
System.out.println(Hex.encodeHex(buf));
System.out.printf("====0x%x====%d====\n",offset[finalI2],counts[finalI1]);
return true;
}
});
return true;
}
});
}

// 0x219CC
attach.addBreakPoint(cryptoDDmodule.base + 0x219CC, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
UnidbgPointer lr = context.getLRPointer();
UnidbgPointer pointerArg = context.getPointerArg(0);
byte[] buf = new byte[16];
pointerArg.read(0, buf, 0, 16);
counts[3]++;
System.out.printf("[0x219CC] x0="+ new String(Hex.encodeHex(buf)) +"\n");
pointerArg = context.getPointerArg(1);
pointerArg.read(0, buf, 0, 16);
System.out.printf("[0x219CC] x1="+ new String(Hex.encodeHex(buf))+"\n");

attach.addBreakPoint(lr.peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer pointerArg = context.getPointerArg(0);
pointerArg.read(0, buf, 0, 16);
System.out.println(Hex.encodeHex(buf));
System.out.printf("====0x%x====%d====\n",0x219CC,counts[3]);
return true;
}
});
return true;
}
});

找到如下输出位置,从这里开始加密

再次修改hook

限制0x219CC函数的输出条件,从加密的时候开始

如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 0x219CC
final boolean[] is_ok = {false};
attach.addBreakPoint(cryptoDDmodule.base + 0x219CC, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
UnidbgPointer lr = context.getLRPointer();
UnidbgPointer pointerArg = context.getPointerArg(0);
byte[] buf = new byte[16];
pointerArg.read(0, buf, 0, 16);
if(new String(Hex.encodeHex(buf)).equals("786a080869690808616108086f610808")){
is_ok[0] =true;
}
if (is_ok[0]){
counts[3]++;
System.out.printf("[0x219CC] x0="+ new String(Hex.encodeHex(buf)) +"\n");
pointerArg = context.getPointerArg(1);
pointerArg.read(0, buf, 0, 16);
System.out.printf("[0x219CC] x1="+ new String(Hex.encodeHex(buf))+"\n");

attach.addBreakPoint(lr.peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer pointerArg = context.getPointerArg(0);
pointerArg.read(0, buf, 0, 16);
System.out.println(Hex.encodeHex(buf));
System.out.printf("====0x%x====%d====\n",0x219CC,counts[3]);
return true;
}
});
}

return true;
}
});

然后就有完美的输出展示了

把这部分的代码封装成一个函数,方便后续调用

Dfa攻击

我们知道dfa攻击是从第九轮的列混淆开始的

首先保存正确的加密结果

1
c9cbd740c87749c660690b89498d077d

找到第九轮列混淆进行攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Debugger attach = emulator.attach();
int[] counts = {0};
attach.addBreakPoint(cryptoDDmodule.base + 0x22810, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
counts[0]++;
if (counts[0]==9){
Random random = new Random();
UnidbgPointer pointerArg = emulator.getContext().getPointerArg(0);
System.out.println("攻击成功!");
pointerArg.setByte(0,(byte) random.nextInt(255));
}
return true;
}
});

攻击成功之后

写个循环,从第1个字节到第16个字节依次进行攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
for (int i = 0; i < 16; i++) {
Debugger attach = emulator.attach();
int[] counts = {0};
int finalI = i;
attach.addBreakPoint(cryptoDDmodule.base + 0x22810, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
counts[0]++;
if (counts[0]==9){
Random random = new Random();
UnidbgPointer pointerArg = emulator.getContext().getPointerArg(0);
pointerArg.setByte(finalI,(byte) random.nextInt(255));
}
return true;
}
});

ByteArray byteValues = CryptoHelperClass.callStaticJniMethodObject(emulator, "localAESWork([BI[B)[B",
"xiaojiaa".getBytes(),
2,
decode_a10);
System.out.println(Hex.encodeHex(byteValues.getValue()));
}

关掉日志打印,输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0ccbd740c87749fc60698689497f077d
c9d3d7400a7749c660690b98498d527d
c9cbe040c82649c667690b89498d071c
c9cbd737c877ddc660390b89d48d077d
93cbd740c87749c860698b8949ec077d
c970d740737749c660690b26498df27d
c9cbfc40c86949c629690b89498d0741
c9cbd7a4c877a3c660460b89e68d077d
9fcbd740c87749b66069e8894930077d
c927d7401d7749c660690b0f498d787d
c9cb5240c8c249c6f6690b89498d0718
c9cbd781c87753c660d20b891c8d077d
c4cbd740c877493e606905894918077d
c9aed740067749c660690b40498dd47d
c9cbe640c8a349c65a690b89498d0772
c9cbd740c87749c660690b89498d077d

还原第9轮的子密钥

使用phoenixAES库还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import phoenixAES

with open("tracefile","wb") as t: # 第一行放正确的加密结果
t.write("""c9cbd740c87749c660690b89498d077d
0ccbd740c87749fc60698689497f077d
c9d3d7400a7749c660690b98498d527d
c9cbe040c82649c667690b89498d071c
c9cbd737c877ddc660390b89d48d077d
93cbd740c87749c860698b8949ec077d
c970d740737749c660690b26498df27d
c9cbfc40c86949c629690b89498d0741
c9cbd7a4c877a3c660460b89e68d077d
9fcbd740c87749b66069e8894930077d
c927d7401d7749c660690b0f498d787d
c9cb5240c8c249c6f6690b89498d0718
c9cbd781c87753c660d20b891c8d077d
c4cbd740c877493e606905894918077d
c9aed740067749c660690b40498dd47d
c9cbe640c8a349c65a690b89498d0772
c9cbd740c87749c660690b89498d077d
""".encode('utf8'))

phoenixAES.crack_file('tracefile', [], True, False, 3)

输出

1
2
3
4
Round key bytes recovered:
0E10F6D230677EBDF0D7D12C8DBD21CF
Last round key #N found:
0E10F6D230677EBDF0D7D12C8DBD21CF

根据第九轮子密钥推出初始密钥

密钥为655534527846474A7372787545766C74

用密钥对响应体解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

def toStandardB64(data):
return data.replace("-","+").replace("_","/")

def aes_decrypted_ecb(encrypted_data,key):
cipher = AES.new(key,AES.MODE_ECB)
decrypted_data = cipher.decrypt(encrypted_data)
unpadded_data = unpad(decrypted_data,AES.block_size)
return unpadded_data

def decrypt_response(ciphertext):
standardb64 = toStandardB64(ciphertext).encode("utf-8")
plaintext = aes_decrypted_ecb(base64.b64decode(standardb64),bytes.fromhex("655534527846474A7372787545766C74"))
return plaintext.decode("utf-8")

ciphertext = "57l6aGIjM8ySP5qUW5cmXG_swqXZ3-CDvlz4K-CW5JHHIAiIXoiAtXFef1i898pFnXG2UaYYFPgo4B-QISWmwxGVgzf63I98g8s1TCjJ2qHAI2q4UI-IP1nRHfLevhZ8Kgx6gyMYgsoibWQJ_IU1cONmes8MQqWG3BlsfA7TAVesjF0N3KaEEfHH0RONmDSU3hSp9D9T3nl-xsHSpLBlPQPQRsLBEHzAgEeKO5EueBVpw8cCq3WnWYGT_Ai2TxBpFIImOPBAMnR-T3lHx0D4Pp-iF9p7c_xa8Ek3IhwVx5m8lpQCuAta8PG1ffVGX7Pgr82woPcekBWvF9qFV4kiabebYKsMQHmAbnNfKxLsSEs="
print(decrypt_response(ciphertext=ciphertext))

{"bizId":"","busiCode":"BASE001","code":7,"content":null,"handler":"USER","msg":"验证码不正确","shardingId":"","status":"BASE_ERROR","uid":"b4e31b9d-d7ab-40a9-9b64-14e52f633b911741972183784","version":"101","zeusId":"luckycapiproxy-0ade0947-483881-21580"}