C++跳转到其他方法执行

Aks*_*nik 14 c++ java-native-interface stack javaagents

在我的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;
}
Run Code Online (Sandbox Code Playgroud)

上面的函数只需要跳转到:

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
}
Run Code Online (Sandbox Code Playgroud)

上面的代码工作正常,原始函数可以正确读取所有参数(使用包括数组在内的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;
}
Run Code Online (Sandbox Code Playgroud)

这仍然可以正常工作,我能够做一些基本的计算,然后重置堆栈指针并跳转到我原来的函数,原始函数也正确地从var_args读取参数.但是:如果我用malloc或替换基本的int操作printf("any string");,那么,不知何故,如果跳转到我的原始函数,那么我的参数搞砸了,原始函数结束读取错误的值...

我试图调试这个行为,我检查了内存区域,看看有什么问题...在跳转之前,一切看起来很好,ebp后面跟着函数参数.

如果我跳过没有复杂的计算,一切正常,ebp后面的内存区域不会改变.原始函数读取正确的值 ...

现在如果我在执行printf之后跳转(例如),原始方法读取的参数会被破坏 ...

是什么导致了这种奇怪的行为?printf甚至不会在我的方法中存储任何lokal变量...好吧它确实在寄存器中存储了一些文字,但为什么我的堆栈只在跳转之后才被破坏而不是在它之前?

对于这个项目,我使用在Windows机器上运行的g ++版本4.9.1编译器.

是的,我担心std :: forward和模板选项,但它们只是在我的情况下不起作用... Aaand是的我知道跳进其他方法有点hacky但这就是我唯一的想法如何带来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
}
Run Code Online (Sandbox Code Playgroud)

现在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
}
Run Code Online (Sandbox Code Playgroud)

这是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
Run Code Online (Sandbox Code Playgroud)

看起来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)
Run Code Online (Sandbox Code Playgroud)

*************编辑2**************

我认为在运行时查看内存如何变化会很有用:

第一张图片显示了进入拦截器功能后的内存布局:

进入拦截器时的内存布局

第二个图像显示有问题的代码后的相同内存区域(如printf等)

在此输入图像描述

第三张图显示了跳转到原始功能后的内存布局.

在此输入图像描述

正如你所看到的,在调用printf之后,堆栈看起来很好,但是当我跳进原始函数时,它会混乱......

查看屏幕截图,我很确定所有参数都位于内存中的堆栈中,参数不会通过寄存器传递.

Ger*_*lny 0

最有可能的是,您在转发之前调用的任何函数都会破坏处理变量参数列表所需的结构(在您的程序集中仍然存在您没有显示反汇编的 mingw_printf 调用)。

为了更好地了解发生了什么,您可能想看看这个问题

为了解决您的问题,您可以考虑添加另一个间接,我认为以下方法可能有效(但我还没有测试过)。

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);
}
Run Code Online (Sandbox Code Playgroud)

恕我直言,重要的是您需要 va_list/va_start/va_end 设置来确保参数正确传递到下一个函数。

但是,由于您似乎知道要转发到的函数的签名,并且它似乎不接受可变数量的参数,因此为什么不提取参数并正确调用该函数,如下所示:

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; 
}
Run Code Online (Sandbox Code Playgroud)

但请注意,这va_arg无法检查参数的类型是否正确或根本可用。