从内联汇编修改RIP寄存器

5

我正在尝试修改rip寄存器(只是为了好玩)。buffer应该是一个内存地址,所以我不知道为什么会得到Error: operand type mismatch for 'movq'

#include <stdio.h>
#include <stdlib.h> 

int main(){
        char* buffer;
        buffer = (char*) malloc(8*sizeof(char));
        asm volatile("movq %0, %%rip \n" : : "r" (buffer));
        free(buffer);
}

可能重复:http://stackoverflow.com/questions/23468799/operand-size-mismatch-for-movq.. 你也可以看看这个:https://dev59.com/4oHba4cB1Zd3GeqPUatA - Paulo
1
@Paulo:这些不是重复的。它们是尝试完成完全不同的事情,恰好给出了相同的错误信息。 - Peter Cordes
1个回答

12

在x86中,你不能直接使用rip作为mov的源或目的地。如果要更改rip,必须使用jmpcallret

尝试将地址移动到rax,然后使用jmp rax。确保将rax标记为“clobbered”。

不过,你所尝试的方法更容易的方式是简单地创建一个函数指针,将其指向你分配的内存,并调用它。

请注意,你使用malloc分配的内存不会被标记为可执行,因此测试将立即崩溃。为了解决这个问题,你可以使用mprotect(2)更改页面权限。但是这样会更改整个页面的权限。更好的解决方案是使用mmap(2)映射匿名、可执行页面。


首先,我们编写一个小的汇编函数:

prog.s

[BITS 64]
global addints

;; unsigned long addints(unsigned long x, unsigned int y);
addints:
    mov     rax, rdi    ;; x
    add     rax, rsi    ;; y
    ret
然后将其组装起来,并查看操作码:
$ nasm -o binary prog.s
$ ndisasm -b64 prog
00000000  4889F8            mov rax,rdi
00000003  4801F0            add rax,rsi
00000006  C3                ret

现在将其编码到我们的示例程序中:

mmap.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/mman.h>

#define PAGE_SIZE   0x1000  /* always 4 KiB (min.) on x86 */

static const uint8_t mycode[] = {
    0x48, 0x89, 0xF8,   /* mov rax, rdi */
    0x48, 0x01, 0xF0,   /* add rax, rsi */
    0xC3,               /* ret */
};

int main(void)
{
    void *m;

    /* Use mmap to allocate a page of executable memory */
    m = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    if (m == MAP_FAILED) {
        fprintf(stderr, "mmap() failed: %m\n");
        exit(2);
    }

    /* Copy our code into the executable memory */
    memcpy(m, mycode, sizeof(mycode));

    /* For safety, remove the 'writable' flag */
    mprotect(m, PAGE_SIZE, PROT_READ | PROT_EXEC);

    /* Create a function pointer, and point it at the executable memory */
    unsigned long (*func)(unsigned long x, unsigned long y) = m;

    /* Call our code */
    unsigned long result = func(7, 3);

    printf("Result: %lu\n", result);

    return 0;
}

试一试吧:

$ gcc -Wall -Werror -o mmap mmap.c
$ ./mmap
Result: 10

无需组装!


2
jmp,call或push / ret。 - rcgldr
创建一个函数指针,将其指向已分配的内存... 你的意思是什么?将指向数据的指针转换为指向函数的指针不符合ISO C标准。 - edmz
@black 我很好奇,那么如何在ISO C中实现JITer呢?这正是Linux内核在JIT BPF代码时使用的确切结构。 - Jonathon Reinhart
它也可以通过 ld 函数完成(如 dlopen)。ISO C 没有定义这个,但符合 POSIX 的实现必须具备此功能。 - edmz
@black,如果你已经知道我的意思,那我不确定你为什么还要问我。 - Jonathon Reinhart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接