首页
社区
课程
招聘
[原创]KCTF2021 第五题 华山论剑 writeup
发表于: 2021-5-16 12:32 7918

[原创]KCTF2021 第五题 华山论剑 writeup

2021-5-16 12:32
7918

拖进jadx java层逻辑简单,只需要关注native层stringFromJNI。

使用IDA静态分析libhello-jni.so,两个跳转让我直接懵逼,都不知道跳哪去了。
尝试用IDA调试,直接挂,有点绝望。
掏出frida,先跑了下frida-trace,结果正确的序列号弹出了“输入错误”,打不出正确的调用路径。让我直接放弃frida,绝望++。
想起之前瞥过一眼的unidbg-v0.9.3,感觉这题用它正合适。

先试着使用unidbg主动调用stringFromJNI:
图片描述
能够运行和输出正确结果,感觉找对了路。
尝试使用IDA连unidbg动态调试:
emulator.attach(DebuggerType.ANDROID_SERVER_V7);
IDA附加进程后,简单跟踪了几步后,要么跑飞要么卡死...

emulator.traceCode();
traceLog看下发现ARM指令和Thumb指令来回切换。
想恢复IDA的指令静态分析,无奈找不到替代Alt+G(Change Segment Register Value)的api函数,放弃。

看着traceCode的效果:

失望,对于合格的TraceLog来说应该有寄存器的变化。
还是自己写个吧

我这东拼西凑的代码,能用就行。看着日志效果感觉还不错

正确的序列号 name:ed8b9244350d3644 serial:7C9815255BFE832D3F93140B
生成日志trace_log_true.txt,37MB的日志23w多行,直接去分析太耗时间了。

试着控制最小变量来找差异,把序列号最后一位改成C:
name:ed8b9244350d3644 serial:7C9815255BFE832D3F93140C
生成日志trace_log_flase_serial.txt,与正确的大小和行数差不多。

用BeyondCompare比较下,忽略掉push pop nop b 指令行,减少干扰。
看着这差异行并不是很多,才477个差异部分,感觉自己又可以了。
根据习惯倒着浏览差异处
图片描述
0x400076a4的r4寄存器存着输入的序列号最后一位,感觉找到了关键位置。
搜索 "0x400076a4" "0x400076a8" trace_log_true.txt (匹配60次)

搜索 "0x400076a4" "0x400076a8" trace_log_flase_serial.txt (匹配60次)

看着这熟悉的序列号,算法大概是通过name算出serial后再与输入的进行异或对比。
使用 name:KCTF serial:7C9815255BFE832D3F93140B 生成trace_log_false_name_kctf.txt
搜索 "0x400076a8" "0x400076a8" trace_log_false_name_kctf.txt (匹配60次) 还好相同,说明没有少跑逻辑。

推测r5异或前就是正确的序列号

17726331DA0FE737149C8202
图片描述

public void Btn1_Click(View view) {
    String str;
    String input = this.text.getText().toString();
    String input2 = this.text2.getText().toString();
    if (input == null || input.isEmpty()) {
        str = "name为空";
    } else if (input2 == null || input2.isEmpty()) {
        str = "serial为空";
    } else {
        System.loadLibrary("hello-jni");
        str = stringFromJNI(input, input2);
    }
    AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
    builder.setTitle("");
    builder.setMessage(str);
    builder.show();
}
public void Btn1_Click(View view) {
    String str;
    String input = this.text.getText().toString();
    String input2 = this.text2.getText().toString();
    if (input == null || input.isEmpty()) {
        str = "name为空";
    } else if (input2 == null || input2.isEmpty()) {
        str = "serial为空";
    } else {
        System.loadLibrary("hello-jni");
        str = stringFromJNI(input, input2);
    }
    AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
    builder.setTitle("");
    builder.setMessage(str);
    builder.show();
}
 
 
 
### Trace Instruction [libhello-jni.so] [0x07028] [ 01 40 84 e2 ] 0x40007028: add r4, r4, #1
### Trace Instruction [libhello-jni.so] [0x0702c] [ 01 30 43 e2 ] 0x4000702c: sub r3, r3, #1
### Trace Instruction [libhello-jni.so] [0x07030] [ ca a0 a0 e1 ] 0x40007030: asr sl, sl, #1
### Trace Instruction [libhello-jni.so] [0x07034] [ df ff ff ea ] 0x40007034: b #0x40006fb8
### Trace Instruction [libhello-jni.so] [0x06fb8] [ 0a 20 01 e0 ] 0x40006fb8: and r2, r1, sl
### Trace Instruction [libhello-jni.so] [0x06fbc] [ 52 23 a0 e1 ] 0x40006fbc: asr r2, r2, r3
### Trace Instruction [libhello-jni.so] [0x06fc0] [ 00 20 22 e2 ] 0x40006fc0: eor r2, r2, #0
### Trace Instruction [libhello-jni.so] [0x07028] [ 01 40 84 e2 ] 0x40007028: add r4, r4, #1
### Trace Instruction [libhello-jni.so] [0x0702c] [ 01 30 43 e2 ] 0x4000702c: sub r3, r3, #1
### Trace Instruction [libhello-jni.so] [0x07030] [ ca a0 a0 e1 ] 0x40007030: asr sl, sl, #1
### Trace Instruction [libhello-jni.so] [0x07034] [ df ff ff ea ] 0x40007034: b #0x40006fb8
### Trace Instruction [libhello-jni.so] [0x06fb8] [ 0a 20 01 e0 ] 0x40006fb8: and r2, r1, sl
### Trace Instruction [libhello-jni.so] [0x06fbc] [ 52 23 a0 e1 ] 0x40006fbc: asr r2, r2, r3
### Trace Instruction [libhello-jni.so] [0x06fc0] [ 00 20 22 e2 ] 0x40006fc0: eor r2, r2, #0
package com.kctf;
 
import capstone.Capstone;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.ARM;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.DebugHook;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.*;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
 
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
 
public class hellojni extends AbstractJni {
 
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
 
    private final DvmClass hellojniUtils;
 
    private final boolean logging;
 
    private final Set<Integer> trace_addr;
    private final Map<Integer, String> insn_map;
    private final Map<Integer, Integer> insn_size;
    private String lastContent;
    private int[] lastReg;
 
    hellojni(boolean logging) {
        lastContent = "";
        lastReg = null;
        trace_addr = new TreeSet<Integer>();
        insn_map = new TreeMap<Integer, String>();
        insn_size = new TreeMap<>();
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.example.hellojni").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
 
        vm = emulator.createDalvikVM(null); // 创建Android虚拟机
 
        vm.setJni(this);
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libhello-jni.so"), true); // 加载libhello-jni.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libhello-jni.so对应为一个模块
 
        //com_example_hellojni_HelloJni_
        hellojniUtils = vm.resolveClass("com/example/hellojni/HelloJni");
    }
 
    void destroy() throws IOException {
        Object[] itr = trace_addr.toArray();
        File f = new File("E:/out.txt");
        FileWriter fw = new FileWriter(f);
        for (Object i : itr) {
//            System.out.println(String.format("set_color(0x%x, CIC_FUNC, 0xd3d3d3) %s", (Integer) i, insn_map.get(i)));
//            fw.write(insn_map.get(i));
            fw.write(String.format("setreg(0x%x, %d)\n", i, insn_size.get(i)));
        }
        fw.close();
 
        emulator.close();
        if (logging) {
            System.out.println("destroy");
        }
    }
 
    public static void main(String[] args) throws Exception {
        com.kctf.hellojni test = new com.kctf.hellojni(true);
 
        String data = test.stringFromJNI("ed8b9244350d3644", "7C9815255BFE832D3F93140B");
        System.out.println(data);
        test.destroy();
    }
 
    public static int[] getReg(String reg) {
        int[] ret = new int[1];
        switch (reg) {
            case "r0": ret[0] = ArmConst.UC_ARM_REG_R0; break;
            case "r1": ret[0] = ArmConst.UC_ARM_REG_R1; break;
            case "r2": ret[0] = ArmConst.UC_ARM_REG_R2; break;
            case "r3": ret[0] = ArmConst.UC_ARM_REG_R3; break;
            case "r4": ret[0] = ArmConst.UC_ARM_REG_R4; break;
            case "r5": ret[0] = ArmConst.UC_ARM_REG_R5; break;
            case "r6": ret[0] = ArmConst.UC_ARM_REG_R6; break;
            case "r7": ret[0] = ArmConst.UC_ARM_REG_R7; break;
            case "r8": ret[0] = ArmConst.UC_ARM_REG_R8; break;
            case "r9": ret[0] = ArmConst.UC_ARM_REG_R9; break;
            case "r10": ret[0] = ArmConst.UC_ARM_REG_R10; break;
            case "r11": ret[0] = ArmConst.UC_ARM_REG_R11; break;
            case "r12": ret[0] = ArmConst.UC_ARM_REG_R12; break;
            case "pc": ret[0] = ArmConst.UC_ARM_REG_PC; break;
            case "sp": ret[0] = ArmConst.UC_ARM_REG_SP; break;
            case "lr": ret[0] = ArmConst.UC_ARM_REG_LR; break;
            case "sb": ret[0] = ArmConst.UC_ARM_REG_SB; break;
            default: return null;
        }
        return ret;
    }
 
    String getMemStr(Backend backend, int addr) {
        StringBuilder builder = new StringBuilder();
        a:
        do {
            try {
                byte[] bytes = backend.mem_read(addr, 1);
                for (int i = 0; i < bytes.length; i++) {
                    int fb = bytes[i];
                    if (fb <= 31 || fb >= 127)
                        break a;
                    builder.append(String.format("%c",fb));
                }
                addr++;
            } catch (Exception e) {
                return "";
            }
        } while (true);
        return builder.toString();
    }
 
    String stringFromJNI(String name, String serial) {
        if(false)
        {
            Object ret = hellojniUtils.callStaticJniMethodObject(emulator, "stringFromJNI(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String", name, serial); // 执行Jni方法
            return ret.toString();
        }
        // so function address
        final Set<Integer> st = new TreeSet<Integer>() {{
            Integer[] a = new Integer[]{0xff5, 0x1035, 0x10e5, 0x1421, 0x1438, 0x14dc, 0x152c, 0x1634, 0x16c0, 0x1724, 0x1854, 0x1914, 0x19c4, 0x1a48, 0x1aa4, 0x1ad0, 0x1b98, 0x1fa4, 0x2350, 0x2394, 0x24b4, 0x2540, 0x28c8};
            for (Integer b : a) {
                add(b);
            }
        }};
        emulator.getBackend().hook_add_new(new DebugHook() {
            @Override
            public void onBreak(Backend backend, long address, int size, Object user) {
            }
 
            @Override
            public void hook(Backend backend, long address, int size, Object user) {
                //打印当前地址。这里要把unidbg使用的基址给去掉。
                int addr = (int) address - 0x40000000;
                trace_addr.add(addr);
                if (st.contains(addr + 1)) {
//                    System.out.println(String.format("0x%x", addr));
                }
//
                if (false) {
                    Capstone.CsInsn[] insns = emulator.printAssemble(System.out, address, size);
//                    ARM.showRegs(emulator, ARM.getRegArgs(emulator));
                }
                if (true) {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    PrintStream ps = null;
                    try {
                        ps = new PrintStream(baos, true, "utf-8");
                    } catch (IOException e) {
                    }
                    Capstone.CsInsn[] insns = emulator.printAssemble(ps, address, size);
                    String content = new String(baos.toByteArray(), StandardCharsets.UTF_8);
                    content = content.replace('\n', ' ');
                    ps.close();
                    String regStr = "";
                    for (int i = 0; i < insns.length; i++) {
                        regStr = insns[i].opStr.split(",")[0];
                    }
                    int[] regs = getReg(regStr);
                    if (regs != null) {
                        content = content + " >>> " + regStr + "=" + "0x%x";
                    }
                    if (lastReg == null) {
                        System.out.print(lastContent);
                        ARM.showRegs(emulator, null);
                    } else {
                        int value = backend.reg_read(lastReg[0]).intValue();
                        String memStr = getMemStr(backend, value);
                        if (!memStr.equals("")) {
                            memStr = "\"" + memStr + "\"";
                        }
                        System.out.println(String.format(lastContent, value) + " " + memStr);
 
                    }
 
                    lastContent = content;
                    lastReg = regs;
                    if (!insn_map.containsKey(addr)) {
                        insn_map.put(addr, content);
                        insn_size.put(addr, (int) insns[0].size);
                    }
                }
            }
        }, 0x40000ff4, 0x400076BC, null);
//        emulator.traceCode();
//        emulator.traceWrite();
//        emulator.traceRead();
//        emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,可输入c命令取消附加继续运行
        Object ret = hellojniUtils.callStaticJniMethodObject(emulator, "stringFromJNI(Ljava/lang/String;Ljava/lang/String)Ljava/lang/String", name, serial); // 执行Jni方法
        return ret.toString();
    }
 
}
package com.kctf;
 
import capstone.Capstone;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.Symbol;
import com.github.unidbg.arm.ARM;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.DebugHook;
import com.github.unidbg.arm.context.Arm32RegisterContext;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.*;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
 
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
 
public class hellojni extends AbstractJni {
 
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
 
    private final DvmClass hellojniUtils;
 
    private final boolean logging;
 
    private final Set<Integer> trace_addr;
    private final Map<Integer, String> insn_map;
    private final Map<Integer, Integer> insn_size;
    private String lastContent;
    private int[] lastReg;
 
    hellojni(boolean logging) {
        lastContent = "";
        lastReg = null;
        trace_addr = new TreeSet<Integer>();
        insn_map = new TreeMap<Integer, String>();
        insn_size = new TreeMap<>();
        this.logging = logging;
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.example.hellojni").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
 
        vm = emulator.createDalvikVM(null); // 创建Android虚拟机
 
        vm.setJni(this);
        vm.setVerbose(logging); // 设置是否打印Jni调用细节
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libhello-jni.so"), true); // 加载libhello-jni.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
        module = dm.getModule(); // 加载好的libhello-jni.so对应为一个模块
 
        //com_example_hellojni_HelloJni_
        hellojniUtils = vm.resolveClass("com/example/hellojni/HelloJni");
    }
 
    void destroy() throws IOException {
        Object[] itr = trace_addr.toArray();
        File f = new File("E:/out.txt");
        FileWriter fw = new FileWriter(f);
        for (Object i : itr) {
//            System.out.println(String.format("set_color(0x%x, CIC_FUNC, 0xd3d3d3) %s", (Integer) i, insn_map.get(i)));
//            fw.write(insn_map.get(i));
            fw.write(String.format("setreg(0x%x, %d)\n", i, insn_size.get(i)));
        }
        fw.close();
 
        emulator.close();
        if (logging) {
            System.out.println("destroy");
        }
    }
 
    public static void main(String[] args) throws Exception {
        com.kctf.hellojni test = new com.kctf.hellojni(true);
 
        String data = test.stringFromJNI("ed8b9244350d3644", "7C9815255BFE832D3F93140B");
        System.out.println(data);
        test.destroy();
    }
 
    public static int[] getReg(String reg) {
        int[] ret = new int[1];
        switch (reg) {
            case "r0": ret[0] = ArmConst.UC_ARM_REG_R0; break;
            case "r1": ret[0] = ArmConst.UC_ARM_REG_R1; break;
            case "r2": ret[0] = ArmConst.UC_ARM_REG_R2; break;
            case "r3": ret[0] = ArmConst.UC_ARM_REG_R3; break;
            case "r4": ret[0] = ArmConst.UC_ARM_REG_R4; break;
            case "r5": ret[0] = ArmConst.UC_ARM_REG_R5; break;
            case "r6": ret[0] = ArmConst.UC_ARM_REG_R6; break;
            case "r7": ret[0] = ArmConst.UC_ARM_REG_R7; break;
            case "r8": ret[0] = ArmConst.UC_ARM_REG_R8; break;
            case "r9": ret[0] = ArmConst.UC_ARM_REG_R9; break;
            case "r10": ret[0] = ArmConst.UC_ARM_REG_R10; break;
            case "r11": ret[0] = ArmConst.UC_ARM_REG_R11; break;
            case "r12": ret[0] = ArmConst.UC_ARM_REG_R12; break;
            case "pc": ret[0] = ArmConst.UC_ARM_REG_PC; break;
            case "sp": ret[0] = ArmConst.UC_ARM_REG_SP; break;
            case "lr": ret[0] = ArmConst.UC_ARM_REG_LR; break;
            case "sb": ret[0] = ArmConst.UC_ARM_REG_SB; break;
            default: return null;
        }
        return ret;
    }
 
    String getMemStr(Backend backend, int addr) {
        StringBuilder builder = new StringBuilder();
        a:
        do {
            try {
                byte[] bytes = backend.mem_read(addr, 1);
                for (int i = 0; i < bytes.length; i++) {
                    int fb = bytes[i];
                    if (fb <= 31 || fb >= 127)
                        break a;
                    builder.append(String.format("%c",fb));
                }
                addr++;
            } catch (Exception e) {
                return "";
            }
        } while (true);
        return builder.toString();
    }
 
    String stringFromJNI(String name, String serial) {
        if(false)
        {

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-5-16 15:53 被Wblank编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (2)
雪    币: 269
活跃值: (906)
能力值: ( LV12,RANK:345 )
在线值:
发帖
回帖
粉丝
2
棒棒的,下次把eor拿掉,太简单了
2021-5-17 14:43
0
雪    币: 222
活跃值: (140)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感觉这里有头大牛
2021-5-17 19:51
0
游客
登录 | 注册 方可回帖
返回
//