C++ jump to other method execution
Asked Answered
A

3

14

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:

Memory Layout when entering the interceptor

The second images shows the same memory region after problematic code (like printf and so)

enter image description here

The third picture shows the memory layout right after jumping to original function.

enter image description here

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.

Aurthur answered 22/5, 2017 at 16:8 Comment(17)
Just a little comment: imho, push ebp / mov ebp esp is 3 bytes long, not 4.Serrell
oh , that true, thank you manuellAurthur
I made a little C stand-alone program with your code but without JNI. Works like a charm, with printf and malloc before the jump. But it's Windows, Visual Studio 2010, 32 bits. You should double-check your code, and add more details to your question, with more infos about assembly generated/seen when debugging.Serrell
Check Your compiler documentation on ABI. Typically some arguments are passed via registers and printf (or whatever function) may not restore them.Lake
Could you post the assembler generated by gcc with and without the printf? And I'm not sure why you want to skip the push ebp; mov ebp esp of the called method instead of reversing the stack modifications made by your interceptor method, before jumping.Sergius
Thank you guys for you replies, i have added the assembler code to the initial post... @Sergius i want to skip push/mov part , so that the var_args parameters are read by my intercepted function correctly... You mentioned reversing the stack modfications before jumping. How would I do that? Would it help in this case?Aurthur
is there any particular reason not to use stdarg.h?Groenendael
@AlexCohn The reason is that i cannot change the signature of the intercepted method, so i cannot pass va_lists to them... What I have tried to do is creating generic templates with functions which take variadic parameters but that just did not work out.... the jump into another function seems like a much smoother, yet dirty and yet a solution to me...Aurthur
No, I did not suggest to pass the list to original function. You shoul pass the list to a proxy function which knows exactly how to extract relevant parameters from this list and send them "by name" to the original. Or I am wrong and your interceptor does not know the signature of the intercepted function, and thus cannot read the parameters?Groenendael
@AlexCohn the interceptor can get the signature of the intercepted method, however if I extract parameters and call the other function with parameters by names, then the interceptor is not generic anymore... I was already trying it in this discussion.Aurthur
@AksimElnik "can get the signature" - How? would it be a template function? Side note: I added another answer to the referred discussion, maybe it helps you...Derril
@Derril JNI allows me to read thread info, which contains the information about a called native function (signature , jmethodID etc... ) ... But having signature doesnt help me at all since i need to have a generic interceptor intercepting over 50 different native functions with different signaturesAurthur
@AksimElnik And how do you get your function pointer from that information? Instead of getting a void* pointer, you could get a polymorphic class providing e. g. an operator()(va_list&) which would resolve the arguments... Sure, now I'm getting rather into the domain of the referenced discussion, so possibly we might go on there?Derril
@Derril When JVM binds native methods to java methods, an NativeMethodBind(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, void* address, void** new_address_ptr) method gets called , where address 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 set new_address to the address of my interceptor... Thus my interceptor is able to restore the initial function pointer from a map.Aurthur
@Derril So this way I am able to rebind java methods to my interceptor , which , at runtime , would retrieve initial function locations from the map, and would pass the execution there...Aurthur
@Derril I had no luck with templates , so i tried to do it with just jumping to that address and countinuing the execution , however I run into the stack corruption problem....Aurthur
@AksimElnik A map? No luck with templates? OK, then I have something for you on the other discussion...Derril
U
1

Arguments are passed manually in assembly using a set calling convention. In this case, the arguments are passed in registers beginning with %rcx. Any modification to the registers used as calling conventions will change the arguments perceived by any proceeding jmp.

Calling printf before your jmp changes the value of %rcx from *env to a pointer to constant "hello". After you change the value of %rcx you need to restore it to the value it was previously. The following code should work:

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;

}

Unexpressive answered 14/6, 2017 at 0:33 Comment(2)
Hey, you were indeed right, i needed to restore some registers...However your code instead of restoring rcx, puts rcx into 0x10(%rbp) :D But its okay, you gave me a nice idea which led to the solutionAurthur
I appreciate the feedback. The code I wrote follows the rule seen in the assembly you posted in that the value/register on the left is the target for the given instruction. So the code actually puts %rbp + 10 into the %rcx register but assembly is freakin confusing so blehhhUnexpressive
D
0

Most likely any function you call before your forwarding destroys the structure that is needed to handle the variable argument list (in your assembly there is still the mingw_printf call of which you didn't show the disassembly).

To understand better what's going on you might want to have a look at this question.

To solve your problem you could consider to add another indirection, I think that the following might work (but I haven't tested it).

void *forward_interceptor(env, 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;
}

void* interceptor(JNIEnv *env, jclass clazz, ...){
    //do your preparations 
    ...

    va_list args;
    va_start(args, clazz);
    forward_interceptor(env, clazz, args);
    va_end(args);
}

IMHO the important thing is that you need the va_list/va_start/va_end setup to make sure that the parameters are properly passed on to the next function.

However, since you seem to know the signature of the function you are forwarding to and it doesn't seem to accept a variable number of arguments, why not extract the arguments, and call the function properly like:

void* interceptor(JNIEnv *env, jclass clazz, ...){
    //do your preparations 
    ...

    va_list args;
    va_start(args, clazz);

    jboolean p1 = va_arg(args, jboolean); 
    jbyte p2 =  va_arg(args, jbyte); 
    jshort p3 = va_arg(args, jshort); 
    ...
    Java_main_Main_theOriginalFunction(env, clazz, p1, p2, ...
    va_end(args);

    return NULL; 
}

Note, however, that va_arg can not check whether the parameter is of the correct type or available at all.

Denial answered 1/6, 2017 at 11:33 Comment(3)
Passing a va_list does not push all the variable number of arguments again!Xenogenesis
@BenVoigt care to elaborate what you mean by that?Denial
@GertWollny forward_interceptor(env, clazz, args); You pass the va_list to your forward_interceptor function - but that is not equivalent to passing all the arguments of interceptor to forward_interceptor. Instead, after applying va_start in forward_interceptor, the only argument you could get there would be the (outer) va_list again (so you'd have two such lists within forward_interceptor, the one passed from interceptor and the own one needed to retrieve the former...).Derril
X
0

What architecture is this? From the register names, it appears to be x64.

You say the parameters are wrong. I agree. You jump from there to believing the stack is wrong. Probably not. x64 passes some parameters in registers, but not varargs. So the function signature for your forwarder is simply incompatible with the function you are trying to call.

Post the assembly for a direct call to Java_main_Main_theOriginalFunction and then for a call to your forwarder using the exact same parameters; you'll see a terrible difference in how the arguments are passed.

Xenogenesis answered 1/6, 2017 at 11:57 Comment(4)
I am pretty sure that parameters are passed on the stack... I ve added some pictures to my initial post, showing that. However printf does modify some registers which influence the workflow of my original function.... that part is not clear to me though. Is it possible to copy block of memory with the parameters , put it on stack, and call my original function ?Aurthur
You see the arguments on the stack because you have a call to interceptor, a varargs function that uses the varargs calling convention. However, the original function is not varargs, and would most likely expect the fastcall convention which passes arguments in registers. Maybe not, it depends on exactly what is hidden in those JNIEXPORT and JNICALL macros. If there is a calling convention specified in these, you really need to apply it on interceptor as well.Xenogenesis
@AksimElnik: It definitely isn't helping that you're forcing me to guess at your architecture.Xenogenesis
@AksimElnik: If is is x86_64, you need to read this: eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64 Your assumption that all parameters are on the stack is wrong, and you are failing to account for the RED ZONE.Xenogenesis

© 2022 - 2024 — McMap. All rights reserved.