下面这个32位的x86 Linux程序可以打印任意长度的字符串(只要程序够长)。执行完后会调用
如果愿意牺牲位置独立性(而是强制链接器插入字符串地址),并且不关心程序返回零,那么可以将其简化为:
这两个程序都可以通过
一直困扰着我的问题是,是否可能再挤压几个字节?我自己的猜测有以下几种可能:
exit(0)
函数:.global _start ; notice on entry here, all regs but %esp are zero
_start:
call .L0 ; offset == strlen, provided by your assembler
.byte 'H','e','l','l','o',',',' ','W','o','r','l','d'
.L0:
pop %ecx ; ret addr is starting addr of string
mov -4(%ecx),%edx ; argument to `call`, 4 bytes: strlen
inc %ebx ; stdout == 1
movb $4, %al ; SYS_write == 4
int $0x80
xchg %eax,%ebp ; %ebp is still zero
xchg %eax,%ebx ; SYS_exit == 1, return value == 0
int $0x80
如果愿意牺牲位置独立性(而是强制链接器插入字符串地址),并且不关心程序返回零,那么可以将其简化为:
.global _start
_start:
movb $4, %al
inc %ebx
mov $.L0, %ecx ; this address is calculated when linking
movb $.Lend-.L0, %dl ; strlen, calculated by assembler
int $0x80
xchg %eax,%ebx
int %0x80
.L0:
.byte 'H','e','l','l','o',',',' ','W','o','r','l','d'
.Lend:
这两个程序都可以通过
as --32 -o x.o x.S; ld -s -m elf_i386 x.o
组装/链接,并正常运行。第二个程序只有26字节的代码,如果在打印 Hello, World
后允许崩溃,则可以省略最后两条指令,代码长度为23字节,已经是极限了。一直困扰着我的问题是,是否可能再挤压几个字节?我自己的猜测有以下几种可能:
- 以某种方式使用“Hello,World”本身的部分作为代码?
- 有人知道可用的系统调用彩蛋吗?
- 欺骗链接器使入口点成为16位地址,以便使用
movw $.L0, %cx
(节省一个字节)? - 进行8位偏移
jmp
到一个已知的位置(或通过汇编/链接器调用奇技淫巧创建)包含exit(...)
系统调用所必需的指令,比xchg; int
序列多节省一个字节?
编辑
澄清一下,问题不在于最小化ELF可执行文件的大小;那方面的技巧是众所周知的。我的问题明确地询问了一个Linux 32位x86汇编程序的大小,该程序执行了相当于以下代码的编译后代码的操作:int main(int argc, char **argv)
{
puts("Hello, World");
exit(0); /* or whatever code */
}
我会很高兴如果有任何不需要手动编辑ELF头的方法。如果您找到一种方法,例如将"Hello, World"嵌入到某个ELF对象中,并从汇编源代码中引用它,仅使用汇编器/链接器命令行和/或mapfile输入,我认为这足够有效,即使这会增加ELF可执行文件的大小。我只想知道打印"Hello, World"并在之后调用exit()
的指令序列是否可以缩小。
问题是关于代码大小,而不是可执行文件大小。
.ascii
指令来嵌入字符串吗?如果你想要一个终止的空字节,可以尝试使用.asciiz
。 - fuz