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

时间:2017-05-22 16:08:06

标签: 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;
}

上述功能只需跳转到:

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读取参数。但是:如果我用mallocprintf("any string");替换基本的int操作,那么,不知何故,如果跳转到我的原始函数,那么我的参数搞砸了,原始函数结束读取错误的值...

我试图调试这种行为,我检查了内存区域,看看有什么问题...在跳转之前,一切看起来很好,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
}

现在为printf变体输出asm ...

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 **************

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

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

Memory Layout when entering the interceptor

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

enter image description here

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

enter image description here

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

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

3 个答案:

答案 0 :(得分:1)

使用set调用约定在程序集中手动传递参数。在这种情况下,参数将以%rcx 开头的寄存器传递。对用作调用约定的寄存器的任何修改都将改变任何进程 jmp 所感知的参数。

jmp %rcx 的值从 * env 更改为指向常量的指针之前调用 printf "你好" 即可。更改%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;

}

答案 1 :(得分:0)

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

要更好地了解发生了什么,您可能需要查看this question

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

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);
}

恕我直言,重要的是你需要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; 
}

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

答案 2 :(得分:0)

这是什么架构?从寄存器名称,它似乎是x64。

你说参数是错误的。我同意。你从那里跳到相信堆栈是错误的。可能不是。 x64在寄存器中传递一些参数,但不传递varargs。因此,转发器的功能签名与您尝试调用的功能完全不兼容。

发布程序集以直接调用Java_main_Main_theOriginalFunction,然后使用完全相同的参数调用转发器;你会看到参数传递的可怕差异。