; fasmg hello.asm hello && chmod 755 hello && ./hello; echo $? ; ; fasmg quine.asm quine && ./quine > quine2; echo "exit code:" $?; echo; hexdump -C quine; echo; hexdump -C quine2; cmp quine quine2; echo; echo "compare:" $? ; ZydisDisasm -64 quine macro rex.0 db 0x40 end macro macro rex.w db 0x48 end macro macro rex.xb db 0x43 end macro macro modrm mod, reg, rm assert mod >= 0 & mod < 4 assert reg >= 0 & reg < 8 assert rm >= 0 & rm < 8 db (mod shl 6) or (reg shl 3) or rm end macro macro sib scale, index, base assert scale >= 0 & scale < 4 assert index >= 0 & index < 8 assert base >= 0 & index < 8 db (scale shl 6) or (index shl 3) or base end macro macro mov.b target, source match =rax?, target db 0xB8 dd source else match =rdi?, target db 0xBF dd source else assert 0 end match end macro macro qwordreg result, register match =rax?, register result = 0 else match =rcx?, regiser result = 1 else match =rdx?, register result = 2 else match =rbx?, register result = 3 else match =rsp?, register result = 4 else match =rbp?, register result = 5 else match =rsi?, register result = 6 else match =rdi?, register result = 7 else assert 0 end match end macro macro mov.d.dimm target, source rex.w db 0xC7 qwordreg reg, target modrm 3, 0, reg dd source end macro macro mov.q target, source match =rax, target rex.w db 0xB8 dq source else match =rdi, target rex.w db 0xBF dq source else match =rsi, target match =rsp, source rex.w db 0x89 modrm 3, 4, 6 else rex.w db 0xBE dq source end match else match =rdx, target rex.w db 0xBA dq source else assert 0 end match end macro macro add.b target, source match =rax, target rex.w db 0x83 modrm 3, 0, 0 db source else assert 0 end match end macro macro add.q target, source db 0x01 qwordreg treg, target qwordreg sreg, source modrm 3, sreg, treg end macro macro sub.b target, source match =rsp, target rex.w db 0x83 modrm 3, 5, 4 db source else assert 0 end match end macro ; Move from an 8-bit immediate value, to a location relative to a 64-bit ; register, with an 8-bit displacement and no indexing. ; ; This uses opcode 0xC6, which has w = 0. Since we run in 64-bit mode, that ; makes the operand size 8 bits, regardless of the current operand-size ; attribute. [Intel] volume 2D, section B.1.43, table B-6. macro mov.rel.b target, offset, source match =rsp, target db 0xC6 modrm 1, 0, 4 sib 0, 0, 4 db offset db source else assert 0 end match end macro ; Move from a 16-bit immediate value, to a location relative to a 64-bit ; register, with an 8-bit displacement and no indexing. ; ; This uses opcode 0xC7, which has w = 1. We run in 64-bit mode, so that gives ; us an operand size of 32 bits by default. [Intel] volume 1, section 3.6.1, ; table 3-4. We want a 16-bit operand, so we use the operand-size prefix, ; 0x66, and we leave REX.W unset. macro mov.rel.w target, offset, source match =rsp, target db 0x66 db 0xC7 modrm 1, 0, 4 sib 0, 4, 4 db offset dw source else assert 0 end match end macro ; Move from a 32-bit immediate value, to a location relative to a 64-bit ; register, with an 8-bit displacement and no indexing. ; ; This uses opcode 0x67, which has w = 1. We run in 64-bit mode, so that gives ; us an operand size of 32 by default. [Intel] volume 2D, section B.1.43, ; table B-6. This is what we want, so we leave it. macro mov.rel.d target, offset, source match =rsp, target db 0xC7 modrm 1, 0, 4 sib 0, 4, 4 db offset dd source else assert 0 end match end macro ; Move from a 64-bit register, to a 64-bit location relative to a 64-bit ; register, with an 8-bit displacement and no indexing. ; ; This uses opcode 0x89. macro mov.rel.q target, offset, source match =rsp, target match =rax, source rex.w db 0x89 modrm 1, 0, 4 sib 0, 4, 4 db offset else match =rdx, source ;mov.rel.q rsp, 0x60, rdx ; size in file ; todo doesn't work yet rex.w db 0x89 ;0x44 0100 0100 ; 01 000 100 ;0x54 0101 0100 ; 01 010 100 modrm 1, 2, 4 ; <- changing this reg value should do it, but... sib 0, 4, 4 db offset else assert 0 end match else assert 0 end match end macro ; Move from a 32-bit immediate value, to a 64-bit location relative to a ; 64-bit register, with an 8-bit displacement and no indexing. ; ; Note that there is no instruction to move a 64-bit immediate to memory. ; ; This uses opcode 0xC7, which has w = 1. We run in 64-bit mode, so that ; gives us an operand size of 32 by default. [Intel] volume 2D, ; section B.1.43, table B-6. We want a 64-bit operand, so we use the REX.W ; prefix, 0x48. macro mov.rel.q.d target, offset, source match =rsp, target rex.w db 0xC7 modrm 1, 0, 4 sib 0, 4, 4 db offset dd source else assert 0 end match end macro macro syscall db 0x0F, 0x05 ; 0f two-byte escape ; 05 syscall ^ o64 end macro org 0x08000000 elf_header: ; * denotes mandatory fields according to breadbox db 0x7F, "ELF" ; *magic number db 2 ; 64-bit db 1 ; little-endian db 1 ; ELF header format version 1 db 0 ; System-V ABI db 8 dup 0 ; (padding) dw 2 ; *executable dw 0x3E ; *Intel x86-64 dd 1 ; ELF format version dq _start ; *entry point dq program_header - $$ ; *program header offset dq 0 ; section header offset dd 0 ; processor flags dw elf_header_size dw program_header_entry_size ; * dw 1 ; *number of program header entries dw 0 ; section header entry size dw 0 ; number of section header entries dw 0 ; section name string table index elf_header_size = $ - elf_header program_header: dd 1 ; *"loadable" segment type dd 0x05 ; *read+execute permission dq 0 ; *offset in file dq $$ ; *virtual address ; required, but can be anything, subject to ; alignment dq 0 ; physical address (ignored) dq file_size ; *size in file dq file_size ; *size in memory dq 0 ; segment alignment ; for relocation - will we be ASLR'd? program_header_entry_size = $ - program_header load_origin = 0x08000000 _start: mov.d.dimm rdx, 0 ; store running file size here sub.b rsp, 0xFF ; reserve stack space ; ELF header mov.rel.d rsp, 0x00, 0x7F bappend "ELF" ; magic number mov.rel.b rsp, 0x04, 2 ; 64-bit mov.rel.b rsp, 0x05, 1 ; little-endian mov.rel.b rsp, 0x06, 1 ; ELF header format version 1 mov.rel.b rsp, 0x07, 0 ; System-V ABI mov.rel.q.d rsp, 0x08, 0 ; (padding) mov.rel.w rsp, 0x10, 2 ; executable mov.rel.w rsp, 0x12, 0x3E ; Intel x86-64 mov.rel.d rsp, 0x14, 1 ; ELF format version ; Compute the entry pointer. mov.q rax, load_origin add.b rax, 120 mov.rel.q rsp, 0x18, rax ; entry point mov.rel.q.d rsp, 0x20, 64 ; program header offset ; We place the program header immediately after the ELF header. This ; offset is from the start of the file. mov.rel.q.d rsp, 0x28, 0 ; section header offset mov.rel.d rsp, 0x30, 0 ; processor flags mov.rel.w rsp, 0x34, 64 ; ELF header size mov.rel.w rsp, 0x36, 56 ; program header entry size mov.rel.w rsp, 0x38, 1 ; number of program header entries mov.rel.w rsp, 0x3a, 0 ; section header entry size mov.rel.w rsp, 0x3c, 0 ; number of section header entries mov.rel.w rsp, 0x3e, 0 ; section name string table index ; Program header mov.rel.d rsp, 0x40, 1 ; "loadable" segment type mov.rel.d rsp, 0x44, 0x05 ; read+execute permission mov.rel.q.d rsp, 0x48, 0 ; offset in file mov.rel.q.d rsp, 0x50, load_origin ; virtual address ; required, but can be anything, subject to alignment mov.rel.q.d rsp, 0x58, 0 ; physical address (ignored) ; Fill in 0 as the file size for now, to avoid unitialized memory. mov.rel.q.d rsp, 0x60, 0 ; size in file mov.rel.q.d rsp, 0x68, 0 ; size in memory mov.rel.q.d rsp, 0x70, 0 ; segment alignment ; for relocation - will we be ASLR'd? ; Add the size of the ELF header to the running total mov.d.dimm rax, 0x78 add.q rdx, rax ; Go back and fill in the file size now that we know it. ; so notionally, let's pretend rdx is a variable that keeps track of file ; size. we would do something to populate it, then we would add it to rax... mov.q rax, 0x185 add.q rdx, rax ; TODO of course, really we want to for-real track these mov.rel.q rsp, 0x60, rdx ; size in file mov.rel.q rsp, 0x68, rdx ; size in memory ; TODO it works for rax source but not yet for rdx ;;; after the file size is populated, that's the entire ELF header. yay! ; write() from stack-allocated buffer mov.b rax,1 mov.q rdi, 1 mov.q rsi, rsp mov.q rdx, 0x78 syscall ; write() hardcoded header mov.b rax, 1 mov.q rdi, 1 mov.q rsi, elf_header + 0x78 mov.q rdx, file_size - 0x78 syscall ; write() greeting mov.b rax, 1 mov.q rdi, 2 mov.q rsi, greeting mov.q rdx, greeting_size syscall ; exit() mov.b rax, 60 mov.b rdi, 0 syscall greeting: db "Hello, Irenes!", 0x0A greeting_size = $ - greeting file_size = $ - $$