问题 从C执行二进制机器代码


以下 这个 我已经设法生成大小只有528字节的a.out(当gcc main.c最初给我8539字节的大文件时)。

main.c是:

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

    return 42;
}

但我已经从这个汇编文件中构建了一个a.out:

电源:

; 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以不同的方式调用它?


11256
2017-08-27 21:45


起源

您不能只将一些随机字节视为一个函数。您必须尊重编译器的调用约定并提供合适的函数序言和结尾。 - Kerrek SB
当然,这个操作码是用相同的编译器生成的,而不是随机的,所以应该没关系,你知道我该怎么做吗?为什么我可以从终端运行它? - 4pie0
首先,您需要确保代码驻留在可执行内存中。尝试添加类似的东西 __attribute__((section, ".text")) 或类似的(见手册)。正如我所说,确保实现正确的调用约定。 - Kerrek SB
谢谢,我将尝试从gcc正常方式生成的a.out中获取操作码,但是然后将操作码放到asm并像以前一样构建a.out以避免运行时库的开销。你觉得这是个好主意吗?不,我将只使用a.out中的操作码,因为我在这里不使用任何库 - 4pie0
我有这个:* 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)/NetBeansProjects/examples/tiny_c/tiny.c:15返回42; 00000000004004bf <main + 0xb> b8 2a 00 00 00 mov $ 0x2a,%eax /NetBeansProjects/examples/tiny_c/tiny.c:16} 00000000004004c4 <main + 0x10> c9 leaveq 00000000004004c5 <main + 0x11> c3 retq - 4pie0


答案:


我完成了这件事。代码必须标记为可执行代码。一种方法是将此二进制机器代码复制到可执行缓冲区中。

#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)
/NetBeansProjects/examples/tiny_c/tiny.c:15
    return 42;
00000000004004bf <main+0xb> b8 2a 00 00 00          mov    $0x2a,%eax
/NetBeansProjects/examples/tiny_c/tiny.c:16
}
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));

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

输出:

完成这件事。返回:42

RUN SUCCESSFUL(总时间:57ms)


12
2017-08-27 23:27



不错,但知识渊博,在你滥用这个之前要考虑到这一点。 - Raymond Nijland
你可以使用这个代码进行一些微小的调整作为shellcode执行器,当有内存泄漏是软件,但它更难以正确...但我认为你正在构建某种虚拟机? - Raymond Nijland
不,我只是想从我自己编写的C / C ++程序机器代码中执行。 - 4pie0
然后考虑使用一些JIT机器代码生成器库: LLVM, 要么 GNU闪电 要么 libjit。或者只是使用 asm C语言教学 - Basile Starynkevitch
或者(在Linux上),在运行时生成C代码,将编译分成共享对象,然后 dlopen那个共享对象...... - Basile Starynkevitch