样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMWl5Q21NNVd1WC1NTkNsRkh2X0w3TVE/cHdkPWxpbm4=
最近比较忙,拖更了一段时间,不过好在事情在逐步逐步地减少,毕设搞完了,论文答辩了,毕业照也拍了,出来社会当上社畜了,所以更新频率会降低很多,不过我还是会将工作上遇到的东西拿出部分来做案例分享,可能会比较简单甚至出错,大佬们轻点喷。
大姐姐打开libsgmainso-6.5.55.so
直接在导出表找到JNI_OnLoad
进来
可以看到没有正常的解析出预想的代码 而是出现了
__asm { BR X11 }
跳回汇编看看
SUB SP, SP, #0x50
STP X25, X23, [SP,#0x40+var_30]
STP X22, X21, [SP,#0x40+var_20]
STP X20, X19, [SP,#0x40+var_10]
STP X29, X30, [SP,#0x40+var_s0]
ADD X29, SP, #0x40
MRS X21, #3, c13, c0, #2
LDR X8, [X21,#0x28]
MOV W9, #0x8A
ADRL X10, dword_166320
STR X8, [SP,#0x40+var_38]
STR W9, [SP,#0x40+var_3C]
LDRB W9, [X10,#(byte_166352 - 0x166320)]
MOV X20, X0
ADD X10, SP, #0x40+var_3C
MOV W8, #0xCE
ADD W9, W9, #0x8A
ADRP X22, #0x166000
STR W9, [SP,#0x40+var_3C]
ADR X11, loc_1E10C
LDRSW X3, =0xFFFFFEF7
LDRSW X25, [X10]
ADD X3, X3, X25
ADD X11, X11, X3
MOV X9, #0x16
BR X11
根据上面的汇编代码可知 最终需要跳转的地址放在X11
寄存器里面 而寄存器是通过计算得到的 除去入栈出栈的操作 最终可以简化为
X10 = 0x166320
W9 = X10 + 0x32 = 0x166352=>0xab
W9 = W9 + 0x8a = 0x135
X25 = W9
X3 = 0xFFFFFEF7 + X25 = 0x2c
X11 = 0x1E10C + X3 = 0x1e138
得到X11的地址手动Patch一下看看效果
确认后发现对应的地址是一堆常量 那就需要我们手动将对应地址回归undefined状态(快捷键 U)
接着再到对应的地址创建Function(快捷键 P)
最后回到伪代码界面 按下F5让代码重新分析即可
这里被IDA识别为sub方法 不过问题不大 只需保存Patch文件 然后IDA重新打开即可
接回原来的话 手动修复跳转后 可以看到大段的代码 说明目的已经达到了
但是一个so里面不止一处的出现跳转 况且还有好几个so需要跳转修复 这将是一处大工程 手动还原是不现实的
这里我将尝试使用flare_emu
代替手动计算工作 为此创建一个脚本
将范围限制在[0x1E0B8-0x1E120]
import flare_emu
myEH= flare_emu.EmuHelper()
myEH.emulateRange(
startAddr=0x1E0B8,
endAddr=0x1E120
)
myEH.getRegVal("X11")
将脚本放到IDA里面运行 得到X11
的结果为0x1e08d
这和我们前面计算的0x1e138
相差甚远
那么问题出现在哪里呢?
通过分析发现 手动获取0x166352
得到
myEH.getEmuBytes(0x166352,1)
Out[9]: bytearray(b'\x00')
而在unidbg中拿到0x166352
却是有值的
那么问题就显而易见了 如果要解决可以使用dump_so
去拿到初始化后的so文件
但是我并没有那么做 这次我使用unidbg去做跳转修复(有点杀鸡用牛刀的感觉了)
前面已经分析了那么多 我就直接上脚本了(学习借鉴了一篇看雪的文章)
首先你得补出一份可以跑得通的unidbg环境
主要思想就是tracecode 并实时保存10条汇编数据 只要出现BR
跳转指令 就进入判断特征 符合我们筛选的条件后 记录地址和真实跳转地址 等待完全执行完后 将记录下来的所有数据替换进原始SO文件输出修复后的SO文件
public void find_junk(long base_addr, long so_size) {
patchlist = new ArrayList<>();
emulator.getBackend().hook_add_new(new CodeHook() {
int count_code = 0;
String[] opcode = new String[10];
Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64, Capstone.CS_MODE_ARM);
@Override
public void hook(Backend backend, long address, int size, Object user) {
byte[] bytes = emulator.getBackend().mem_read(address, 4);
Instruction[] disasm = capstone.disasm(bytes, 0);
String mnemonic = disasm[0].getMnemonic();
String opStr = disasm[0].getOpStr();
opcode[(count_code % 10)] = mnemonic;
count_code++;
if (mnemonic.indexOf("br") != -1) {
List<String> list = Arrays.asList(opcode);
if (list.contains("add") && list.contains("ldrsw")) {
int reg = 0;
switch (opStr) {
case "x0":
reg = 199;
break;
case "x1":
reg = 200;
break;
case "x2":
reg = 201;
break;
case "x3":
reg = 202;
break;
case "x4":
reg = 203;
break;
case "x5":
reg = 204;
break;
case "x6":
reg = 205;
break;
case "x7":
reg = 206;
break;
case "x8":
reg = 207;
break;
case "x9":
reg = 208;
break;
case "x10":
reg = 209;
break;
case "x11":
reg = 210;
break;
case "x12":
reg = 211;
break;
case "x13":
reg = 212;
break;
case "x14":
reg = 213;
break;
case "x15":
reg = 214;
break;
case "x16":
reg = 215;
break;
case "x17":
reg = 216;
break;
case "x18":
reg = 217;
break;
case "x19":
reg = 218;
break;
case "x20":
reg = 219;
break;
case "x21":
reg = 220;
break;
case "x22":
reg = 221;
break;
case "x23":
reg = 222;
break;
case "x24":
reg = 223;
break;
case "x25":
reg = 224;
break;
case "x26":
reg = 225;
break;
case "x27":
reg = 226;
break;
case "x28":
reg = 227;
break;
}
long value = (long) emulator.getBackend().reg_read(reg);
int TRUE_JUMP = (int) (value - base_addr);
if (TRUE_JUMP < so_size) {
PatchIns patchIns = new PatchIns();
patchIns.setAddr(address - base_addr);
patchIns.setIns("b 0x" + Integer.toHexString(TRUE_JUMP));
patchlist.add(patchIns);
}
}
}
} @Override
public void onAttach(UnHook unHook) {}
@Override
public void detach() {}
}, base_addr, base_addr + so_size, null);
}
public void save_fix(String so_path, String so_fix_path) {
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
try {
File sgmain = new File(so_path);
FileInputStream fileInputStream = new FileInputStream(sgmain);
byte[] data = new byte[(int) sgmain.length()];
fileInputStream.read(data);
fileInputStream.close();
for (PatchIns patchins : patchlist) {
KeystoneEncoded assemble = keystone.assemble(patchins.ins, (int) patchins.addr);
for (int i = 0; i < assemble.getMachineCode().length; i++) {
data[(int) patchins.addr + i] = assemble.getMachineCode()[i];
}
}
File sgmain_fix = new File(so_fix_path);
FileOutputStream fileOutputStream = new FileOutputStream(sgmain_fix);
fileOutputStream.write(data);
fileOutputStream.flush();
fileOutputStream.close();
System.out.println("fix finish,fix count->" + patchlist.toArray().length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
最终修复效果
还原效果还是很不错的 只要运行到了的 基本被修复好了
看看doCommandNative
入口 效果也可以
不过也会出现例外的情况 例如极少量的误判没有还原到 不过更大的可能性是 没有执行到这个位置 所以没做出修复操作 例如下面这种分支
不过总的来说效果还是ok的
《记一次基于unidbg模拟执行的去除ollvm混淆》 https://mp.weixin.qq.com/s/KuWi39Grl9lrhI8iY_S8pw