SeeFlowerX - 2022年8月
type: channel
link: https://blog.seeflower.dev/2022/08
description: Be patient.
items: eBPF on Android之编译ecapture
items: dump内存与模拟执行（五）——实战复杂样本
items: dump内存与模拟执行（四）——接入unidbg
items: dump内存与模拟执行（三）——实践dump上下文
items: dump内存与模拟执行（二）——编写dump脚本

eBPF on Android之编译ecapture
type: item
date: 2022-08-09T21:36:00-04:00
description: 前言好像还没有人发过编译安卓版ecapture的教程，这里记录下编译要点步骤编译必须是在arm64的系统下进行官方指南建议使用ubuntu aarch64编译，不过编译老是买服务器也耗不起所以我这搞个手机编译，首先参考下面的文章在手机上准备好rootfseBPF on Android之bcc环境准备——eadb简版在rootfs系统安装上下面的库/软件，如果遇到网络问题自行挂代理安装编译工具apt install cmake apt install llvm-12 apt install clang-12安装go，通过下面的方式安装（记得proxychains挂代理）wget "https://golang.org/dl/go1.17.12.linux-arm64.tar.gz" tar -C /usr/local -xvf "go1.17.12.linux-arm64.tar.gz"rootfs好像没有xz命令，安装上apt install xz-utils拉取ecapturegit clone https://github.com/ehids/ecapture.git cd ecapture先向~/.bashrc添加下面的配置，然后source ~/.bashrc注意，如果rootfs退出重新进，这一步需要再次手动执行export GOPATH=$HOME/go export PATH=/usr/local/go/bin:$PATH:$GOPATH/bin然后设置环境变量export CMD_CLANG=clang-12 export CMD_STRIP=llvm-strip-12 export CMD_LLC=llc-12 export GOPROXY=https://proxy.golang.com.cn,direct开始编译ANDROID=1 make nocore然后会提示找不到linux/kconfig.h，详细错误如下clang-12 \ -I ./kern -I ./kern/bpf/arm64 \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/arch/arm64/include \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/arch/arm64/include/uapi \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/arch/arm64/include/generated \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/arch/arm64/include/generated/uapi \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/include \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/include \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/include/uapi \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/include/generated \ -I /lib/modules/5.10.43-android12-9-00007-g9771767708df-ab8009062/build/include/generated/uapi \ -emit-llvm -O2 -S -xc -g -D__BPF_TRACING__ -D__KERNEL__ -DNOCORE -Wall -Wno-unused-variable -Wno-frame-address -Wno-unused-value -Wno-unknown-warning-option -Wno-pragma-once-outside-header -Wno-pointer-sign -Wno-gnu-variable-sized-type-not-at-end -Wno-deprecated-declarations -Wno-com…

dump内存与模拟执行（五）——实战复杂样本
type: item
date: 2022-08-07T13:34:00-04:00
description: 前言在上一篇系列文章中，以为绿洲为样本演示了dump上下文后模拟执行的整个过程绿洲com.weibo.xvideo.NativeApi的s算法相对简单，涉及的JNI调用较少，整体模拟执行过程还是比较顺利但这似乎让人对dump上下文后模拟执行的优势感知不强，本文将以更加复杂的目标算法为例以提升用户感知~本文的样本是快手 10.6.50.26734 32位目标是模拟执行com.kuaishou.android.security.internal.dispatch.JNICLibrary的doCommandNative方法记录frida hook获取入参和结果首先是通过frida获取传入参数，不熟悉frida的同学可以看看怎么打印[Ljava.lang.Object;包裹的数组和Ljava/lang/Object;包裹的[Bfunction obj_byte_arr_to_hexstr(byte_arr){ let HexDump = Java.use("com.android.internal.util.HexDump"); // HexDump.hexStringToByteArray() // HexDump.toHexString() let buffer = Java.cast(byte_arr, Java.use("[B")); return HexDump.toHexString(Java.array('byte', buffer)); } function hook_ks_doCommandNative(){ Java.perform(function(){ let Arrays = Java.use("java.util.Arrays"); let ArrayClz = Java.use("java.lang.reflect.Array"); let JNICLibrary = Java.use("com.kuaishou.android.security.internal.dispatch.JNICLibrary"); JNICLibrary.doCommandNative.overload('int', '[Ljava.lang.Object;').implementation = function(num, objs){ console.log(`[enter] => ${num} ${objs}`); let bflag = false; if (num == 10400){ bflag = true; } let arr = Arrays.asList(objs); for (let index = 0; index < arr.size(); index++) { let element = arr.get(index); console.log(`[arrarr] ${index} ===>${element}<===`); if (index == 0){ if (bflag){ console.log(`[first arr] ===>${obj_byte_arr_to_hexstr(element)}<===`); } else{ for(let index = 0; index < ArrayClz.getLength(element); index++){ let item = ArrayClz.get(element, index).toString(); console.log(`[first arr] ${index} ===>${item}<===`); } } } } let result = this.doCommandNative(num, objs); if (bflag){ console.log(`[leave] => ${obj_byte_arr_to_hexstr(result)}`); }else{ console.log(`[leave] => ${result}`); } return result; } }) }可以得到这些输出，现在入参和结果都有了[enter] => 10418 [Ljava.lang.String;@a2ac698,d7b7d042-d4f2-4012-be60-d97ff2429c17,-1,false,com.yxcorp.gifshow.App@9ac1cd7,,false, [arrarr] 0 ===>[Ljava.lang.String;@a2ac698<=== [first arr] 0 ===>/rest/n/magicFace/ycnn/models88a33d1370a36dd86aa8af7781ed3bb3<===…

dump内存与模拟执行（四）——接入unidbg
type: item
date: 2022-08-06T12:03:00-04:00
description: 前言以绿洲 v4.5.6为例（64位），目标是com.weibo.xvideo.NativeApi的s函数通过frida hook RegisterNative，可以得到其native函数位于liboasiscore.so + 0x116CC在上一篇文章中，已经实现了APP运行到liboasiscore.so + 0x116CC时的上下文dump了现在可以开始尝试使用unidbg加载上下文并模拟执行，这里使用当前最新的0.9.7版本可能出现的问题如果开启traceCode出现null的异常，请修改AssemblyCodeDumper中的代码：maxLengthLibraryName = memory.getMaxLengthLibraryName().length();改为：maxLengthLibraryName = 32;步骤先搭个框架如下，目前这里不需要载入任何so，仅仅初始化虚拟机即可public class NativeApi extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; NativeApi() { emulator = AndroidEmulatorBuilder.for64Bit() .setProcessName("com.sina.oasis") .addBackendFactory(new Unicorn2Factory(true)) .build(); // 创建模拟器实例，要模拟32位或者64位，在这里区分 final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 vm = emulator.createDalvikVM(); // 创建Android虚拟机 vm.setJni(this); vm.setVerbose(true); // 设置是否打印Jni调用细节 } public static void main(String[] args) throws Exception { NativeApi mNativeApi = new NativeApi(); mNativeApi.load_context("unidbg-android/src/test/resources/DumpContext_20220806_130827"); mNativeApi.s(); } private void load_context(String dump_dir) throws IOException, DataFormatException, IOException { } private void s() { } }Q: 先思考一个问题，已经有上下文了，那么加载上下文，使用unidbg接着上下文运行，需要做什么？A: 首先当然是还原上下文状态，即设置寄存器，加载内存A: 其次是从上下文的地方开始运行Q 再想想为什么要使用unidbg而不是直接使用unicorn呢？A unidbg对jni和syscall的支持相对完善，接着上下文运行前，我们将JNIEnv和JavaVM替换为unidbg的，那么后续遇到jni调用或是syscall调用也能处理，而unicorn不具备这样的优势在load_context通过下面的代码恢复上下文的寄存器，这里只处理了通用寄存器，浮点寄存器暂时没有写这里一定要向TPIDR_EL0写入刚才dump得到的那个寄存器的值Q: 为什么要向CPACR_EL1写入0x300000？A: 没有仔细研究...但是unidbg的enableVFP是这样的写的:(backend.reg_write(Arm64Const.UC_ARM64_REG_CPACR_EL1, 0x300000L); backend.reg_write(Arm64Const.UC_ARM64_REG_TPIDR_EL0, 0x0000006d4aa68000L);Q: x29和x30呢，为什么没有写？A: FP就是x29，LR就是x30简略代码如下：Backend backend = emulator.getBackend(); Memory memory = emulator.getMemory(); String context_file = dump_dir + "\\" + "_index.json"; InputStream is = new FileInputStream(context_file); String…

dump内存与模拟执行（三）——实践dump上下文
type: item
date: 2022-08-06T10:59:00-04:00
description: 前言假定你已经完成下面的操作，那么现在要实际开始操作了termux + lldb的环境准备好了写好了dump上下文的lldb脚本2023的补充最新版的termux中lldb已经是16版本了，根据反馈可能出现image list只有几个库的情况，请尝试改为使用remote方式步骤首先在PC上进入termux的环境中（root用户）记得要进入/data/data/com.termux/files/home执行命令，并且dump脚本也放到这个目录，以确保能正常读写文件以绿洲 v4.5.6为例（64位），目标是com.weibo.xvideo.NativeApi的s函数通过frida hook RegisterNative，可以得到其native函数位于liboasiscore.so + 0x116CC先查看其主进程pidps -ef | grep oasis然后执行lldb进入lldb交互界面再执行attach {pid}附加到主进程这个时候APP会被暂停，此时再按c即可恢复运行恢复运行后我们依然可以执行命令，这点相比gdb要好很多，gdb附加真的很慢而且只能在暂停的时候执行命令（？）有时候会遇到这样的信号，这个时候APP就突然暂停了通过下面的命令查看信号处理设置process handle SIGSEGV输出如下NAME PASS STOP NOTIFY =========== ===== ===== ====== SIGSEGV true true true那么可以在刚附加的时候就把这个信号PASS动作设置为true，STOP动作设置为false，这样可以避免APP突然暂停process handle SIGSEGV -p true -s false如果其他APP存在反调试，可能会发SIGQUIT SIGSTOP SIGKILL之类的信号暂停的时候可以打调用栈看看，说不定能找到反调试信息可以用下面的命令查看目标so的基址信息image list -o -f liboasiscore.so这里只是演示下，lldb可以直接通过下面的命令实现对模块名+偏移的断点设置，相比gdb的操作方便得多breakpoint set -s liboasiscore.so -a 0x116CC现在断点下好了，将APP切换到后台，再进入APP就能触发s函数的调用如果前面没有载入脚本，那么先通过下面的命令载入command script import lldb_dumper.py然后执行dumpctx命令即可开始dump上下文提前做了过滤，所以不会太花时间最后脚本会将dump好的内容打包如果是用的ttyd+浏览器远程，那现在可以使用lsz命令直接下载文件，如果是adb方式，记得手动pull出来现在就完成了dump上下文了这里还没有结束查看liboasiscore.so + 0x116CC的反汇编代码，可以看到这样的代码v35 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);对应的汇编是MRS X8, #3, c13, c0, #2也就是在获取TPIDR_EL0这个系统寄存器，有关说明可以参考下面的文章Android逆向中的Canary机制还有哪些系统寄存器？请参考https://github.com/TrungNguyen1909/aarch64-sysreg-ida/blob/main/aarch64_sysreg.py不过我们只需要获取到TPIDR_EL0就足以应付后续的模拟执行了为了能稳妥的模拟执行，必须拿到这个寄存器的值，经过实践，发现无法直接在最开始断点的时候就拿到只能在执行完MRS这条指令之后去获取x8的值所以还需要在MRS的下一行断点，也就是liboasiscore.so + 0x116F4breakpoint set -s liboasiscore.so -a 0x116F4下完断点后按c执行，等断在0x116F4，然后通过register read x8读取TPIDR_EL0的值，记录下来注意，不一定都是把值赋给x8，请结合实际情况确定是哪个寄存器现在才算是完成上下文dump了如果要结束附加进程，建议使用下面的命令（最好取消断点，再按c让APP恢复运行）process detach直接quit也能退，但是会导致APP崩溃或者无响应，不建议

dump内存与模拟执行（二）——编写dump脚本
type: item
date: 2022-08-06T10:34:00-04:00
description: 前言提到dump，（做安卓逆向）大家可能会首先想到是dump内存中的so，然后修复，接着再去逆向或者模拟执行dump后模拟执行，按道理说比自己做so解析并完整模拟会更有优势，毕竟省去了初始化so的工作但我也只见到过几篇文章有这样做，都是使用frida去dump，然后修复so后再模拟执行我想问题在于这样的dump操作没有拿到准确的上下文，构造参数仍然是一件很麻烦的事情不过好在已经有人做过这件事了，只是大家可能没有注意到，亦或是脚本年久失修...afl-unicorn: Fuzzing The 'Unfuzzable'注意，执行测试脚本的时候需要先附加到进程脚本编写主要参考下面的脚本，这个脚本已经是两年前的了，存在诸多问题https://github.com/AFLplusplus/AFLplusplus/blob/stable/unicorn_mode/helper_scripts/unicorn_dumper_lldb.pyQ: 为什么选择lldb而不是gdbA: 经过实践lldb更快，更方便，更现代化起初我是想进行简单修补上面的脚本，后面发现还是得重写下下面就详细讲解如何编写可以dump上下文的lldb脚本编写过程中用到的API可以在下面的文档中查询https://lldb.llvm.org/use/python-reference.htmllldb的python库源码在线版本，可以点击下面网页的source code查看https://lldb.llvm.org/python_reference/如果你已经在termux安装好lldb了，那么你也可以在下面的路径找到lldb库源代码/data/data/com.termux/files/usr/lib/python3.10/site-packages/lldb/__init__.py建议把这个脚本拿出来，改名为lldb.py然后新建一个lldb_dumper.py文件，把上面的lldb.py放在同一级目录在lldb_dumper.py中写上如下代码，这样使用vscode或者其他IDE即可获得代码提示from typing import TYPE_CHECKING if TYPE_CHECKING: from lldb import * import lldb我希望命中断点后，进程都停下来，这个时候只需要执行一个命令就可以dump上下文根据Create a new lldb command using a Python function小节https://lldb.llvm.org/use/python-reference.html#create-a-new-lldb-command-using-a-python-function可以知道应该在代码中添加一个__lldb_init_module函数，然后通过debugger.HandleCommand去添加自定义命令def __lldb_init_module(debugger: 'SBDebugger', internal_dict: dict): debugger.HandleCommand('command script add -f lldb_dumper.helloworld hello')command script add -f lldb_dumper.helloworld hello这个命令的意思是，当执行hello命令的时候，lldb会去调用lldb_dumper脚本中的helloworld函数即lldb_dumper.py被视为一个模块根据文档，可以知道被调用的函数接收下面这些参数，详细解释请参考文档debuggercommandexe_ctxresultinternal_dict对于dump上下文来说，最重要的就是exe_ctx了，其类型为SBExecutionContext，看到这个就知道它肯定和上下文紧密相关写个helloworld如下def helloworld(debugger: 'SBDebugger', command: str, exe_ctx: 'SBExecutionContext', result: 'SBCommandReturnObject', internal_dict: dict): print('helloWorld debugger:', debugger) print('helloWorld command:', command) print('helloWorld exe_ctx:', exe_ctx)进入lldb，通过下面的命令加载脚本command script import lldb_dumper.py这个时候执行hello you命令可以看到有输出信息，其中co…