-
-
[原创]混淆还原的几种方式实践
-
发表于: 2024-12-5 19:38 1973
-
作者@uiop
下面都是使用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进行修改即可
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- 实战分享:构建高效平台型C2的经验总结 1167
- [原创]浅谈目录权限导致的文件劫持 1585
- [原创]混淆还原的几种方式实践 1974
- [原创]指针分析与Java反序列化利用链挖掘实践(一) 1643
- [原创]什么?IL2CPP APP分析这一篇就够啦! 3504