【问题标题】:Smallest executable program (x86-64)最小的可执行程序 (x86-64)
【发布时间】:2019-04-22 06:46:52
【问题描述】:

我最近遇到this post 描述了最小的 ELF 可执行文件,但是这篇文章是为 32 位编写的,我无法在我的机器上编译最终版本。这让我想到了一个问题:可以编写的最小的 x86-64 ELF 可执行文件是什么?

【问题讨论】:

  • 你有什么机器?适用于 Linux 的 Windows 子系统(根本不支持 32 位可执行文件)?还是一个没有 IA-32 兼容的合适的 Linux 内核?你的意思是你甚至无法编译最终版本?当然你有一个二进制文件,但不能运行它? (无论如何,我知道您的问题与此无关,但如果您甚至无法编译 32 位版本,您可能无法使用 NASM 的平面二进制输出来创建带有代码的 64 位可执行文件也打包到 ELF 标头中。)
  • 能否在 64 位可执行文件中使用 32 位 int 0x80 系统调用?如果是这样,你可能不需要改变太多。我知道 ELF 标头字段有一些重叠被解释为机器代码的一部分,因此 ELF64 可能需要进行一些更改。
  • 对于 64 位模式,您基本上需要重新创建整个程序,因为机器代码和 ELF 标头的布局都有很大的不同。虽然这对于有经验的程序员来说是一个很好的练习,但我不确定您是否会在本网站的范围内得到您的问题的答案。
  • 我投票决定将此问题作为题外话结束,因为代码高尔夫问题在 StackOverflow 上是题外话。

标签: assembly x86-64 elf


【解决方案1】:

from an answer of mine 开始,关于 Linux 上 ELF 可执行文件的“真实”入口点和“原始”系统调用,我们可以将其剥离为

bits 64
global _start
_start:
   mov di,42        ; only the low byte of the exit code is kept,
                    ; so we can use di instead of the full edi/rdi
   xor eax,eax
   mov al,60        ; shorter than mov eax,60
   syscall          ; perform the syscall

我不认为你可以在不超出规格的情况下让它变得更小 - 特别是,psABI 不能保证eax 的状态。这被组装成精确的 10 个字节(而不是 32 位有效负载的 7 个字节):

66 bf 2a 00 31 c0 b0 3c 0f 05

直接的方式(用nasm 组装,用ld 链接)生成一个352 字节的可执行文件。

他所做的第一个“真正”转变是“手工”构建 ELF;这样做(有一些修改,因为 x86_64 的 ELF 标头有点大)

bits 64
            org 0x08048000

ehdr:                                           ; Elf64_Ehdr
            db  0x7F, "ELF", 2, 1, 1, 0         ;   e_ident
    times 8 db  0
            dw  2                               ;   e_type
            dw  62                              ;   e_machine
            dd  1                               ;   e_version
            dq  _start                          ;   e_entry
            dq  phdr - $$                       ;   e_phoff
            dq  0                               ;   e_shoff
            dd  0                               ;   e_flags
            dw  ehdrsize                        ;   e_ehsize
            dw  phdrsize                        ;   e_phentsize
            dw  1                               ;   e_phnum
            dw  0                               ;   e_shentsize
            dw  0                               ;   e_shnum
            dw  0                               ;   e_shstrndx

ehdrsize    equ $ - ehdr

phdr:                                           ; Elf64_Phdr
            dd  1                               ;   p_type
            dd  5                               ;   p_flags
            dq  0                               ;   p_offset
            dq  $$                              ;   p_vaddr
            dq  $$                              ;   p_paddr
            dq  filesize                        ;   p_filesz
            dq  filesize                        ;   p_memsz
            dq  0x1000                          ;   p_align

phdrsize    equ     $ - phdr

_start:
   mov di,42        ; only the low byte of the exit code is kept,
                    ; so we can use di instead of the full edi/rdi
   xor eax,eax
   mov al,60        ; shorter than mov eax,60
   syscall          ; perform the syscall

filesize      equ     $ - $$

我们减少到 130 个字节。这比 91 字节的可执行文件大一点,但它来自于几个字段变成 64 位而不是 32 位的事实。


然后我们可以应用一些类似于他的技巧; phdrehdr 的部分重叠可以做到,尽管phdr 中的字段顺序不同,我们必须将p_flagse_shnum 重叠(但是由于e_shentsize 应该忽略它为 0)。

在标头内移动代码稍微困难一些,因为它大了 3 个字节,但标头的那部分与 32 位的情况一样大。我们通过提前 2 个字节开始,覆盖填充字节(ok)和 ABI 版本字段(不好,但仍然有效)来克服这个问题。

所以,我们到达:

bits 64
            org 0x08048000

ehdr:                                           ; Elf64_Ehdr
            db  0x7F, "ELF", 2, 1,              ;   e_ident
_start:
            mov di,42        ; only the low byte of the exit code is kept,
                            ; so we can use di instead of the full edi/rdi
            xor eax,eax
            mov al,60        ; shorter than mov eax,60
            syscall          ; perform the syscall
            dw  2                               ;   e_type
            dw  62                              ;   e_machine
            dd  1                               ;   e_version
            dq  _start                          ;   e_entry
            dq  phdr - $$                       ;   e_phoff
            dq  0                               ;   e_shoff
            dd  0                               ;   e_flags
            dw  ehdrsize                        ;   e_ehsize
            dw  phdrsize                        ;   e_phentsize
phdr:                                           ; Elf64_Phdr
            dw  1                               ;   e_phnum         p_type
            dw  0                               ;   e_shentsize
            dw  5                               ;   e_shnum         p_flags
            dw  0                               ;   e_shstrndx
ehdrsize    equ $ - ehdr
            dq  0                               ;   p_offset
            dq  $$                              ;   p_vaddr
            dq  $$                              ;   p_paddr
            dq  filesize                        ;   p_filesz
            dq  filesize                        ;   p_memsz
            dq  0x1000                          ;   p_align

phdrsize    equ     $ - phdr
filesize    equ     $ - $$

112 字节长。

我暂时停在这里,因为我现在没有太多时间。您现在已经有了 64 位相关修改的基本布局,因此您只需要尝试更大胆的重叠

【讨论】:

  • 如果您正在为代码大小打高尔夫球,但您仍想像普通人一样使用_exit(42) 而不是xor edi,edi,您可以使用push 42/pop rdi(3 个字节)而不是 4 字节的 66 mov-di imm16。然后是一个 3 字节的 lea eax, [rdi - 42 + 60] 或另一个 push/pop。 Tips for golfing in x86/x64 machine code。当然,在实践中,Linux 在进程启动之前会将所有寄存器归零。根据您的高尔夫规则,您可能会利用。 (codegolf.SE 只要求代码至少在一种实现上工作,不一定是全部。)
  • 只设置低字节,另一个选项是mov al,42(2字节)/xchg eax,edi(1字节)。
  • @PeterCordes: 啊,通常的push/pop 把戏,我一直忘记它......可能是因为我通常在 16 位 x86 上打高尔夫球,它们没有那么有用(除了对于段寄存器)。 _exit(42) 与原件相匹配,否则我只会让它退出 rdi :-D 中发生的任何事情。不幸的是,由于这不是“常规”的代码高尔夫,因此没有真正定义明确的规则......
  • 我和use64; xor edi, edi; mov al, 42; xchg eax, edi; mov al, 60; syscall一起是9字节?
  • @mercury0114:代码本身是 12 个字节,其余的是各种标题、符号表、其他标准可执行部分的定义等等。使用nasm -felf 组装您的代码并将其与ld -m elf_i386 链接我得到484 个字节,在生成的二进制文件上执行strip -s 会下降到248(您可以在使用objdump -x -D 之前/之后了解内容)。
猜你喜欢
  • 2012-03-11
  • 2013-09-17
  • 2014-08-07
  • 2018-09-25
  • 2015-02-20
  • 2014-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多