文章目录[隐藏]
转载 24 / 27
- 【转载】unidbg hook 使用大全
- 【转载】ida 基本使用 常用快捷键
- 【转载】Frida和调试器共存的问题
- 【转载】Charles抓包Android最佳流量转发APP
- 【转载】安卓自定义代码调用系统函数并编译so记录
- 【转载】jnitrace 手机卡死黑屏问题解决
- 【转载】Unidbg SO 逆向入门实战教程一 OASIS
- 【转载】Unidbg SO 逆向入门实战教程二 calculateS
- 【转载】Unidbg SO 逆向入门实战教程三 V2-Sign
- 【转载】Unidbg SO 逆向入门实战教程四 mfw
- 【转载】Unidbg SO 逆向入门实战教程五 qxs
- 【转载】Unidbg SO 逆向入门实战教程六 s
- 【转载】Unidbg SO 逆向入门实战教程七 main
- 【转载】Unidbg SO 逆向入门实战教程八 文件读写
- 【转载】Unidbg SO 逆向入门实战教程九 blackbox
- 【转载】Unidbg SO 逆向入门实战教程十 simpleSign
- 【转载】android app 加密参数分析研究 hash aes
- 【转载】第一讲——从黑盒攻击模型到白盒攻击模型
- 【转载】第二讲——白盒加密攻击方法的选择
- 【转载】第三讲——差分故障攻击的原理
- 【转载】第四讲——差分故障攻击的工具链
- 【转载】第五讲——使用源码进行DFA攻击
- 【转载】第六讲——使用Frida进行DFA攻击
- 【转载】第七讲——使用Unidbg进行DFA攻击
- 【转载】第八讲——案例
- 【转载】猿人学 - app 逆向比赛第四题 grpc 题解
- 【转载】猿人学 - app 逆向比赛第五题双向认证题解
说说在前面
本文转载龙哥星球 白盒 AES 密码学系列
,相关文件代码等,可加入星球下载
一、引言
这一篇中,我们简单讨论使用Unidbg处理DFA。
二、案例
案例就是前文所处理过的案例,在前面我们说,Unidbg中有另一个办法可以帮助”找时机“。我先卖个关子,大家先往下看。
首先和前文一样,Call wbaes_encrypt_ecb。
package com.luckin;
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.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import java.io.File;
public class LKAES extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public LKAES(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
// 模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/luckin/ruixingkafei_4.9.3.apk"));
vm.setVerbose(true);
// 加载so到虚拟内存
DalvikModule dm = vm.loadLibrary("cryptoDD", true);
module = dm.getModule();
// 设置JNI
vm.setJni(this);
dm.callJNI_OnLoad(emulator);
}
public static byte[] hexStringToBytes(String hexString) {
if (hexString.isEmpty()) {
return null;
}
hexString = hexString.toLowerCase();
final byte[] byteArray = new byte[hexString.length() >> 1];
int index = 0;
for (int i = 0; i < hexString.length(); i++) {
if (index > hexString.length() - 1) {
return byteArray;
}
byte highDit = (byte) (Character.digit(hexString.charAt(index), 16) & 0xFF);
byte lowDit = (byte) (Character.digit(hexString.charAt(index + 1), 16) & 0xFF);
byteArray[i] = (byte) (highDit << 4 | lowDit);
index += 2;
}
return byteArray;
}
public static String bytesTohexString(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if(hex.length() < 2){
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
public void call_wbaes(){
MemoryBlock inblock = emulator.getMemory().malloc(16, true);
UnidbgPointer inPtr = inblock.getPointer();
MemoryBlock outblock = emulator.getMemory().malloc(16, true);
UnidbgPointer outPtr = outblock.getPointer();
byte[] stub = hexStringToBytes("30313233343536373839616263646566");
assert stub != null;
inPtr.write(0, stub, 0, stub.length);
module.callFunction(emulator, 0x17bd5, inPtr, 16, outPtr, 0);
String ret = bytesTohexString(outPtr.getByteArray(0, 0x10));
System.out.println("white box result:"+ret);
inblock.free();
outblock.free();
}
public static void main(String[] args) {
LKAES lkaes = new LKAES();
lkaes.call_wbaes();
}
}
运行结果
[15:41:39 547] INFO [com.github.unidbg.linux.AndroidElfLoader] (AndroidElfLoader:464) - libcryptoDD.so load dependency libandroid.so failed
JNIEnv->FindClass(com/luckincoffee/safeboxlib/CryptoHelper) was called from RX@0x4001b43b[libcryptoDD.so]0x1b43b
JNIEnv->RegisterNatives(com/luckincoffee/safeboxlib/CryptoHelper, RW@0x400dfd0c[libcryptoDD.so]0xdfd0c, 4) was called from RX@0x4001b3e3[libcryptoDD.so]0x1b3e3
RegisterNative(com/luckincoffee/safeboxlib/CryptoHelper, localAESWork([BI[B)[B, RX@0x4001984d[libcryptoDD.so]0x1984d)
RegisterNative(com/luckincoffee/safeboxlib/CryptoHelper, localConnectWork([B[B)[B, RX@0x4001978d[libcryptoDD.so]0x1978d)
RegisterNative(com/luckincoffee/safeboxlib/CryptoHelper, md5_crypt([BI)[B, RX@0x4001a981[libcryptoDD.so]0x1a981)
RegisterNative(com/luckincoffee/safeboxlib/CryptoHelper, localAESWork4Api([BI)[B, RX@0x4001b1cd[libcryptoDD.so]0x1b1cd)
white box result:b0f59c0d48c145915fc8f6a842c4d5eb
可以发现计算结果完全一致。
接下来我们要作图,读者可能会困惑,做什么图?事实上,因为白盒加密的主要实现方式是查表法,所以加密主体就是大量的内存访问。那么记录函数对内存的访问以及发起访问的地址(PC指针),绘制成折线图,就可以较好的反映加密流程。
使用Unidbg的ReadHook
public void traceAESRead(){
emulator.getBackend().hook_add_new(new ReadHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
long now = emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_PC).intValue();
if((now>module.base) & (now < (module.base+module.size))){
System.out.println(now - module.base);
}
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base, module.base+module.size, null);
}
规则如下:监控整个SO地址范围内的内存读取操作,记录其发起地址,我减去了SO基地址,只打印偏移,这样呈现效果更好。
traceAESRead 函数放于如下位置
public static void main(String[] args) {
LKAES lkaes = new LKAES();
lkaes.traceAESRead();
lkaes.call_wbaes();
}
运行结果如图
将这几千条记录拷贝出来,保存在trace.txt 中,在Python中做可视化,这十分方便。需要安装matplotlib以及numpy库。
import matplotlib.pyplot as plt
import numpy
input = numpy.loadtxt("trace.txt", int)
plt.plot(range(len(input)), input)
plt.show()
运行后生成折线图,将其放大是如下效果
X轴的计数单位是次数,表示当前是第几次内存访问,如图,在程序的运行过程中,发生了1400余次对SO内存的读操作。Y轴是发起访问的偏移地址。需要注意,X与Y轴的数值表示为十进制。图上可得,Y主要在80000-100000之间,我们修改Y轴范围,增强呈现效果。
import matplotlib.pyplot as plt
import numpy
input = numpy.loadtxt("trace.txt", int)
# 限制Y
plt.ylim(80000,100000)
plt.plot(range(len(input)), input)
plt.show()
运行后
似乎还可以缩小到85000-90000之间,再次缩小Y的范围
这样看着顺眼多了,我们对它进行分析。
首先,可以比较明显的看到,存在十个重复的模式,这代表了十轮运算。这一点是有用的,可用于区分AES-128/192/256,分别对应10/12/14 轮。
除此之外,我们发现每轮运算的起点是一个较低的地址,具体在86000附近左右,转成十六进制就是0x14FF0附近。
在IDA 中查看该处,我们发现正是上一篇中所分析的wbShiftRows中。这就是我说的”Unidbg中的另一个好办法“,这得益于Unidbg中获取执行流非常容易。
接下来就再次来到故障攻击的部分,整个代码近似于Frida Hook代码的复刻。
package com.luckin;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.ReadHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
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.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.MemoryBlock;
import com.github.unidbg.pointer.UnidbgPointer;
import unicorn.ArmConst;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
public class LKAES extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public LKAES(){
emulator = AndroidEmulatorBuilder.for32Bit().build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
// 模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/luckin/ruixingkafei_4.9.3.apk"));
vm.setVerbose(true);
// 加载so到虚拟内存
DalvikModule dm = vm.loadLibrary("cryptoDD", true);
module = dm.getModule();
// 设置JNI
vm.setJni(this);
dm.callJNI_OnLoad(emulator);
}
public static byte[] hexStringToBytes(String hexString) {
if (hexString.isEmpty()) {
return null;
}
hexString = hexString.toLowerCase();
final byte[] byteArray = new byte[hexString.length() >> 1];
int index = 0;
for (int i = 0; i < hexString.length(); i++) {
if (index > hexString.length() - 1) {
return byteArray;
}
byte highDit = (byte) (Character.digit(hexString.charAt(index), 16) & 0xFF);
byte lowDit = (byte) (Character.digit(hexString.charAt(index + 1), 16) & 0xFF);
byteArray[i] = (byte) (highDit << 4 | lowDit);
index += 2;
}
return byteArray;
}
public static String bytesTohexString(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if(hex.length() < 2){
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
public void call_wbaes(){
MemoryBlock inblock = emulator.getMemory().malloc(16, true);
UnidbgPointer inPtr = inblock.getPointer();
MemoryBlock outblock = emulator.getMemory().malloc(16, true);
UnidbgPointer outPtr = outblock.getPointer();
byte[] stub = hexStringToBytes("30313233343536373839616263646566");
assert stub != null;
inPtr.write(0, stub, 0, stub.length);
module.callFunction(emulator, 0x17bd5, inPtr, 16, outPtr, 0);
String ret = bytesTohexString(outPtr.getByteArray(0, 0x10));
System.out.println(ret);
inblock.free();
outblock.free();
}
public static int randInt(int min, int max) {
Random rand = new Random();
return rand.nextInt((max - min) + 1) + min;
}
public void dfaAttack(){
emulator.attach().addBreakPoint(module.base + 0x14F98 + 1, new BreakPointCallback() {
int count = 0;
UnidbgPointer pointer;
@Override
public boolean onHit(Emulator<?> emulator, long address) {
count += 1;
RegisterContext registerContext = emulator.getContext();
pointer = registerContext.getPointerArg(0);
emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
if(count % 9 == 0){
pointer.setByte(randInt(0, 15), (byte) randInt(0, 0xff));
}
return true;
}
});
return true;
}
});
}
public void traceAESRead(){
emulator.getBackend().hook_add_new(new ReadHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
long now = emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_PC).intValue();
if((now>module.base) & (now < (module.base+module.size))){
System.out.println(now - module.base);
}
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base, module.base+module.size, null);
}
public static void main(String[] args) {
LKAES lkaes = new LKAES();
// lkaes.traceAESRead();
lkaes.dfaAttack();
for(int i =0;i<200;i++){
lkaes.call_wbaes();
}
}
}
将正确密文与运行出的故障密文放入phoenixAES以及stark,可以得到和前文相同的结果。
二、尾声
后续会再补充三个左右样例。