【问题标题】:How to call machine code stored in char array?如何调用存储在char数组中的机器码?
【发布时间】:2017-02-13 12:15:27
【问题描述】:

我正在尝试调用本机机器语言代码。到目前为止,这是我所拥有的(出现总线错误):

char prog[] = {'\xc3'}; // x86 ret instruction

int main()
{
    typedef double (*dfunc)();

    dfunc d = (dfunc)(&prog[0]);
    (*d)();
    return 0;
}

它确实正确地调用了该函数并进入了 ret 指令。但是当它尝试执行 ret 指令时,它有一个 SIGBUS 错误。是因为我在一个没有被清除执行的页面上执行代码吗?

那么我在这里做错了什么?

【问题讨论】:

  • 如果它对任何人都有帮助:我经常发现 SIGBUS 表示对齐错误。
  • @user 请将您的解决方案作为答案发布,而不是将其编辑到您的问题中。
  • 我恢复了对代码示例的更改,所以这个问题又有意义了。请按照@dandan78 的建议去做并接受答案,而不是更新您的问题,在此过程中更改其含义。
  • 使用asm()函数会不会更实用?
  • 请,请,请,请,请,请,请使用asm()(根据@Stavr00 的评论和格雷厄姆的回答)而不是任何其他方法,特别是如果您的代码有 在一块以任何方式连接到互联网或有任何可能与您以外的任何人交互的硅片上看到曙光的任何可能性。

标签: c++ c linux native machine-language


【解决方案1】:

一个明显的错误是\xc3 没有返回您声称它正在返回的double

【讨论】:

  • 真的! (这只是填充)
【解决方案2】:

第一个问题可能是存储 prog 数据的位置不可执行。

至少在 Linux 上,生成的二进制文件会将全局变量的内容放在 "data" segmenthere 中,这在 most normal cases 中是不可执行的。

第二个问题可能是您调用的代码在某种程度上是无效的。调用 C 中的方法有一定的过程,称为calling convention(例如,您可能正在使用“cdecl”)。被调用的函数仅仅“ret”可能是不够的。它可能还需要进行一些堆栈清理等,否则程序将出现意外行为。一旦你解决了第一个问题,这可能会成为一个问题。

【讨论】:

  • This article 非常完整地详细介绍了如何在 C 中嵌入和调用机器代码。它以将 main() 转换为 char 数组为前提。
【解决方案3】:

您需要调用 memprotect 以使 prog 所在的页面可执行。以下代码确实进行了此调用,并且可以执行 prog 中的文本。

#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>

char prog[] = {
   0x55,             // push   %rbp
   0x48, 0x89, 0xe5, // mov    %rsp,%rbp
   0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
       //movsd  0x0(%rip),%xmm0        # c <x+0xc>
   0x00,
   0x5d,             // pop    %rbp
   0xc3,             // retq
};

int main()
{
    long pagesize = sysconf(_SC_PAGE_SIZE);
    long page_no = (long)prog/pagesize;
    int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
    if(res)
    {
        fprintf(stderr, "mprotect error:%d\n", res);
        return 1;
    }
    typedef double (*dfunc)(void);

    dfunc d = (dfunc)(&prog[0]);
    double x = (*d)();
    printf("x=%f\n", x);
    fflush(stdout);
    return 0;
}

【讨论】:

  • 这种情况下,也可以声明数组const,让它存放在进程内存的可执行部分:stackoverflow.com/q/12446965/1025391
  • @moooeeeep 你通常不能假设只读数据的内存部分是可执行的,即使一些糟糕的链接器会这样做。
  • @CodesInChaos 您通常不能假设您可以执行存储在数组中的机器代码,但 OP 要求它。
  • @moooeeeep: 在去年左右的某个时候,GNU ld 开始将.rodata 链接到它自己的 ELF 段中,因此它可以是只读的没有 exec 权限.不像以前那样成为文本段的一部分。所以这个简单的技巧不再有效。不过,在 GNU C 中,您可以在 const 数组上使用 __attribute__((section(".text")))
  • 您的 shellcode 读取到其数组末尾的 movsd 0x0(%rip),%xmm0。这是从0x5d 字节(movsd 指令之后的字节,因为 RIP+0)开始的 8 字节加载。 x86 是 little-endian,因此 double 的指数字段将来自接下来出现的任何垃圾。看起来您天真地复制了 objdump 输出,用于编译器为返回 double 的函数生成的调试模式代码。它当然会从 .rodata 加载该常量,因为 x86 没有 FP 立即操作数。但是您没有将引用的 double 放入 shellcode。
【解决方案4】:

基本上这已被取缔,因为这是对病毒作者的公开邀请。但是您可以使用直接 C 语言中的本机机器代码分配和缓冲并设置它——这没问题。问题是调用它。虽然您可以尝试使用缓冲区的地址设置一个函数指针并调用它,但这极不可能工作,并且如果您设法以某种方式设法诱使它执行您想要的操作,则极有可能在编译器的下一个版本中中断.所以最好的办法是简单地使用一些内联汇编,设置返回并跳转到自动生成的代码。但是,如果系统对此进行了保护,您将不得不找到绕过保护的方法,正如 Rudi 在他的回答中所描述的那样(但对于一个特定的系统非常具体)。

【讨论】:

    【解决方案5】:

    正如大家已经说过的,您必须确保prog[] 是可执行的,但是正确的方法是,除非您正在编写 JIT 编译器,否则执行此操作的正确方法是将符号放在可执行区域中,或者使用链接描述文件或者在编译器允许的情况下指定 C 代码中的部分,例如:

    const char prog[] __attribute__((section(".text"))) = {...}
    

    【讨论】:

      【解决方案6】:

      几乎所有 C 编译器都可以通过在代码中嵌入常规汇编语言来实现这一点。当然,它是对 C 的非标准扩展,但编译器编写者认识到它通常是必要的。作为非标准扩展,您必须阅读编译器手册并检查如何操作,但 GCC "asm" extension 是一种相当标准的方法。

       void DoCheck(uint32_t dwSomeValue)
       {
          uint32_t dwRes;
      
          // Assumes dwSomeValue is not zero.
          asm ("bsfl %1,%0"
            : "=r" (dwRes)
            : "r" (dwSomeValue)
            : "cc");
      
          assert(dwRes > 3);
       }
      

      由于在汇编器中很容易丢弃堆栈,因此编译器通常还允许您识别将用作汇编器一部分的寄存器。然后编译器可以确保该函数的其余部分避开这些寄存器。

      如果您自己编写汇编程序代码,则没有充分的理由将该汇编程序设置为字节数组。这不仅仅是代码异味——我会说这是一个真正的错误,只有在不知道“asm”扩展名的情况下才会发生,这是将汇编程序嵌入到 C 中的正确方法。

      【讨论】:

      • 天哪,五个不同的用户是如何在没有提及asm的情况下回答这个问题的? Bleeeeeeaaaaaaaaarrrrrrrggggggghhhhh。
      • @KyleStrand 也许其他人都会区分机器语言(用户想要什么)和汇编程序。例如,如果您想即时生成代码,asm 就不太有用。
      • 此外,您不能在每个 C 编译器中使用真正的汇编。例如。 MSVC 会将其__asm 代码视为另一种高级语言的代码:它会尝试对其进行优化,并且不会让您发出原始字节(就像 MASM 中的 db 指令一样)。
      • @pipe 除了他正在使用指令字节码设置常量数组。如果他知道自己想要什么指令,他所做的只是嵌入“asm”块的复杂版本。
      • @KyleStrand 我从来没有提到过const。我很确定尝试从 C 执行机器语言的人知道 asm() 构造,我很高兴他将 example code sn-p 减少到最低限度说明问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      • 2012-10-12
      • 1970-01-01
      相关资源
      最近更新 更多