Bingolfing - WASM/GBA/7Zip in 584 Bytes.

Jun 23, 2021

10 mins read

Let’s build the smallest WASM / GBA ROM / 7Zip polyglot in 584 bytes for the Binary Golf Grand Prix 2021.

Rules:

  • The host file must be a binary executable.
    • This is any binary executable that stores either machine code (such as ELF, PE etc.) or bytecode (wasm, pyc, etc.)
  • Overlap with at least one additional file of any type to create a polyglot.
  • The host binary must return or print the number 2 when executed.

Entry:

  • Valid WebAssembly binary that calls host.print(2)
  • Gameboy Advance ROM that fills the screen with Green
  • 7Zip Archive containing a single empty file named “a”

Download: bggp.wasm (584 Bytes)

SHA256: 59c9a995f11e76ce5b996e62d5baeb4410a5f9492f9c6b205b7a9a5a8750434e

00000000: 0061 736d 0100 0000 0108 0260 017f 0060  .asm.......`...`
00000010: 0000 020e 0104 686f 7374 0570 7269 6e74  ......host.print
00000020: 0000 0302 0101 0503 0100 0107 6c01 6800  ............l.h.
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000090: 0000 0000 0000 0000 0108 0101 0a08 0106  ................
000000a0: 0041 0210 000b 0b9f 0302 0041 000b c802  .A.........A....
000000b0: 3031 9600 0000 0000 0000 0000 00f0 0000  01..............
000000c0: 1200 a0e3 00f0 29e1 2cd0 9fe5 1f00 a0e3  ......).,.......
000000d0: 00f0 29e1 24d0 9fe5 0103 a0e3 2010 9fe5  ..).$....... ...
000000e0: 0410 00e5 0110 a0e3 0812 80e5 1430 9fe5  .............0..
000000f0: 13ff 2fe1 1000 9fe5 10ff 2fe1 0000 0003  ../......./.....
00000100: 0004 0003 f400 0008 5401 0008 1001 0008  ........T.......
00000110: 0048 2de9 34c0 9fe5 ffe0 a0e3 0020 a0e3  .H-.4........ ..
00000120: 0100 a0e3 ffec 8ee3 b010 dce1 0e00 52e3  ..............R.
00000130: 0048 bd08 1eff 2f01 0e30 02e0 0120 82e2  .H..../..0... ..
00000140: 1003 11e1 1033 a011 b030 cc11 f6ff ffea  .....3...0......
00000150: 0202 0004 8800 9fe5 8810 9fe5 0020 a0e3  ............. ..
00000160: 0000 51e1 0100 000a 0420 80e4 fbff ffea  ..Q...... ......
00000170: 7400 9fe5 7410 9fe5 7420 9fe5 0000 52e1  t...t...t ....R.
00000180: 0200 000a 0430 91e4 0430 80e4 faff ffea  .....0...0......
00000190: 0103 a0e3 0310 a0e3 00c0 a0e3 3e2e a0e3  ............>...
000001a0: 0010 c0e5 4103 a0e3 0410 a0e3 0010 c0e5  ....A...........
000001b0: 0614 a0e3 f000 5ce3 1eff 2f01 0030 a0e3  ......\.../..0..
000001c0: 4b0b 53e3 0300 000a 0300 81e0 1e3e 83e2  K.S..........>..
000001d0: b020 c0e1 f9ff ffea 0210 81e2 01c0 8ce2  . ..............
000001e0: f3ff ffea 000c 0003 000c 0003 000c 0003  ................
000001f0: f801 0008 000c 0003 0041 000b 4bff 377a  .........A..K.7z
00000200: bcaf 271c 0004 e78d 3350 0000 0000 0000  ..'.....3P......
00000210: 0000 2a00 0000 0000 0000 5145 9b61 0105  ..*.......QE.a..
00000220: 010e 0180 0f01 8019 0200 0011 0500 6100  ..............a.
00000230: 0000 140a 0100 e672 35c7 7267 d701 1506  .......r5.rg....
00000240: 0100 2000 0000 0000                      .. .....

File Breakdown

0000000: 0061 736d                                 ; WASM_BINARY_MAGIC   
0000004: 0100 0000                                 ; WASM_BINARY_VERSION 

All WASM files must start with these 4 magic bytes to be considered valid. The version number (1) may change in the future, but I can find no examples where this is not the case.

; section "Type" (1)
0000008: 01                                        ; section code        
0000009: 00                                        ; section size (guess)
000000a: 02                                        ; num types
; func type 0
000000b: 60                                        ; func
000000c: 01                                        ; num params
000000d: 7f                                        ; i32
000000e: 00                                        ; num results

Defines a func used by the imported host.print for writing “2” to the console

; func type 1
000000f: 60                                        ; func       
0000010: 00                                        ; num params
0000011: 00                                        ; num results
0000009: 08                                        ; FIXUP section size

Defines a func with no parameters or return values to be used by “start” func on initialization

; section "Import" (2)
0000012: 02                                        ; section code
0000013: 00                                        ; section size (guess)
0000014: 01                                        ; num imports
; import header 0
0000015: 04                                        ; string length
0000016: 686f 7374                                host  ; import module name
000001a: 05                                        ; string length
000001b: 7072 696e 74                             print  ; import field name
0000020: 00                                        ; import kind
0000021: 00                                        ; import signature index
0000013: 0e                                        ; FIXUP section size

Defines the importing of external function from host running the WASM to call “host.print()”

; section "Function" (3)
0000022: 03                                        ; section code
0000023: 00                                        ; section size (guess)
0000024: 01                                        ; num functions
0000025: 01                                        ; function 0 signature index
0000023: 02                                        ; FIXUP section size

Defines a func that will have have have an index of 1

; section "Memory" (5)
0000026: 05                                        ; section code
0000027: 00                                        ; section size (guess)
0000028: 01                                        ; num memories
; memory 0
0000029: 00                                        ; limits: flags
000002a: 01                                        ; limits: initial (Allocate 64kb page)
0000027: 03                                        ; FIXUP section size

Defines allocation of a 64kb memory page for storing data into

; section "Export" (7)
000002b: 07                                        ; section code
000002c: 00                                        ; section size (guess)
000002d: 01                                        ; num exports
000002e: 68                                        ; string length
000002f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000003f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000004f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000005f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000006f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000007f: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000008f: 0000 0000 0000 0000                      ........  ; export name
0000097: 00                                        ; export kind
0000098: 01                                        ; export func index
000002c: 6c                                        ; FIXUP section size

Exports a section of code with a name of “\00\00\00…” for use of padding binary data to correct offsets without creating bytes that mess with the GBA ROM. Export names can be any valid UTF-8 string with no apparent length limitations.

; section "Start" (8)
0000099: 08                                        ; section code
000009a: 00                                        ; section size (guess)
000009b: 01                                        ; start func index
000009a: 01                                        ; FIXUP section size
; section "Code" (10)
000009c: 0a                                        ; section code
000009d: 00                                        ; section size (guess)
000009e: 01                                        ; num functions
; function body 0
000009f: 00                                        ; func body size (guess)
00000a0: 00                                        ; local decl count
00000a1: 41                                        ; i32.const
00000a2: 02                                        ; i32 literal
00000a3: 10                                        ; call
00000a4: 00                                        ; function index
00000a5: 0b                                        ; end
000009f: 06                                        ; FIXUP func body size
000009d: 08                                        ; FIXUP section size
; section "Data" (11)
00000a6: 0b                                        ; section code
00000a7: 00                                        ; section size (guess)
00000a8: 02                                        ; num data segments

Defines the logic of our func at index 1 that creates a i32.const of value 2 and calls the function of host.print

; data segment header 0
00000a9: 00                                        ; segment flags
00000aa: 41                                        ; i32.const
00000ab: 00                                        ; i32 literal
00000ac: 0b                                        ; end
00000ad: c802                                      ; data segment size
; data segment data 0
00000af: 3031 9600 0000 0000 0000 0000 00f0 0000
00000bf: 1200 a0e3 00f0 29e1 2cd0 9fe5 1f00 a0e3
00000cf: 00f0 29e1 24d0 9fe5 0103 a0e3 2010 9fe5
00000df: 0410 00e5 0110 a0e3 0812 80e5 1430 9fe5
00000ef: 13ff 2fe1 1000 9fe5 10ff 2fe1 0000 0003
00000ff: 0004 0003 f400 0008 5401 0008 1001 0008
000010f: 0048 2de9 34c0 9fe5 ffe0 a0e3 0020 a0e3
000011f: 0100 a0e3 ffec 8ee3 b010 dce1 0e00 52e3 
000012f: 0048 bd08 1eff 2f01 0e30 02e0 0120 82e2
000013f: 1003 11e1 1033 a011 b030 cc11 f6ff ffea
000014f: 0202 0004 8800 9fe5 8810 9fe5 0020 a0e3
000015f: 0000 51e1 0100 000a 0420 80e4 fbff ffea
000016f: 7400 9fe5 7410 9fe5 7420 9fe5 0000 52e1
000017f: 0200 000a 0430 91e4 0430 80e4 faff ffea
000018f: 0103 a0e3 0310 a0e3 00c0 a0e3 3e2e a0e3
000019f: 0010 c0e5 4103 a0e3 0410 a0e3 0010 c0e5
00001af: 0614 a0e3 f000 5ce3 1eff 2f01 0030 a0e3
00001bf: 4b0b 53e3 0300 000a 0300 81e0 1e3e 83e2
00001cf: b020 c0e1 f9ff ffea 0210 81e2 01c0 8ce2
00001df: f3ff ffea 000c 0003 000c 0003 000c 0003
00001ef: f801 0008 000c 0003                       ; data segment data

Defines a data segment that is alligned to offset B0 containing the GBA ROM

; data segment header 1
00001f7: 00                                        ; segment flags
00001f8: 41                                        ; i32.const
00001f9: 00                                        ; i32 literal
00001fa: 0b                                        ; end
00001fb: 4b                                        ; data segment size
; data segment data 1
00001fc: ff37 7abc af27 1c00 04e7 8d33 5000 0000
000020c: 0000 0000 002a 0000 0000 0000 0051 459b
000021c: 6101 0501 0e01 800f 0180 1902 0000 1105
000022c: 0061 0000 0014 0a01 00e6 7235 c772 67d7 
000023c: 0115 0601 0020 0000 0000 00               ; data segment data

Defines a data segment containing a 7-Zip Archive prepended with “\FF” to prevent the GBA ROM from eagerly parsing into the archive and crashing the game. Also prevents some weirdness with GBA ROM and whatever value it reads from offset A7

; move data: [a8, 247) -> [a9, 248)
00000a7: 9f03                                      ; FIXUP section size

Condensed WebAssembly Text format (WAT)

Binary represented as a WAT

(module
;;imports must occur before all non-import definitions
(import "host" "print"(func $log (param i32)))
;; Define a single page memory of 64KB.
(memory $0 1)
;;Use function export name to pad GBA game to offset(h) B0
(export "\00\00\00\00..." (func $r))
;;GBA 504 bytes as embedded data section
(data (i32.const 0) "\30\31\96\00...")
;;Store string in 64kb allocated block of memory
;;Prepend 7Zip file with FF so that value at compiled offset A7
;; is not a valid GBA opcode that allows skipping header
(data (i32.const 0) "\FF\37\7A\BC...")
;;Function that returns i32 0x2
(func $r
    i32.const 2
    ;;Call imported console log
    call $log
)
;;Default WASM Instantiation
(start $r)
)

But does it run?

WASM

Using WABT: The WebAssembly Binary Toolkit

> npx -p wabt wasm-interp out/main.wasm --host-print
called host host.print(i32:2) =>

Success! host.print() is called with an int32 value of 2

GBA

Using the emulator Visual Boy Advance - M v2.1.4

Rename file to have .gba file extension and open using File–>Open Green screen displayed on GBA

Success! The tiny GBA ROM I wrote to draw a green screen runs!

7-Zip

Right click the WASM file and Open as Archive using 7-Zip GUI

7zip Archive

Success! The empty file named “a” is properly recognized in the 7-Zip Archive

Summary

I had a lot of fun and intend to try to fit another file polyglot at 2f where it’s currently padded with “\00\00\00…”

This section should technically allow for any UTF-8 characters. When attempting to add anything else in this section I have to be mindful that those characters may also be GBA opcodes and break something else.

If nothing else, I’d like to try to cram some ASCII art in there before submitting.

Hope you enjoyed the read.

-remy

Sharing is caring!