JNI — C ++ перейти к выполнению другого метода

В моем проекте JNI-агента на C ++ я реализую функцию, которой будет присвоено переменное число параметров и которая передаст выполнение другой функции:

// 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. Однако: если я заменю основные операции int на malloc или же printf("any string"); тогда, если каким-то образом, если перейти в мою исходную функцию, то мои параметры будут испорчены, и исходная функция завершит чтение неправильных значений …

Я попытался отладить это поведение, и я проверил области памяти, чтобы увидеть, что происходит неправильно … Прямо перед прыжком, там все выглядит хорошо, за ebp следуют параметры функции.

Если я прыгать без сложных вычислений, все работает нормально, область памяти за ebp не меняется. оригинальная функция читает правильные значения

Теперь, если я например, после выполнения printf (параметры), считанные исходным методом, будут повреждены

Что вызывает это странное поведение? printf даже не хранит никаких локальных переменных в моем методе … Хорошо, он хранит некоторые литералы в регистрах, но почему мой стек портится только после прыжка а не уже до этого?

Для этого проекта я использую компилятор g ++ версии 4.9.1, работающий на машине с Windows.

И да, я обеспокоен опциями std :: forward и templates, но они просто не работают в моем случае … Да, и да, я знаю, что переходить в другие методы немного нелепо, но это моя единственная идея, как вывести 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
}

А вот код asm для функции printf:

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 стек выглядит нормально, однако, когда я перехожу к исходной функции, она портится …

Глядя на скриншоты, я почти уверен, что все параметры лежат в стеке в памяти, а параметры не передаются регистрами.

14

Решение

Аргументы передаются вручную в сборке с использованием установленного соглашения о вызовах. В этом случае аргументы передаются в регистрах, начинающихся с % RCX. Любая модификация регистров, используемых в качестве соглашений о вызовах, изменит аргументы, воспринимаемые любой процедурой. JMP.

призвание Printf до вашего JMP меняет значение % RCX от * окр на указатель на константу «Привет». После того, как вы измените значение % 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

Другие решения

Что это за архитектура? Из имен регистров, кажется, x64.

Вы говорите, что параметры неверны. Согласен. Вы прыгаете оттуда, полагая, что стек неправильный. Возможно нет. x64 передает некоторые параметры в регистрах, но не varargs. Таким образом, подпись функции для вашего сервера пересылки просто несовместима с функцией, которую вы пытаетесь вызвать.

Разместите сборку для прямого звонка Java_main_Main_theOriginalFunction а затем для вызова вашего экспедитора с использованием точно таких же параметров; вы увидите ужасную разницу в том, как аргументы передаются.

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

ИМХО, важно то, что вам нужна настройка 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 не может проверить, имеет ли параметр правильный тип или доступен вообще.

0
По вопросам рекламы [email protected]