【问题标题】:execute binary machine code from C从 C 执行二进制机器代码
【发布时间】:2013-08-30 19:46:58
【问题描述】:

按照this 的指令,我只生成了大小为 a.out 的 528 字节(当 gcc main.c 最初给了我 8539 字节的大文件时)。

main.c 是:

int main(int argc, char** argv) {

    return 42;
}

但我已经从这个程序集文件构建了 a.out:

main.s:

; tiny.asm
  BITS 64
  GLOBAL _start
  SECTION .text
  _start:
                mov     eax, 1
                mov     ebx, 42  
                int     0x80

与:

me@comp# nasm -f elf64 tiny.s
me@comp# gcc -Wall -s -nostartfiles -nostdlib tiny.o
me@comp# ./a.out ; echo $?
42
me@comp# wc -c a.out
528 a.out

因为我需要机器码:

objdump -d a.out

a.out:     file format elf64-x86-64


Disassembly of section .text:

00000000004000e0 <.text>:
  4000e0:   b8 01 00 00 00          mov    $0x1,%eax
  4000e5:   bb 2a 00 00 00          mov    $0x2a,%ebx
  4000ea:   cd 80                   int    $0x80

># objdump -hrt a.out

a.out:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
 0 .note.gnu.build-id 00000024  00000000004000b0  00000000004000b0  000000b0 2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 1 .text         0000000c  00000000004000e0  00000000004000e0  000000e0 2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
SYMBOL TABLE:
no symbols

文件采用 little endian 约定:

me@comp# readelf -a a.out
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4000e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          272 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         4
  Section header string table index: 3

现在我想这样执行:

#include <unistd.h>
 // which version is (more) correct?
 // this might be related to endiannes (???)
char code[] = "\x01\xb8\x00\x00\xbb\x00\x00\x2a\x00\x00\x80\xcd\x00";
char code_v1[] = "\xb8\x01\x00\x00\x00\xbb\x2a\x00\x00\x00\xcd\x80\x00";

int main(int argc, char **argv)
{
/*creating a function pointer*/
int (*func)();
func = (int (*)()) code;
(int)(*func)();

return 0;
}

但是我得到分段错误。 我的问题是:这是一段文字

  4000e0:   b8 01 00 00 00          mov    $0x1,%eax
  4000e5:   bb 2a 00 00 00          mov    $0x2a,%ebx
  4000ea:   cd 80                   int    $0x80

(这个机器代码)我真正需要的一切?我做错了什么(字节序??),也许我只需要从 SIGSEGV 开始以不同的方式调用它?

【问题讨论】:

  • 您不能只将几个随机字节视为一个函数。您必须尊重编译器的调用约定,并提供合适的函数序言和结语。
  • 当然,这个操作码是用相同的编译器生成的,不是随机的,所以应该没问题,你知道我该怎么做吗?为什么我可以从终端运行它?
  • 首先,您需要确保代码驻留在可执行内存中。尝试添加类似__attribute__((section, ".text")) 或类似的内容(参见手册)。正如我所说,确保实现正确的调用约定。
  • 谢谢,我将尝试从 gcc 正常方式生成的 a.out 中获取操作码,但将操作码然后放入 asm 并像以前一样构建 a.out 以避免运行时库开销。你认为这是个好主意吗?不,我将只使用来自 a.out 的操作码,因为我在这里不使用任何库
  • 我有这个:* 00000000004004b4
    55 push %rbp 00000000004004b5
    48 89 e5 mov %rsp,%rbp 00000000004004b8
    89 7d fc mov %ed 0x4(%rbp) 00000000004004bb
    48 89 75 f0 mov %rsi,-0x10(%rbp) /NetBeansProjects/examples/tiny_c/tiny.c:15 返回 42; 00000000004004bf
    b8 2a 00 00 00 mov $0x2a,%eax /NetBeansProjects/examples/tiny_c/tiny.c:16 } 00000000004004c4
    c9 leaveq 000000000040034c5

标签: c gcc assembly nasm opcode


【解决方案1】:

代码必须在具有执行权限的页面中。默认情况下,出于安全原因,堆栈和读写静态数据(如非 const 全局变量)在没有 exec 权限的情况下映射到页面中。

最简单的方法是使用gcc -z execstack 进行编译,它链接您的程序以便堆栈 全局变量(静态存储)映射到可执行页面中,malloc 的分配也是如此。


另一种不使一切可执行的方法是将此二进制机器代码复制到可执行缓冲区中。

#include <unistd.h>
#include <sys/mman.h>
#include <string.h>

char code[] = {0x55,0x48,0x89,0xe5,0x89,0x7d,0xfc,0x48,
    0x89,0x75,0xf0,0xb8,0x2a,0x00,0x00,0x00,0xc9,0xc3,0x00};
/*
00000000004004b4 <main> 55                          push   %rbp
00000000004004b5 <main+0x1>  48 89 e5               mov    %rsp,%rbp
00000000004004b8 <main+0x4>  89 7d fc               mov    %edi,-0x4(%rbp)
00000000004004bb <main+0x7>  48 89 75 f0            mov    %rsi,-0x10(%rbp)
'return 42;'
00000000004004bf <main+0xb>  b8 2a 00 00 00         mov    $0x2a,%eax
'}'
00000000004004c4 <main+0x10> c9                     leaveq 
00000000004004c5 <main+0x11> c3                     retq 
*/

int main(int argc, char **argv) { 
    void *buf;

    /* copy code to executable buffer */    
    buf = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
                MAP_PRIVATE|MAP_ANON,-1,0);
    memcpy (buf, code, sizeof(code));
    __builtin___clear_cache(buf, buf+sizeof(code)-1);  // on x86 this just stops memcpy from optimizing away as a dead store

    /* run code */
    int i = ((int (*) (void))buf)();
    printf("get this done. returned: %d", i);

    return 0;
}

输出:

完成这件事。返回:42

运行成功(总时间:57 毫秒)

没有__builtin___clear_cachethis could break 启用优化因为gcc 会认为memcpy 是一个死存储并优化它。在为 x86 编译时,__builtin___clear_cache 确实 not 实际上清除了任何缓存;额外指令为零;它只是将内存标记为“已使用”,因此不会将其存储为“已死”。 (见the gcc manual。)


另一个选项是mprotect 包含char code[] 数组的页面,将其设为PROT_READ|PROT_WRITE|PROT_EXEC。无论是本地数组(在堆栈上)还是 .data 中的全局数组,这都有效。

或者,如果它是 .rodata 部分中的 const char code[],您可以直接给它 PROT_READ|PROT_EXEC

(在大约 2019 年之前的 binutils ld 版本中,.rodata 被链接为与 .text 相同的段的一部分,并且已经被映射为可执行文件。但最近的 ld 给了它一个单独的段,所以它可以在没有 exec 权限的情况下进行映射,因此 const char code[] 不再为您提供可执行数组,但它曾经提供过,因此您可以在其他地方使用这个旧建议。)

【讨论】:

  • 很好,但知识渊博,责任重大,在你滥用它之前考虑一下......
  • 当软件存在内存泄漏时,您可以使用此代码进行一些小的调整作为 shellcode 执行器,但要正确处理它要困难得多.. 但我认为您正在构建某种作为虚拟机?
  • 没有,我只是想从自己写的C/C++程序机器码执行。
  • 然后考虑使用一些 JIT 机器代码生成器库:LLVM,或GNU lightninglibjit。或者只使用 C 中的asm 指令。
  • 我稍微扩展了您的答案,使其成为其他人可以复制的更好的规范答案,并且即使启用了优化,也使示例代码防弹。
【解决方案2】:

关键是启用了 DEP 保护! 您可以转到配置 -> 链接器 -> 高级 -> DEP 关闭, 现在好了。

void main(){
int i = 11;
//The following is the method to generate the machine code directly!
//mov eax, 1; ret;
const char *code = "\xB8\x10\x00\x00\x00\xc3";
    __asm call code;  //test successful~..vs 2017
    __asm mov i ,eax;
printf("i=%d", i);
}

【讨论】:

  • 这无关紧要,OP 在 Linux 上使用了 GCC 编译器(如使用的命令所见),Linux 不将 DEP 用于用户空间进程(afaik),并且 GCC 不具有 MSVC 的 GUI
猜你喜欢
  • 2016-08-28
  • 2012-04-15
  • 1970-01-01
  • 2020-05-31
  • 2011-02-17
  • 1970-01-01
  • 1970-01-01
  • 2017-02-17
相关资源
最近更新 更多