In my C++ JNI-Agent project i am implementing a function which would be given a variable number of parameters and would pass the execution to the other function:
// 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;
}
The function above needs to just jump to the:
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
}
The code above works perfectly, the original function can read all the parameters correctly (tested with 9 parameters of different types including arrays).
However, before jumping into original function from the interceptor i need to do some computations. However, here i observe interesting behavior.
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;
}
This still works fine, i am able to do some basic computations , then reset the stack pointer and jump to my original function, the original function also reads the parameters from the var_args correctly. However: if i replace the basic int operations with malloc
or printf("any string");
, then, somehow, if jump into my original function, then my parameters get messed up and the original function ends reading wrong values...
I have tried to debug this behavior and i inspected the memory regions to see what is goin wrong... Right before the jump, everything looks fine there, ebp is being followed by function parameters.
If i jump without complicated computations, everything works fine, memory region behind ebp doesnt get changed. original function reads correct values...
Now if i jump after doing printf (for example), the parameters read by the original method get corrupted...
What is causing this strange behavior? printf doesnt even store any lokal variables in my method... Ok it does store some literals in registers but why my stack gets corrupted only after the jump and not already before it?
For this project I use g++ version 4.9.1 compiler running on a windows machine.
And yes I am concerned of std::forward and templates options but they just do not work in my case... Aaand yes I know that jumping into other methods is a bit hacky but thats my only idea of how to bring JNI-interceptor to work...
******************** EDIT ********************
As discussed i am adding the generated assembler code with the source functions.
Function without printf (which works fine):
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
}
Now asm output for printf variant...
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
}
And here is the asm code for the printf function:
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
It looks like printf does many operations on rbp , but i cannot see anything wrong with it...
And here is the asm code of the intercepted function.
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)
************* EDIT 2 **************
I thought it would be useful to see how memory changes at the run-time:
The first picture shows the memory layout right after entering the interceptor function:
The second images shows the same memory region after problematic code (like printf and so)
The third picture shows the memory layout right after jumping to original function.
As you can see, right after calling printf , stack looks fine, however when i jump into the original function, it messes up...
Looking at the screenshots, I am pretty sure that all the parameters lie on the stack in the memory, and parameter are not passed by registers.
push ebp / mov ebp esp
is 3 bytes long, not 4. – Serrellprintf
? And I'm not sure why you want to skip thepush ebp; mov ebp esp
of the called method instead of reversing the stack modifications made by yourinterceptor
method, before jumping. – SergiusNativeMethodBind(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, void* address, void** new_address_ptr)
method gets called , whereaddress
is the pointer to native method. This value i store in a map (with jmethodID as key), to restore it later, when my interceptor is called... To rebind the method to my interceptor , i just need to setnew_address
to the address of my interceptor... Thus my interceptor is able to restore the initial function pointer from a map. – Aurthur