013. Linux a.out quine in 44 bytes

Sun, 17 Mar 2024 04:57:16 +0100

Susam Pal's Self-Printing Machine Code (on mastussy) demonstrates a 12-byte dos program copying its image to the standard output. This platform lends itself well to the problem, since executables are allowed to be bare code and copied to a fixed offset, naturally advantaging it over real systems like unix. Nevertheless, the unix a.out header is only 32 bytes and the entire image is similarly mapped at a well-known location; too bad Linux doesn't support a.out.

But Debian GNU/Linux 0.93 will be the last release that uses the a.out binary format., and 0.93R6 is, obviously, readily available to install. A musty 1995 vintage, for which you definitely want to consult the installation guide, but 1995 debian-installer is irrevocably and unfalteringly debian-installer. There are, of course, some fundamental issues with running a 30-year-old system:

overall qemu-system-i386 -m 32m -fda boot.bin -hda rootfs -boot a -fdb debs.tar works, and the most annoying limitation is that the floppy will be truncated at 2.8M, so you can't tar up every deb at once; not that it really matters because strace/gdb/binutils/gcc are the only relevant ones not on the installation floppies.

This draws heavily from the aforelinked BGGP4 submission, where I had been robbed of my winning submissions for both the binary and shell program categories. Nevertheless, this allows for more interesting packing; given the annotated program:

(build with cut -sf2 < prog | tr a-f A-F | tr -d '[:space:]' | base16 -d)
0	cc 00 64 00	; a_midmag
4	0C 00 00 00	; a_text
8	00 00 00 00	; a_data
12	b0 04      	; a_bss eax=4	mov    al,0x4	number=__NR_write
14	EB 10      	;            	jmp    +16
18	00 00 00 00	; a_syms
22	0C 10 00 00	; a_entry
26	00 00 00 00	; a_trsize
30	00 00 00 00	; a_drsize
32	31 db      	; ebx=0     	xor    ebx,ebx	fd    =0
34	31 c9      	;           	xor    ecx,ecx
36	b5 10      	; ecx=0x1000	mov    ch,0x10	buf   =loadaddr
38	31 d2      	;           	xor    edx,edx
40	b2 2C      	; edx=44    	mov    dl,44	count =progsize
42	cd 80      	; syscall() 	int    0x80
44

note that a_midmag (the magic), a_text (the size of the program text immediately following the header; Linux has started requiring this (and, thus, the file size) to be page-aligned by 2.6), and a_data (the size of program data, following the text) are fixed parameters while a_trsize and a_drsize must be 0; a_bss can be anything, since it describes the initial allocation to map after the program data, while a_syms can, empirically, sometimes have a non-zero low byte.

While COMs are loaded at 0x100 and a.outs are loaded at 0x1000, the start address for COMs is also 0x100, while for a.outs it's a_entry. "Real" a.outs in the distribution include the libc runtime entry point at start of the program text, and thus a_entry is unwaveringly 0x1020 (0x1000 + the 32 header bytes); except in this case it's a_bss, into which the first instruxion is packed.

While Susam's program amounts to, effectively, a "write a buffer, then return" implementation (down to exiting with ret) in 12 bytes, this is an exercise in zeroing registers, since it amounts to

	write(0, 0x1000, 44);

in 44 bytes – conveniently an even 32 more, matching the header size. The astute reader will've noted that it actually executes 16 bytes to do so, but if all registers were clear on entry this would've been just 10.

There is no explicit exit, since the 270M of zeroes (allocated by a_bss=0x10eb04b0) decode to add byte [eax], al which instantly segfaults, and, if you don't disable coredumps, fills your disk.

Debian GNU/Linux 0.93R6
Copyright (C) 1993, 1994, 1995 Debian Association, Inc. and others
0.93R6 login: nab
Password:
Last login: Sat Mar 16 21:15:48 on tty1
Linux 0.93R6 1.2.13 #2 Wed Oct 4 17:35:14 EST 1995 i686
Copyright (C) 1993, 1994, 1995 Debian Association, Inc. and others

Everything included with the Debian GNU/Linux system is freely
redistributable; the exact distribution terms for each program
are described in the individual files in /usr/doc/copyright/.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
$ ulimit -c 0
$ ./newd3
�d
  ��
    1�1ɵ1Ҳ,̀Segmentation fault
$ ./newd3 0>&1 | od -tx1 -Ad
0000000 cc 00 64 00 0c 00 00 00 00 00 00 00 b0 04 eb 10
0000016 00 00 00 00 0c 10 00 00 00 00 00 00 00 00 00 00
0000032 31 db 31 c9 b5 10 31 d2 b2 2c cd 80
0000044
Segmentation fault
$ < newd3 od -tx1 -Ad
0000000 cc 00 64 00 0c 00 00 00 00 00 00 00 b0 04 eb 10
0000016 00 00 00 00 0c 10 00 00 00 00 00 00 00 00 00 00
0000032 31 db 31 c9 b5 10 31 d2 b2 2c cd 80
0000044


Nit-pick? Correction? Improvement? Annoying? Cute? Anything? Mail, post, or open!


Creative text licensed under CC-BY-SA 4.0, code licensed under The MIT License.
This page is open-source, you can find it at GitHub, and contribute and/or yell at me there.
Like what you see? Consider giving me a follow over at social medias listed here, or maybe even a sending a buck liberapay donate or two patreon my way if my software helped you in some significant way?
Compiled with Clang 19's C preprocessor on 18.09.2025 16:46:34 UTC from src/blogn_t/013-linux-a.out-quine.html.pp.
See job on builds.sr.ht.
RSS feed