App逆向百例|18|某A系防护SO跳转修复
2023-6-1 18:22:28 Author: 逆向lin狗(查看原文) 阅读量:54 收藏

观前提示:
本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除
样本: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


文章来源: http://mp.weixin.qq.com/s?__biz=MzUxMjU3ODc1MA==&mid=2247485887&idx=1&sn=2a1821c1aaa457994e66e840ed33e0b7&chksm=f9630c6ece1485788415bc4a71439adf35cf464f1dc49410737861c510a5bf0a97441644c7a0#rd
如有侵权请联系:admin#unsafe.sh