下面都是使用goron的混淆进行符号执行以及模拟执行处理的结果
还原前
还原后
符号执行和ollvm还原思路相同:
找序言块、真实块、ret块、分发器;在deflate,不同的是需要续住寄存器的值来精准找到下一真实块
发现赋值在序言块
思路
在执行真实块之前对x29偏移出进行初始化赋值或者直接将序言块寄存器状态续到真实块对代码进行修改,结合https://github.com/cq674350529/deflat的解混淆修改即可简单实现
分析间接跳转如下,通过手动计算跳转地址(这里是模拟执行获取跳转地址)再根据条件判断将br指令进行替换即可手动还原
替换指令为br指令以及前一条指令,根据条件指令替换为ture和false的分支跳转
先处理间接跳转,通过汇编代码特征找到判断分支csel的两个寄存器值并获取条件指令,条件true和false的值,通过条件获取ldr的两个值,add固定值,然后替换br和上一条指令:b+条件指令true的地址,bfalse地址,找找前人造的轮子https://bbs.kanxue.com/thread-277086.htm进行修改即可
while (!finish && !instructions.empty())
{
instructions.pop();
ins = instructions.peek().getIns();
if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("add"))
{
String[] split = ins.getOpStr().split(",");
if(split.length == 4)
{
//split[0].toLowerCase(Locale.ROOT).trim().equals("x12") &&
if(split[3].toLowerCase(Locale.ROOT).trim().equals("sxtw"))
{
String reg = split[2].trim().toLowerCase(Locale.ROOT);
base = getRegValue(reg,instructions.peek().getRegs()).longValue();
addinstaddr = instructions.peek().getAddr() - module.base;
}
// else {
// break;
// }
}
// else
// {
// break;
// }
}
if(ins.getMnemonic().toLowerCase(Locale.ROOT).equals("ldr"))
{
String[] sp = ins.getOpStr().toLowerCase().split(",");
if(sp.length == 4)
{
//sp[0].trim().toLowerCase(Locale.ROOT).equals("x12") &&
if(sp[3].trim().toLowerCase(Locale.ROOT).equals("uxtw #3]"))
{
String reg = sp[1].toLowerCase(Locale.ROOT).trim().substring(1);
listoffset = getRegValue(reg,instructions.peek().getRegs()).longValue()-module.base;
ldaaddr = instructions.peek().getAddr()- module.base;
}
}
}
if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("csel") || ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("csinc"))
{
String[] sp = ins.getOpStr().toLowerCase(Locale.ROOT).split(",");
if(sp.length == 4)
{
cond = sp[3].trim();
if(sp[0].trim().equals("w10")&& !sp[2].trim().equals("wzr"))
{
String reg1 = sp[1].trim();
String reg2 = sp[2].trim();
cond1 = getRegValue(reg1,instructions.peek().getRegs()).longValue();
cond2 = getRegValue(reg2,instructions.peek().getRegs()).longValue();
selectaddr = instructions.peek().getAddr() - module.base;
}
if(sp[0].trim().equals("w10")&& sp[2].trim().equals("wzr"))
{
String reg1 = sp[1].trim();
cond1 = getRegValue(reg1,instructions.peek().getRegs()).longValue();
cond2 = 1;
selectaddr = instructions.peek().getAddr() - module.base;
}
}
}
if(ins.getMnemonic().trim().toLowerCase(Locale.ROOT).equals("subs") && ins.getOpStr().trim().toLowerCase(Locale.ROOT).equals("w8, w9, w8"))
{
if(base == -1 || listoffset == -1 || cond1 == -1 || cond2 == -1 || cond.equals("") || addinstaddr == -1 || ldaaddr == -1 || selectaddr == -1)
{
break;
}
else
{
long offset1 = base + readInt64(emulator.getBackend(), module.base+listoffset+cond1*8) - module.base;
long offset2 = base + readInt64(emulator.getBackend(),module.base+listoffset+cond2*8) - module.base;
if( brinsaddr - addinstaddr != 4)
{
System.out.println("add ins and br ins gap more than 4 size,may make mistake");
}
String condBr = "b"+cond.toLowerCase(Locale.ROOT) + " 0x"+ Integer.toHexString((int) (offset1 - addinstaddr));
String br = "b 0x" + Integer.toHexString((int)(offset2 - brinsaddr));
还原前不能f5:
还原后还存在平坦化:
获取执行流
再根据执行流patch之后即可还原
符号执行在处理类似ollvm每个块都已经初始化好的比较好处理,模拟执行处理复杂运算的跳转好用,发现都得结合手动还原,工具只是代替手动部分的批量实现,所以本质还是手动还原的结果;或许ai训练总结算式自动编写d810的配置可能效果更要好一些
参考:
https://github.com/cq674350529/deflat
https://bbs.kanxue.com/thread-277086.htm
https://github.com/amimo/goron
已在FreeBuf发表 0 篇文章
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022