嘘~ 正在从服务器偷取页面 . . .

Unidbg中popen的读处理


首先看下 popen 在so中的代码

typedef const char *string;
extern "C" JNIEXPORT jstring JNICALL
Java_com_ass_app_MainActivity_CallStaticJni(JNIEnv *env, jobject, jstring command) {
    string cmd = env->GetStringUTFChars(command, nullptr); // 通过java层传递的参数执行指令 这里是 uname
    char buffer[128];
    FILE *pipe = popen(cmd, "r"); // 调用popen执行指令
    if (!pipe) {
        return nullptr;
    }

    fgets(buffer, sizeof(buffer), pipe); // 获取指令执行的返回结果

    pclose(pipe);

    return env->NewStringUTF(buffer); // 返回值为 Linux
}
  • popen通过fork创建一个子进程来执行一条指令 并获取返回结果
  • 如果要用unidbgfork那就太麻烦了 其实是我不会
  • 那就可以通过hook popen来返回一个他要的对象 文件指针

hook popen

emulator.attach().addBreakPoint(CryptoTool.findSymbolByName("popen").getAddress(), new BreakPointCallback() {

    @Override
    public boolean onHit(Emulator<?> emulator, long address) {

        RegisterContext registerContext = emulator.getContext(); // 获取上下文 用于获取popen的参数
        String cmd = registerContext.getPointerArg(0).getString(0); // arg1 uname

        switch (cmd) {
            case "uname":
                emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0, File_Ptr??? );
                return true;
        }

        return false;
    }
});
  • 上面代码中 缺少的是一个File_Ptr文件指针 可以通过调用 fopen 来获取文件指针 其中 fopen是存在于libc.sounidbg本身就已经加载了 不需要我们处理
final UnidbgPointer uname_ptr = UnidbgPointer.pointer(
    emulator,
    CryptoTool.findSymbolByName("fopen").call(emulator, "uname", "r")
);
// 这个方法不能写在 onHit n
  • 调用fopen并且传入参数
    • 这里的参数 其实是用于区分在重定向中的返回值 因为我们并不会真的去打开那个文件
  • unidbg中 操作了文件那就要进行io重定向 由我们接管
@Override
public FileResult<?> resolve(Emulator emulator, String pathname, int oflags) {
	// 当打开的路径是uname 返回一个File对象
    if ("uname".equals(pathname)) {
        return FileResult.success(new ByteArrayFileIO(oflags, pathname, "Linux".getBytes(StandardCharsets.UTF_8)));
    }
    return null;
}
  • 到这里上面的代码就可以走通了 其中 uname_ptr 就是popen需要的一个文件指针 其中的信息 由我们控制 返回的是 Linux
  • 最后将文件指针修改到X0 寄存器中 再跳过函数体的执行
  • 我们已经拿到了函数的返回值 所以不需要再执行原函数的逻辑
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_PC, emulator.getContext().getLR());
  • 通过将PC寄存器 修改为 LR 即可

    • PC寄存器 总是指向“正在取指”的指令

    • 而在arm中指令的执行分为 取指 译码 执行PC寄存器指向的就是第一个 取指

      • 取指从存储器装载一条指令
      • 译码识别将要被执行的指令
      • 执行处理指令并将结果写回寄存器
      • 所以处理时实际是这样的:ARM正在执行第1条指令的同时对第2条指令进行译码,并将第3条指令从存储器中取出。所以,ARM7流水线只有在取第4条指令时,第1条指令才算完成执行。
    • 但是人们约定俗成的认为 PC寄存器 指向的是正在执行的指令 所以 PC的值 = 正在执行的指令+ 8

      • 8 是因为 ARM中一条指令长度为4
  • 修改为 LR寄存器 LR寄存器是用来保存程序返回地址

    • LR 可以理解为 A 调用 B时存储返回地址 用于在B函数执行完毕的时候 跳回A继续执行
  • 即让代码直接返回 最终流程如下


  • 首先popenfork一个子进程执行一条指令,但被hook直接跳过函数体执行
  • 我们手动调用libc.so中的fopen打开一个文件 unidbgio重定向拦截到了这个打开文件请求 直接返回了一个文件指针
  • 将这个文件指针改到了popenX0寄存器中

文章作者: 林木木
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 林木木 !
评论