32位GCC C程序中的内联64位汇编

时间:2015-12-25 22:50:45

标签: gcc assembly

我正在编译32位二进制文​​件,但希望在其中嵌入一些64位程序集。

// javapgmr/xstr -- "smart" string "class" for C

typedef struct {
    size_t xstr_maxlen;                 // maximum space in string buffer
    char *xstr_lhs;                     // pointer to start of string
    char *xstr_rhs;                     // pointer to current string append
} xstr_t;

// xstrinit -- reset string buffer
void
xstrinit(xstr_t *xstr)
{

    memset(xstr,0,sizeof(xstr));
}

// xstragain -- reset string buffer
void
xstragain(xstr_t xstr)
{

    xstr->xstr_rhs = xstr->xstr_lhs;
}

// xstrgrow -- grow string buffer
void
xstrgrow(xstr_t *xstr,size_t needlen)
{
    size_t curlen;
    size_t newlen;
    char *lhs;

    lhs = xstr->xstr_lhs;

    // get amount we're currently using
    curlen = xstr->xstr_rhs - lhs;

    // get amount we'll need after adding the whatever
    newlen = curlen + needlen + 1;

    // allocate more if we need it
    if ((newlen + 1) >= xstr->xstr_maxlen) {
        // allocate what we'll need plus a bit more so we're not called on
        // each add operation
        xstr->xstr_maxlen = newlen + 100;

        // get more memory
        lhs = realloc(lhs,xstr->xstr_maxlen);
        xstr->xstr_lhs = lhs;

        // adjust the append pointer
        xstr->xstr_rhs = lhs + curlen;
    }
}

// xstraddchar -- add character to string
void
xstraddchar(xstr_t *xstr,int chr)
{

    // get more space in string buffer if we need it
    xstrgrow(xstr,1);

    // add the character
    *xstr->xstr_rhs++ = chr;

    // maintain the sentinel/EOS as we go along
    *xstr->xstr_rhs = 0;
}

// xstraddstr -- add string to string
void
xstraddstr(xstr_t *xstr,const char *str)
{
    size_t len;

    len = strlen(str);

    // get more space in string buffer if we need it
    xstrgrow(xstr,len);

    // add the string
    memcpy(xstr->xstr_rhs,str,len);
    xstr->xstr_rhs += len;

    // maintain the sentinel/EOS as we go along
    *xstr->xstr_rhs = 0;
}

// xstrcstr -- get the "c string" value
char *
xstrcstr(xstr_t *xstr,int chr)
{

    return xstr->xstr_lhs;
}

// xstrfree -- release string buffer data
void
xstrfree(xstr_t *xstr)
{
    char *lhs;

    lhs = xstr->xstr_lhs;
    if (lhs != NULL)
        free(lhs);

    xstrinit(xstr);
}

当然,当我编译时,我会因为寄存器是64位而错误地引用了错误的寄存器。

void method() {
   asm("...64 bit assembly...");
}

是否可以添加一些注释,以便gcc使用64位汇编程序处理asm部分。我有一个单独编译的解决方法,使用PROT_EXEC | PROT_WRITE在页面中映射并在我的代码中复制,但这非常尴尬。

3 个答案:

答案 0 :(得分:2)

不,这是不可能的。您无法从32位二进制文​​件运行64位程序集,因为运行程序时处理器不在long mode

将64位代码复制到可执行页面将导致该代码被错误地解释为32位代码,这将产生不可预测且不合需要的结果。

答案 1 :(得分:2)

不要试图将64位机器代码放在编译器生成的函数中。它可能有用,因为函数序言/结尾的编码在32位和64位中是相同的,但是只有一个单独的64位代码块会更清晰。

最简单的事情可能就是将该块组装在一个单独的文件中,使用GAS .code64或NASM BITS 64来获取目标文件中的64位代码,您可以链接到32位可执行文件

你说in a comment你正在考虑将这个用于从32位用户空间进程对64位内核的内核攻击,所以你只需要在你的可执行部分中使用一些代码字节进程的内存和获取指向该块的指针的方法。这当然是合理的;如果您可以从32位进程获得内核RIP的控制权,那么这就是您想要的,因为内核代码将始终以长模式运行。

如果您在以32位模式启动的进程中使用64位用户空间代码执行某些操作,则可以使用已知的far jmp到64位代码块(as @RossRidge suggests) __USER_CS内核的syscall 64位代码段描述符的值。来自64位代码的int 0x80应该以64位模式返回,但如果没有,请尝试cs ABI。它始终返回到您所处的模式,保存/恢复ssrip以及rflags.rodata。 (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?

const是可执行文件测试段的一部分,因此只需让编译器将字节放在195数组中。有趣的事实:const int main = 195;编译到一个没有segfaulting的程序退出,因为0xc3 = ret = const char funcname[] = { 0x90, 0x90, ..., 0xc3 }的x86编码(而x86是little-endian)。对于任意长度的机器代码序列, const将起作用.data是必需的,否则它将进入.rodata(读/写/ noexec)而不是const char funcname[] __attribute__((section(".text"))) = { ... };

您可以使用.text to control what section it进入(例如.c以及编译器生成的函数),甚至可以使用链接描述来获得更多控制权。

如果你真的想在一个asm文件中完成所有操作,而不是使用单独组装的纯asm源的简单解决方案:

要汇编一些64位代码以及编译器生成的32位代码,请在任何函数之外的 asm(".pushsection .text \n\t" // AFAIK, there's no guarantee how this will mix with compiler asm output ".code64 \n\t" ".p2align 4 \n\t" ".globl my_codebytes \n\t" // optional "my_codebytes: \n\t" "inc %r10d \n\t" "my_codebytes_end: \n\t" //"my_codebytes_len: .long . - my_codebytes\n\t" // store the length in memory. Optional ".popsection \n\t" #ifdef __i386 ".code32" // back to 32-bit interpretation for gcc's code // "\n\t inc %r10" // uncomment to check that it *doesn't* assemble #endif ); #ifdef __cplusplus extern "C" { #endif // put C names on the labels. // They are *not* pointers, their addresses are link-time constants extern char my_codebytes[], my_codebytes_end[]; //extern const unsigned my_codebytes_len; #ifdef __cplusplus } #endif // This expression for the length isn't a compile-time constant, so this isn't legal C //static const unsigned len = &my_codebytes_end - &my_codebytes; #include <stddef.h> #include <unistd.h> int main(void) { size_t len = my_codebytes_end - my_codebytes; const char* bytes = my_codebytes; // do whatever you want. Writing it to stdout is one option! write(1, bytes, len); } 语句*中使用the .code64 GAS directive < / strong>即可。 IDK如果有任何保证,当gcc发出你的asm时,gcc会将asm与asm混合,但是它不会把它放在函数的中间。

peter@volta$ gcc -m32 -Wall -O3 /tmp/foo.c
peter@volta$ ./a.out  | hd
00000000  41 ff c2                                          |A..|
00000003

使用gcc和clang(compiler explorer)编译和汇编。

我在桌面上试了一下,仔细检查:

inc %r10d

这是-m32的正确编码:)

该程序在没有#ifdef的情况下编译时也可以使用,因为我使用.code32来决定是否在最后使用00000580 <my_codebytes>: 580: 41 inc ecx 581: ff c2 inc edx 。 (没有推送/弹出模式指令,就像部分一样。)

当然,反汇编二进制文件会告诉你:

<select>
<option *ngFor="let x of countedCategory;">{{x._id}} {{x.countCategory}}</option>
</select>

因为反汇编程序不知道切换到该块的64位反汇编。 (我想知道ELF是否具有相关属性......如果存在这样的事情,我没有使用任何汇编程序指令或链接描述文件来生成这些属性。)

答案 2 :(得分:1)

通过更改CS来完成长模式和兼容模式之间的切换。用户模式代码不能修改描述符表,但它可以对已经存在于描述符表中的代码段执行远程跳转或远程调用。在Linux中,存在所需的描述符(根据我的经验;对于所有安装可能都不是这样)。

下面是64位Linux(Ubuntu)的示例代码,它以32位模式启动,切换到64位模式,运行一个函数,然后切换回32位模式。使用gcc -m32构建。

ApplicationController.new.render_to_string(
  :template => 'users/index',
  :layout => 'my_layout',
  :locals => { :@users => @users }
)