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

В моем проекте С++ 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;
}

Это все еще отлично работает, я могу выполнить некоторые базовые вычисления, затем reset указатель стека и перейти к моей исходной функции, исходная функция также правильно считывает параметры из var_args. Однако: если я заменяю базовые операции int на malloc или printf("any string");, то каким-то образом, если перейти в мою исходную функцию, тогда мои параметры перепутаются, а исходная функция закончит чтение неправильных значений...

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

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

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

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

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

И да, я обеспокоен параметрами std:: forward и templates, но они просто не работают в моем случае... Aaand yes Я знаю, что переключение на другие методы немного хаки, но это единственная моя идея о том, как принести JNI-перехватчик для работы...

******************** EDIT ********************

Как обсуждалось, я добавляю сгенерированный код ассемблера с исходными функциями.

Функция без 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, стек выглядит отлично, однако, когда я перехожу в исходную функцию, это беспорядочно...

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

Ответы

Ответ 1

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

Вызов printf до того, как ваш jmp изменит значение % rcx от * env до указателя на константу "привет" . После изменения значения % 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;

}

Ответ 2

Скорее всего, любая функция, которую вы вызываете перед отправкой, разрушает структуру, необходимую для обработки списка аргументов переменных (в вашей сборке все еще есть вызов 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 не может проверить, имеет ли параметр правильный тип или вообще доступен.

Ответ 3

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

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

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