在我的 C++ JNI-Agent 项目中,我正在实现一个函数,该函数将被赋予可变数量的参数并将执行传递给另一个函数:
// address of theOriginalFunction
public static void* originalfunc;
void* interceptor(JNIEnv *env, jclass clazz, ...){
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
上面的函数只需要跳转到:
JNIEXPORT void JNICALL Java_main_Main_theOriginalFunction(JNIEnv *env, jclass clazz, jboolean p1, jbyte p2, jshort p3, jint p4, jlong p5, jfloat p6, jdouble p7, jintArray p8, jbyteArray p9){
// Do something
}
上面的代码完美运行,原函数可以正确读取所有参数(测试了包括数组在内的9个不同类型的参数)。
但是,在从拦截器跳转到原始函数之前,我需要进行一些计算。但是,在这里我观察到有趣的行为。
void* interceptor(JNIEnv *env, jclass clazz, ...){
int x = 10;
int y = 20;
int summ = x + y;
// NEED TO RESTORE ESP TO EBP SO THAT ORIGINAL FUNCTION READS PARAMETERS CORRECTLY
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
这仍然可以正常工作,我能够进行一些基本的计算,然后重置堆栈指针并跳转到我的原始函数,原始函数也正确地从 var_args 读取参数。但是:如果我用 malloc
或 printf("any string");
替换基本的 int 操作,那么,不知何故,如果跳转到我的原始函数,那么我的参数得到搞砸了,原始函数结束读取错误的值...
我已尝试调试此行为,并检查了内存区域以查看发生了什么问题...在跳转之前,一切看起来都很好,ebp 后面是函数参数。
如果我 在没有复杂计算的情况下跳转,一切正常,ebp 后面的内存区域不会改变。原始函数读取正确的值...
现在如果我在执行 printf 后跳转(例如),原始方法读取的参数会损坏...
是什么导致了这种奇怪的行为? printf 甚至没有在我的方法中存储任何 lokal 变量...好吧,它确实在寄存器中存储了一些文字,但是为什么我的堆栈仅在跳转之后才损坏而不是在它之前?
对于这个项目,我使用在 Windows 机器上运行的 g++ 版本 4.9.1 编译器。
是的,我担心 std::forward 和模板选项,但它们在我的情况下不起作用...... Aaand 是的,我知道跳入其他方法有点笨拙,但这是我唯一的想法如何带来JNI 拦截器工作...
********************编辑********************
如前所述,我将生成的汇编代码与源函数一起添加。
没有 printf 的函数(工作正常):
void* interceptor(JNIEnv *env, jclass clazz, ...){
//just an example
int x=8;
// restoring stack pointers
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
void* interceptor(JNIEnv *env, jclass clazz, ...){
// first when interceptor is called, probably some parameter restoring...
push %rbp
mov %rsp %rbp
sub $0x30, %rsp
mov %rcx, 0x10(%rbp)
mov %r8, 0x20(%rbp)
mov %r9, 0x28(%rbp)
mov %rdx, 0x18(%rbp)
// int x = 8;
movl $0x8, -0x4(%rbp)
// my inline asm restoring stack pointers
mov %ebp, %esp
mov %rbp, %rsp
// asm volatile("jmp *%0;"::"r" (originalfunc+4))
mov 0xa698b(%rip),%rax // store originalfunc in rax
add %0x4, %rax
jmpq *%rax
// return NULL;
mov $0x0, %eax
}
现在 asm 输出 printf 变体...
void* interceptor(JNIEnv *env, jclass clazz, ...){
//just an example
int x=8;
printf("hey");
// restoring stack pointers
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
void* interceptor(JNIEnv *env, jclass clazz, ...){
// first when interceptor is called, probably some parameter restoring...
push %rbp
mov %rsp %rbp
sub $0x30, %rsp
mov %rcx, 0x10(%rbp)
mov %r8, 0x20(%rbp)
mov %r9, 0x28(%rbp)
mov %rdx, 0x18(%rbp)
// int x = 8;
movl $0x8, -0x4(%rbp)
// printf("hey");
lea 0x86970(%rip), %rcx // stores "hey" in rcx???
callq 0x6b701450 // calls the print function, i guess
// my inline asm restoring stack pointers
mov %ebp, %esp
mov %rbp, %rsp
// asm volatile("jmp *%0;"::"r" (originalfunc+4))
mov 0xa698b(%rip),%rax // store originalfunc in rax
add %0x4, %rax
jmpq *%rax
// return NULL;
mov $0x0, %eax
}
这里是 printf 函数的 asm 代码:
printf(char const*, ...)
push %rbp
push %rbx
sub $0x38, %rsp
lea 0x80(%rsp), %rbp
mov %rdx, -0x28(%rbp)
mov $r8, -0x20(%rbp)
mov $r9, -0x18(%rbp)
mov $rcx, -0x30(%rbp)
lea -0x28(%rbp), %rax
mov %rax, -0x58(%rbp)
mov -0x58(%rbp), %rax
mov %rax, %rdx
mov -0x30(%rbp), %rcx
callq 0x6b70ff60 // (__mingw_vprintf)
mov %eax, %ebx
mov %ebx, %eax
add $0x38, %rsp
pop %rbx
pop %rbp
retq
看起来 printf 对 rbp 做了很多操作,但我看不出有什么问题...
这里是截获函数的asm代码。
push %rbp // 1 byte
push %rsp, %rbp // 3 bytes , need to skip them
sub $0x50, %rsp
mov %rcx, 0x10(%rbp)
mov %rdx, 0x18(%rbp)
mov %r8d, %ecx
mov %r9d, %edx
mov 0x30(%rbp), %eax
mov %cl, 0x20(%rbp)
mov %dl, 0x28(%rbp)
mov %ax, -0x24(%rbp)
****************** 编辑 2 **************
我认为在运行时查看内存如何变化会很有用:
第一张图是进入拦截器函数后的内存布局:
第二张图片在有问题的代码(如 printf 等)之后显示了相同的内存区域
第三张图是跳转到原函数后的内存布局。
如您所见,调用 printf 后,堆栈看起来很好,但是当我跳转到原始函数时,它搞砸了......
看截图,我很确定所有参数都在内存中的堆栈上,并且参数不是通过寄存器传递的。
最佳答案
使用集合调用约定在汇编中手动传递参数。在这种情况下,参数在以 %rcx 开头的寄存器中传递。对用作调用约定的寄存器的任何修改都会改变任何进行的 jmp 所感知的参数。
在 jmp 之前调用 printf 会将 %rcx 的值从 *env 更改为指向常量的指针“你好”。更改 %rcx 的值后,您需要将其恢复为之前的值。以下代码应该可以工作:
void* interceptor(JNIEnv *env, jclass clazz, ...){
//just an example
int x=8;
printf("hey");
// restoring stack pointers
asm (
"movl %ebp, %esp;"
"mov %rbp, %rsp"
);
// restore %rcx to equal *env
asm volatile("mov %rcx, 0x10(%rbp)");
// add 4 to the function address to skip "push ebp / mov ebp esp"
asm volatile("jmp *%0;"::"r" (originalfunc+4));
// will not get here anyway
return NULL;
}
关于C++跳转到其他方法执行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44117484/