首先看下 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
创建一个子进程来执行一条指令 并获取返回结果- 如果要用
unidbg
补fork
那就太麻烦了其实是我不会
- 那就可以通过
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.so
中unidbg
本身就已经加载了 不需要我们处理
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
继续执行
即让代码直接返回 最终流程如下
- 首先
popen
要fork
一个子进程执行一条指令,但被hook
直接跳过函数体执行 - 我们手动调用
libc.so
中的fopen
打开一个文件unidbg
中io
重定向拦截到了这个打开文件请求 直接返回了一个文件指针 - 将这个文件指针改到了
popen
的X0
寄存器中