Linux

From SizeCoding
Revision as of 07:02, 8 August 2021 by Superogue (talk | contribs)

Jump to: navigation, search

Introduction

For X86 related information, please check the main pages on this website, as a lot of the same tricks will also work with X86 linux sizecoding. This page goes into the specifics of getting actual small binaries on linux using assembler.

While there have been attempts in getting tiny intros to work using self-compilation tricks (using gcc or python hacks), development of actual tiny ELF executables on linux is still in its early days.

So a huge thanks goes out to byteobserver as well as some early work by frag/fsqrt for all their research and hard work in producing tiny ELF binaries for linux.

Linux system

This section of the sizecoding.org wiki targets 32-bit X86 based Linux binaries (ELF format).

Setting up

Setting up your development platform for Linux development:

  • Suggested Distributions : Any X86-based Linux distribution that allows for execution of 32-bit executables.
  • Assembler: NASM (or any other linux compatible 32-bit X86 assembler)

Furthermore, it is important that the user has access to the dev/fbo framebuffer. This can be achieved by launching a virtual (fullscreen) console using CTRL-F3/F4 in most distributions, login and making sure the user has access to the video group. If this is not the case for some reason, you can add your user to the videogroup like so:

sudo usermod -a -G video username

Note: Make sure your binary is executable for everyone using the chmod 777 command after compilation :D

System Calls

Interaction with the Linux OS is mostly done via int 0x80 system calls. This usually includes dealing with opening files/framebuffer/audio and handling timers.

A full list of system calls and their expected register arguments is available at: https://syscalls32.paolostivanin.com/


ELF Header Information

Like a 32-bit windows executable, a 32-bit binary for linux comes with a pretty hefty ELF header.

  org     0x00010000  
  ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1, 0         ;   e_ident
        times 8 db      0
                dw      2                               ;   e_type
                dw      3                               ;   e_machine
                dd      1                               ;   e_version
                dd      _start                          ;   e_entry
                dd      phdr - $$                       ;   e_phoff
                dd      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
  
  
  phdr:                                                 ; Elf32_Phdr
                dd      1                               ;   p_type
                dd      0                               ;   p_offset
                dd      $$                              ;   p_vaddr
                dd      $$                              ;   p_paddr
                dd      filesize                        ;   p_filesz
                dd      filesize                        ;   p_memsz
                dd      5                               ;   p_flags
                dd      0x1000                          ;   p_align
  
  
  _start:
  
  ; your program here

Luckily some parts of the ELF header can be repurposed and used to store some data and code. There is quite an extensive journey about some header optimisations available at http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html for those that are interested.

After merging the ehr and phr parts and changing your entry point, we can get the header down to about the 30 bytes range with a nifty /dev/fb0 string inserted which we'll be able to use later for setting up the framebuffer.

org $00010000
    db $7F,"ELF"    ; e_ident
    dd 1            ; p_type
    dd 0            ; p_offset
    dd $$           ; p_vaddr
    dw 2            ; e_type, p_paddr
    dw 3            ; e_machine
    dd entry        ; e_version, p_filesz
    dd entry        ; e_entry, p_memsz
    dd 4            ; e_phoff, p_flags
fname:
    db "/dev/fb0",0 ; e_shoff, p_align, e_flags, e_ehsize
entry:
    ; this next instruction overlaps with a critical part of the elf header
    ; it needs to look like XX YY YY YY YY where YYYYYYYY=fname
    ; so you can change the register to something else or use push
    ; but the four byte pointer to fname cannot be changed.
    mov ebx,fname   ; e_phentsize, e_phnum

    ; e_shentsize, e_shnum, e_shstrndx are below but we can put whatever code/bytes we want there
    mov cl,1 ; set read/write mode (1 or inc ecx is sufficient for pcopy method, read/write (3) is needed for mmap)
    mov al,5 ; 5 = open syscall
    int 0x80 ; open /dev/fb0 = 3

Accessing video

Accessing video

Getting something on screen

To be added soon.


Example framework

So when we put all the above together, we can get a minimal kind of framework running that will look something like this munching square example provided to us by byteobserver:

; byte.observer's munching square linux example
; assembles with nasm -fbin munch.asm -o munch
width equ 1024
height equ 768

bits 32
org $00010000
    db $7F,"ELF" ; e_ident
    dd 1         ; p_type
    dd 0         ; p_offset
    dd $$        ; p_vaddr
    dw 2         ; e_type, p_paddr
    dw 3         ; e_machine
    dd entry     ; e_version, p_filesz
    dd entry     ; e_entry, p_memsz
    dd 4         ; e_phoff, p_flags
fname:
    db "/dev/fb0",0 ; e_shoff, p_align, e_flags, e_ehsize
entry:
    mov ebx,fname     ; e_phentsize, e_phnum
    inc ecx           ; = 1 = O_WRONLY
    mov al,5          ; 5 = open syscall
    int 0x80          ; open /dev/fb0 = 3

    mov ebp,width*height*4  ; ebp = screen size
    sub esp,ebp             ; make room on the stack for the video memory

mainloop:
    mov ecx,ebp    ; init pixel index
    shr ecx,2      ; divide by bits per pixel
    inc edi        ; frame counter

setpixels:
    mov ebx,width
    mov eax,ecx
    cdq
    div ebx               ; edx = x-coord , eax=y coord
    xor eax,edx           ; xor pattern
    add eax,edi           ; make it munch
    mov [esp+ecx*4+0],al ; b
    mov [esp+ecx*4+1],al ; g
    mov [esp+ecx*4+2],al ; r
    mov [esp+ecx*4+3],al ; a
    loop setpixels

    ; dump the whole thing to the screen using pwrite64 syscall
    mov ecx,esp  ; buffer ptr
    mov edx,ebp  ; screen size
    push edi     ; save frame counter
    xor esi,esi  ; seek to beginning of screen
    xor edi,edi  
    mov ebx,3    ; fd of framebuffer
    mov eax,0xb5 ; pwrite64
    int 0x80     ; pwrite64 to framebuffer
    pop edi

    jmp mainloop

Adding Sound

It is possible to output digital audio by binding the the aplay command into your intro. APLAY is available on most of the Linux distributions and can be tested by running:

Adding Sound

It is possible to output digital audio by binding the the aplay command into your intro. APLAY is available on most of the Linux distributions and can be tested by running:

   $ aplay -c8 /dev/urandom


Make some noise

To be added soon.

Additional Resources