从C执行二进制机器代码

时间:2013-08-27 21:45:13

标签: c gcc assembly nasm opcode

按照this指令,我设法只生成了大小为528字节的a.out(当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

文件采用小端约定:

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

(这个机器代码)我真的需要什么?我做错了什么(endiannes ??),也许我只需要从SIGSEGV以不同的方式调用它?

2 个答案:

答案 0 :(得分:20)

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

最简单的方法是使用gcc -z execstack进行编译,它将您的程序链接起来,使得堆栈全局变量(静态存储)被映射到可执行页面中,所以使用{{进行分配1}}。


在不使所有可执行文件的情况下执行此操作的另一种方法是将此二进制机器代码复制到可执行缓冲区中。

malloc
  

输出:

     

完成这件事。返回:42

     

RUN SUCCESSFUL(总时间:57ms)

没有#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; } this could break已启用优化,因为gcc会认为__builtin___clear_cache是一个死存储并将其优化掉。在为x86进行编译时,memcpy 实际上清除了任何缓存;没有额外的指示;它只是将内存标记为“已使用”,因此它的存储不会被视为“死”。 (见the gcc manual。)


另一个选项是__builtin___clear_cache包含mprotect数组的网页,并为其提供char code[]。这适用于它是一个本地数组(在堆栈上)还是全局在PROT_READ|PROT_WRITE|PROT_EXEC

如果它是.data部分中的const char code[],您可能只是给它.rodata

(在大约2019年之前的binutils PROT_READ|PROT_EXEC版本中,ld被链接为.rodata的同一段的一部分,并且已经映射了可执行文件。但是最近{{1}给它一个单独的段,这样它就可以在没有exec权限的情况下进行映射,因此.text不再给你一个可执行的数组了,但它过去常常让你在其他地方提出这个老建议。)

答案 1 :(得分:1)

重点是启用了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);
}