当前位置: 首页 > 知识库问答 >
问题:

如何在Linux中使用十六进制编辑器创建一个可执行的ELF文件?

公冶安怡
2023-03-14

我的架构是x86。我能做的一个非常简单的程序是什么?一个你好的世界?无限循环?类似于这个问题,但在Linux中。

共有1个答案

申昌勋
2023-03-14
    null
    null
    null
man elf
section .data
    hello_world db "Hello world!", 10
    hello_world_len  equ $ - hello_world
section .text
    global _start
    _start:
        mov rax, 1
        mov rdi, 1
        mov rsi, hello_world
        mov rdx, hello_world_len
        syscall
        mov rax, 60
        mov rdi, 0
        syscall
nasm -w+all -f elf64 -o 'hello_world.o' 'hello_world.asm'
ld -o 'hello_world.out' 'hello_world.o'
    null
hd hello_world.o
hd hello_world.out
readelf -h hello_world.o
readelf -h hello_world.out
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  01 00 3e 00 01 00 00 00  00 00 00 00 00 00 00 00  |..>.............|
00000020  00 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  |........@.......|
00000030  00 00 00 00 40 00 00 00  00 00 40 00 07 00 03 00  |....@.....@.....|
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  b0 00 40 00 00 00 00 00  |..>.......@.....|
00000020  40 00 00 00 00 00 00 00  10 01 00 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  02 00 40 00 06 00 03 00  |....@.8...@.....|

所表示的结构:

typedef struct {
    unsigned char   e_ident[EI_NIDENT];
    Elf64_Half      e_type;
    Elf64_Half      e_machine;
    Elf64_Word      e_version;
    Elf64_Addr      e_entry;
    Elf64_Off       e_phoff;
    Elf64_Off       e_shoff;
    Elf64_Word      e_flags;
    Elf64_Half      e_ehsize;
    Elf64_Half      e_phentsize;
    Elf64_Half      e_phnum;
    Elf64_Half      e_shentsize;
    Elf64_Half      e_shnum;
    Elf64_Half      e_shstrndx;
} Elf64_Ehdr;

手动细分:

>

  • 0 0:ei_mag=7f 45 4c 46=0x7f'e','l','f':ELF magic number

    07:ei_osabi(仅在2003年更新中)=00=elfosabi_none:没有扩展。

    08:ei_pad=8x00:保留字节。必须设置为0。

    1 0:e_type=0100=1(big endian)=et_rel:可重定位格式

    1 4:E_version=01000000:必须为1

    1 8:e_entry=8x00:执行地址入口点,如果不适用,则为0,因为没有入口点。

    在可执行文件上,它是b000400000000000。TODO:我们还能把它调成什么?内核似乎把IP直接放在那个值上,它不是硬编码的。

    3 0:e_flags=00000000TODO。拱门特定。

    3 4:e_ehsize=4000:此elf头的大小。为什么要做这个领域?怎么会有变化呢?

    3 6:e_phentsize=0000:每个程序头的大小,如果不存在则为0。

    可执行文件上的0200:有2个条目。

    3 A:e_shentsizee_shnum=40 00 07 00:节头大小和条目数

    3 e:e_shstrndx(节头字符串索引)=0300:.shstrtab节的索引。

    ELF头的e_shoff给出起始位置,这里是0x40。

    ELF头中的e_shentsizee_shnum表示我们有7个条目,每个0x40字节长。

    因此,该表接受从0x40到0x40+7+0x40-1=0x1ff的字节。

    There are 7 section headers, starting at offset 0x40:
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .data             PROGBITS         0000000000000000  00000200
           000000000000000d  0000000000000000  WA       0     0     4
      [ 2] .text             PROGBITS         0000000000000000  00000210
           0000000000000027  0000000000000000  AX       0     0     16
      [ 3] .shstrtab         STRTAB           0000000000000000  00000240
           0000000000000032  0000000000000000           0     0     1
      [ 4] .symtab           SYMTAB           0000000000000000  00000280
           00000000000000a8  0000000000000018           5     6     4
      [ 5] .strtab           STRTAB           0000000000000000  00000330
           0000000000000034  0000000000000000           0     0     1
      [ 6] .rela.text        RELA             0000000000000000  00000370
           0000000000000018  0000000000000018           4     2     4
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)
    
    typedef struct {
        Elf64_Word  sh_name;
        Elf64_Word  sh_type;
        Elf64_Xword sh_flags;
        Elf64_Addr  sh_addr;
        Elf64_Off   sh_offset;
        Elf64_Xword sh_size;
        Elf64_Word  sh_link;
        Elf64_Word  sh_info;
        Elf64_Xword sh_addralign;
        Elf64_Xword sh_entsize;
    } Elf64_Shdr;
    

    包含在字节0x40到0x7F中。

    第一部分总是很神奇:http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html说:

    如果节数大于或等于SHN_LORESERVE(0xFF00),则e_shnum的值为SHN_UNDEF(0),节头表条目的实际数量包含在索引为0的节头的sh_size字段中(否则,初始条目的sh_size成员包含0)。

    00000080  01 00 00 00 01 00 00 00  03 00 00 00 00 00 00 00  |................|
    00000090  00 00 00 00 00 00 00 00  00 02 00 00 00 00 00 00  |................|
    000000a0  0d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    000000b0  04 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    

    >

  • 800:sh_name=01000000:.shstrtab字符串表中的索引1

    这里,1表示该节的名称从该节的第一个字符开始,到第一个NUL字符结束,组成字符串.data

    .data是节名之一,它具有预定义的含义http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    804:sh_type=01 000000:sht_progbits:区段内容不是由ELF指定的,而是由程序如何解释它指定的。正常,因为.data节。

    808:sh_flags=037x00:shf_allocshf_execinstr:http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags,根据.data部分的要求

    90 0:sh_addr=8x00:在执行期间该节将放置在哪个虚拟地址中,如果未放置则0

    90 8:sh_offset=00 02 0000000000000000=0x200:从程序开始到本节中第一个字节的字节数

    a0 0:sh_size=0d 000000000000000000

    如果从sh_offset200开始取0xD字节,我们会看到:

    00000200  48 65 6c 6c 6f 20 77 6f  72 6c 64 21 0a 00        |Hello world!..  |
    
    readelf -x .data hello_world.o
    
    Hex dump of section '.data':
      0x00000000 48656c6c 6f20776f 726c6421 0a       Hello world!.
    

    NASM为该节设置了合适的属性,因为它神奇地处理.data:http://www.NASM.us/doc/nasmdoc7.html#section-7.9.2

    还要注意,这是一个错误的节选择:一个好的C编译器会将字符串放在.rodata中,因为它是只读的,并且允许进一步的OS优化。

    A08:sh_linksh_info=8x0:不适用于此节类型。http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections

    现在我们已经手动完成了一个部分,让我们毕业并使用其他部分的readelf-s

      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 2] .text             PROGBITS         0000000000000000  00000210
           0000000000000027  0000000000000000  AX       0     0     16
    

    .text是可执行的,但不可写:如果我们尝试向它写,Linux segfaults。让我们看看我们是否真的有一些代码:

    objdump -d hello_world.o
    

    给予:

    hello_world.o:     file format elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <_start>:
       0:       b8 01 00 00 00          mov    $0x1,%eax
       5:       bf 01 00 00 00          mov    $0x1,%edi
       a:       48 be 00 00 00 00 00    movabs $0x0,%rsi
      11:       00 00 00
      14:       ba 0d 00 00 00          mov    $0xd,%edx
      19:       0f 05                   syscall
      1b:       b8 3c 00 00 00          mov    $0x3c,%eax
      20:       bf 00 00 00 00          mov    $0x0,%edi
      25:       0f 05                   syscall
    
    movabs $0x0,%rsi
    
    4000ba: 48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
    

    具有sh_type==sht_strtab的节称为字符串表。

    它们保存一个空分隔的字符串数组。

    当要使用字符串名称时,这些节由其他节使用。使用部分说:

      null
    Data: \0 a b c \0 d e f \0
    Index: 0 1 2 3  4 5 6 7  8
    
      null

    节类型:sh_type==sht_strtab

    通用名称:节头字符串表。

    保留节名.shstrtab。标准说:

    readelf -x .shstrtab hello_world.o
    
    Hex dump of section '.shstrtab':
      0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh
      0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab..
      0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex
      0x00000030 7400                                t.
    

    本节中的数据有固定格式:http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    如果我们查看其他部分的名称,我们会看到它们都包含数字,例如.text部分是number7

    然后,当找到第一个NUL字符时,每个字符串结束,例如,字符12\0紧随.text\0之后的\0

    首先,我们注意到:

    • sh_link=5
    • sh_info=6

    对于sht_symtab节,这些数字表示:

      null
    nm hello_world.o
    
    0000000000000000 T _start
    0000000000000000 d hello_world
    000000000000000d a hello_world_len
    
    readelf -s hello_world.o
    

    它给出:

    Symbol table '.symtab' contains 7 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello_world.asm
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
         4: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    1 hello_world
         5: 000000000000000d     0 NOTYPE  LOCAL  DEFAULT  ABS hello_world_len
         6: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    2 _start
    

    该表的二进制格式见http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

    数据是:

    readelf -x .symtab hello_world.o
    
    Hex dump of section '.symtab':
      0x00000000 00000000 00000000 00000000 00000000 ................
      0x00000010 00000000 00000000 01000000 0400f1ff ................
      0x00000020 00000000 00000000 00000000 00000000 ................
      0x00000030 00000000 03000100 00000000 00000000 ................
      0x00000040 00000000 00000000 00000000 03000200 ................
      0x00000050 00000000 00000000 00000000 00000000 ................
      0x00000060 11000000 00000100 00000000 00000000 ................
      0x00000070 00000000 00000000 1d000000 0000f1ff ................
      0x00000080 0d000000 00000000 00000000 00000000 ................
      0x00000090 2d000000 10000200 00000000 00000000 -...............
      0x000000a0 00000000 00000000                   ........
    
    typedef struct {
        Elf64_Word  st_name;
        unsigned char   st_info;
        unsigned char   st_other;
        Elf64_Half  st_shndx;
        Elf64_Addr  st_value;
        Elf64_Xword st_size;
    } Elf64_Sym;
    

    链接器可以使用这段信息文件来决定哪些区段区段。

    10 12:st_info=04

    位0-3=elf64_r_type=Type=4=stt_file:该条目的主要用途是使用st_name指示生成此对象文件的文件名。

    20 8:st_size=8x00:未分配大小

    现在,从Readel开始,我们将快速解释其他内容。

    有两个这样的条目,一个指向.data,另一个指向.text(节索引12)。

    Num:    Value          Size Type    Bind   Vis      Ndx Name
      2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
      3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
    
    Num:    Value          Size Type    Bind   Vis      Ndx Name
      4: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT    1 hello_world
      5: 000000000000000d     0 NOTYPE  LOCAL  DEFAULT  ABS hello_world_len
      6: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    2 _start
    

    _start标记为全局可见性,因为我们编写了:

    global _start
    

    在纳斯姆。这是必要的,因为它必须被视为切入点。与C语言不同,默认情况下NASM标签是本地的。

    hello_world_len指向特殊的st_shndx==SHN_ABS==0xf1ff

    如果我们在任何地方使用hello_world_len的地址,汇编程序将无法将其标记为shn_abs,链接器稍后将对其进行额外的重新定位。

    默认情况下,NASM还在可执行文件上放置.symtab

    这仅用于调试。没有符号,我们就完全是盲目的,必须对一切进行逆向工程。

    它由.symtab节的sh_link==5指向。

    readelf -x .strtab hello_world.o
    

    给予:

    Hex dump of section '.strtab':
      0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm
      0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel
      0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st
      0x00000030 61727400                            art.
    

    这意味着全局变量不能包含NUL字符是ELF级别的限制。

       a:       48 be 00 00 00 00 00    movabs $0x0,%rsi
      11:       00 00 00
    
    4000ba: 48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
    4000c1: 00 00 00
    

    它由.symtab节的sh_info=6指向。

    readelf-r hello_world.o给出:

    Relocation section '.rela.text' at offset 0x3b0 contains 1 entries:
      Offset          Info           Type           Sym. Value    Sym. Name + Addend
    00000000000c  000200000001 R_X86_64_64       0000000000000000 .data + 0
    

    该节不存在于可执行文件中。

    00000370  0c 00 00 00 00 00 00 00  01 00 00 00 02 00 00 00  |................|
    00000380  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    
    typedef struct {
        Elf64_Addr  r_offset;
        Elf64_Xword r_info;
        Elf64_Sxword    r_addend;
    } Elf64_Rela;
    

    370 8:r_info=0x200000001。包含2个字段:

    • elf64_r_type=0x1:含义取决于确切的体系结构。
    • ELF64_R_SYM=0x2:地址指向的节的索引,因此.data位于索引2。

    AMD64 ABI指出,类型1称为R_x86_64_64,它表示操作S+A,其中:

      null

    此重定位操作总共作用于8个字节。

    380:r_addend=0

    因此,在我们的示例中,我们得出结论,新地址将是:s+a=.data+0,因此是数据部分的第一个内容。

    在Binutils中,这归结为解析链接器脚本和处理一系列默认值。

    您可以获得与ld--verbose一起使用的链接器脚本,并使用ld-t设置一个自定义链接器脚本。

    对文本部分进行重新定位。这取决于将多个节放入内存的方式。

    Elf file type is EXEC (Executable file)
    Entry point 0x4000b0
    There are 2 program headers, starting at offset 64
    
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                     0x00000000000000d7 0x00000000000000d7  R E    200000
      LOAD           0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
                     0x000000000000000d 0x000000000000000d  RW     200000
    
     Section to Segment mapping:
      Segment Sections...
       00     .text
       01     .data
    
    00000040  01 00 00 00 05 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000050  00 00 40 00 00 00 00 00  00 00 40 00 00 00 00 00  |..@.......@.....|
    00000060  d7 00 00 00 00 00 00 00  d7 00 00 00 00 00 00 00  |................|
    00000070  00 00 20 00 00 00 00 00                           |.. .....        |
    
    00000070                           01 00 00 00 06 00 00 00  |        ........|
    00000080  d8 00 00 00 00 00 00 00  d8 00 60 00 00 00 00 00  |..........`.....|
    00000090  d8 00 60 00 00 00 00 00  0d 00 00 00 00 00 00 00  |..`.............|
    000000a0  0d 00 00 00 00 00 00 00  00 00 20 00 00 00 00 00  |.......... .....|
    
    typedef struct {
        Elf64_Word  p_type;
        Elf64_Word  p_flags;
        Elf64_Off   p_offset;
        Elf64_Addr  p_vaddr;
        Elf64_Addr  p_paddr;
        Elf64_Xword p_filesz;
        Elf64_Xword p_memsz;
        Elf64_Xword p_align;
    } Elf64_Phdr;
    

    第一项的细分:

    • 40 0:p_type=01 000000=pt_load:TODO。我想这意味着它将被实际加载到内存中。其他类型不一定是。
    • 404:p_flags=05000000=执行和读取权限,没有写权限
    • 408:P_offset=8x00TODO:这是什么?看起来像是从段开始的偏移量。但这意味着一些片段是交织在一起的?可以使用以下方法来玩:gcc-wl,-ttext-segment=0x400030 hello_world.c
    • 500:P_vaddr=0000400000000000:将此段加载到
    • 的初始虚拟内存地址
    • 508:p_paddr=0000400000000000:加载到内存中的初始物理地址。只有在程序可以设置物理地址的系统中才重要。否则,就像在系统V中一样,可以是任何东西。NASM似乎只是复制p_vaddrr
    • 60 0:p_filesz=d7 00000000000000000000:TODO vsp_memsz
    • 608:p_memsz=d7 000000000000000000:todo
    • 70 0:P_align=00 00 20 00 00 00 00 00:0或1表示不需要对齐,这是什么意思?否则与其他字段冗余

    第二个是类比的。

     Section to Segment mapping:
    
      null

  •  类似资料:
    • 问题内容: 只是好奇。显然,这对于实际编程而言不是一个很好的解决方案,但我想在Bless(十六进制编辑器)中创建一个可执行文件。 我的体系结构是x86。我可以制作一个非常简单的程序?你好,世界?无限循环?与此问题类似,但在Linux中。 问题答案: 如我的评论中所述,您实际上将为可执行文件编写自己的elf-header,以消除不需要的部分。仍然需要几个部分。Muppetlabs- TinyProg

    • 问题内容: 我需要一个适用于Linux的HEX编辑器,我的意思是: 快速 搜索/替换功能 不仅可以以十六进制显示数据,还可以以二进制,八进制等显示数据。 可以处理大型(> 1 gb)文件,而不会变慢且无响应(此要求很重要) (可选)具有一些比较/差异功能 你有什么建议? 问题答案: Bless 是高质量的全功能十六进制编辑器。 它是用mono / Gtk#编写的,其主要平台是GNU / Linux

    • 这只是纯粹的好奇。不...我不打算直接用二进制机器代码编写程序(我甚至不经常编写汇编代码,我只是大部分时间使用C/C++作为我最低级的工具)。我只是想看看是否有可能做到这一点,因为在计算机出现的早期,可能有人必须做到这一点。 附注:我知道关于这个话题有类似的问题,但没有一个提供一个可行的例子。我只想要一个简单的例子,这样它可以帮助我理解编译器和汇编器如何生成一个可执行文件。我是说...一定有人在过

    • 问题内容: 我爱得如此之多的两件事通常并不会引起我那么多的烦恼(除了我的孩子们)。我已经在工作中编写了一个Haskell程序,该程序使用诸如文本,xml- enumerator,attoparsec-text等库。我在Windows计算机,Ubuntu虚拟机(32位), Ubuntu桌面(再次为32位)和运行Ubuntu(64位)的EC2实例。 我们的客户端正在运行64位CentOS 5.3。我无

    • 我从下面的另一个问题中看到了一个代码,但我不知道如何利用它。

    • 我需要能够在如下文件中找到特定值(例如): 4D 54 68 64 00 00 00 06 00 01 00 01 00 C0 4D 54 72 6B 00 00 00 00 00 00 31 00 90 60 40 81 40 80 60 00 90 62 40 81 40 80 62 40 00 90 64 40 81 40 80 64 40 00 90 62 40 81 40 62 40 0