├── .gitignore ├── bin ├── writable-text ├── execute.sh └── writable-text.c ├── basic-printf └── printf.asm ├── factorial ├── factorial.asm └── factorial-mod.asm ├── hello world └── hello.asm ├── basic-control-flow └── basic-control-flow.asm └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.disasm 2 | *.o 3 | *.tmp 4 | *.elf 5 | peda-session-*.txt 6 | .gdb_history 7 | -------------------------------------------------------------------------------- /bin/writable-text: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BrianStadnicki/self-modifying-assembly-examples/HEAD/bin/writable-text -------------------------------------------------------------------------------- /bin/execute.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | nasm -f elf "$1.asm" 4 | ld -lc --dynamic-linker /lib/i386-linux-gnu/ld-linux.so.2 -m elf_i386 -o "$1.tmp" "$1.o" 5 | objcopy --writable-text --set-section-flags .text=CONTENTS,ALLOC,LOAD,CODE "$1.tmp" "$1.elf" 6 | ../bin/writable-text "$1.elf" 7 | objdump -M intel -d "$1.elf" > "$1".disasm 8 | "./$1.elf" 9 | -------------------------------------------------------------------------------- /basic-printf/printf.asm: -------------------------------------------------------------------------------- 1 | extern printf 2 | 3 | section .data 4 | format: db "num: %d",10,0 5 | 6 | section .text 7 | global _start 8 | 9 | _start: 10 | call printf_num 11 | 12 | mov byte [printf_num+1], 0xa 13 | 14 | call printf_num 15 | 16 | mov eax, 1 ; sys_exit 17 | mov ebx, 0 ; return code 0 18 | int 80h ; 19 | 20 | printf_num: 21 | push 8 22 | push format 23 | call printf 24 | add esp, 8 25 | ret 26 | -------------------------------------------------------------------------------- /factorial/factorial.asm: -------------------------------------------------------------------------------- 1 | extern printf 2 | 3 | section .data 4 | format: db "num: %d",10,0 5 | 6 | section .text 7 | global _start 8 | 9 | _start: 10 | mov eax, 0x5 11 | call factorial 12 | 13 | push eax 14 | push format 15 | call printf 16 | 17 | mov eax, 1 18 | mov ebx, 0 19 | int 80h 20 | 21 | factorial: 22 | push ebp 23 | mov ebx, eax 24 | factorial_start: 25 | sub ebx, 1 26 | je factorial_end 27 | mul ebx 28 | jmp factorial_start 29 | factorial_end: 30 | pop ebp 31 | ret 32 | -------------------------------------------------------------------------------- /hello world/hello.asm: -------------------------------------------------------------------------------- 1 | section .data 2 | hello: db 'Hello world!',10 3 | helloLen: equ $-hello 4 | 5 | section .text 6 | global _start 7 | 8 | _start: 9 | call print_hello 10 | 11 | mov al, byte [print_hello+11] ; increment start of hello string 12 | add eax, 1 13 | mov byte [print_hello+11], al 14 | 15 | call print_hello 16 | 17 | mov eax, 1 ; instruction 18 | mov ebx, 0 ; return code 0 19 | int 80h ; sys_exit 20 | 21 | print_hello: 22 | mov eax, 4 ; sys_write code 23 | mov ebx, 1 ; file descriptor 24 | mov ecx, hello ; string 25 | mov edx, helloLen ; len 26 | int 80h ; sys_write 27 | ret 28 | -------------------------------------------------------------------------------- /factorial/factorial-mod.asm: -------------------------------------------------------------------------------- 1 | extern printf 2 | 3 | section .data 4 | format: db "num: %d",10,0 5 | 6 | section .text 7 | global _start 8 | 9 | _start: 10 | mov dword [factorial+2], 0x5 ; start number 11 | 12 | call factorial 13 | ; print result 14 | push eax 15 | push format 16 | call printf 17 | ; exit 18 | mov eax, 1 19 | mov ebx, 0 20 | int 80h 21 | 22 | factorial: 23 | push ebp 24 | mov eax, 0 25 | 26 | mov ebx, eax 27 | sub ebx, 1 28 | mov dword [factorial_start+1], ebx ; init decrementer 29 | mov ebx, 0 30 | 31 | factorial_start: 32 | ; multiply 33 | mov ebx, 0 34 | mul ebx 35 | 36 | ; decrement 37 | dec dword [factorial_start+1] 38 | ; exit if at 0 39 | ; could exit at 1, but then it doesn't handle 0x2 40 | cmp ebx, 1 41 | je factorial_end 42 | ; loop back 43 | jmp factorial_start 44 | 45 | factorial_end: 46 | pop ebp 47 | ret 48 | -------------------------------------------------------------------------------- /basic-control-flow/basic-control-flow.asm: -------------------------------------------------------------------------------- 1 | section .data 2 | dog: db 'Dog',10 3 | dogLen: equ $-dog 4 | cat: db 'Cat',10 5 | catLen: equ $-cat 6 | 7 | section .text 8 | global _start 9 | 10 | _start: 11 | call print_animal 12 | 13 | mov byte [print_animal_dog-1], 90h ; nop jmp before print_animal_dog 14 | mov byte [print_animal_dog-2], 90h ; 15 | 16 | call print_animal 17 | 18 | mov eax, 1 ; instruction 19 | mov ebx, 0 ; return code 0 20 | int 80h ; sys_exit 21 | 22 | print_animal: 23 | mov eax, 4 ; sys_write code 24 | mov ebx, 1 ; file descriptor 25 | jmp print_animal_cat ; always print cat 26 | 27 | print_animal_dog: 28 | mov ecx, dog ; string 29 | mov edx, dogLen ; len 30 | jmp print_animal_write 31 | 32 | print_animal_cat: 33 | mov ecx, cat ; string 34 | mov edx, catLen ; len 35 | 36 | print_animal_write: 37 | int 80h ; sys_write 38 | ret 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # x86 self-modifying code examples 2 | 3 | Self-modifying code is something there doesn't seem to be much talk about, so here's some basic examples of it! 4 | 5 | Current examples: 6 | - Basic control flow 7 | - Basic printf 8 | - Factorial 9 | - Hello, World! 10 | 11 | There's currently only x86 linux examples currently, I may look into windows and llvm in the future. 12 | 13 | ## Usage 14 | 15 | Go into the directory of the code you want to run and execute `../execute.sh code-name`. For example: `../execute.sh factorial`. 16 | 17 | ## Dependencies 18 | 19 | - nasm 20 | - ld 21 | - objcopy 22 | - objdump 23 | - libc:i386 24 | 25 | ## Quirks 26 | 27 | If you look at the execute script, you'll notice that it's a bit odd. `ld` has `--omagic` to allow the `.text` section to be written, but that also disables dynamic linking. `objcopy` is used to make the `.text` section writable, but because of a bug, another script from stackoverflow is used to fix it. 28 | 29 | The normal method of writing self-modifying code is to allocate your own memory and do a dance where sometimes it's executable and sometimes its writable. That's a pain, so the program just tells the system that `.text` is modifiable through a flag here. 30 | 31 | Not all of the code will follow proper calling conventions and have a regular formatting. I'm a reverse engineer, not an assembly programmer. 32 | -------------------------------------------------------------------------------- /bin/writable-text.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | // https://stackoverflow.com/a/44993123 5 | int main(int argc, char** argv) 6 | { 7 | if (argc <= 1) return -1; 8 | FILE* fp = fopen(argv[1], "r+"); 9 | Elf64_Ehdr teh; 10 | fread(&teh, sizeof(teh), 1, fp); 11 | fseek(fp, 0, SEEK_SET); 12 | if (teh.e_ident[EI_CLASS] == ELFCLASS64) { 13 | Elf64_Ehdr eh; 14 | fread(&eh, sizeof(eh), 1, fp); 15 | Elf64_Phdr* ph = malloc(eh.e_phnum * eh.e_phentsize); 16 | Elf64_Shdr* sh = malloc(eh.e_shnum * eh.e_shentsize); 17 | fseek(fp, eh.e_phoff, SEEK_SET); 18 | fread(ph, eh.e_phentsize, eh.e_phnum, fp); 19 | fseek(fp, eh.e_shoff, SEEK_SET); 20 | fread(sh, eh.e_shentsize, eh.e_shnum, fp); 21 | for (int i = 0; i < eh.e_phnum; i++) { 22 | if (ph[i].p_vaddr <= eh.e_entry && ph[i].p_vaddr + ph[i].p_memsz > eh.e_entry) { 23 | fseek(fp, eh.e_phoff + i * eh.e_phentsize + (unsigned int)&((Elf64_Phdr*)0)->p_flags, SEEK_SET); 24 | ph[i].p_flags |= PF_W; 25 | fwrite(&ph[i].p_flags, sizeof(ph[i].p_flags), 1, fp); 26 | } 27 | } 28 | for (int i = 0; i < eh.e_shnum; i++) { 29 | if (sh[i].sh_addr <= eh.e_entry && sh[i].sh_addr + sh[i].sh_size > eh.e_entry) { 30 | fseek(fp, eh.e_shoff + i * eh.e_shentsize + (unsigned int)&((Elf64_Shdr*)0)->sh_flags, SEEK_SET); 31 | sh[i].sh_flags |= SHF_WRITE; 32 | fwrite(&sh[i].sh_flags, sizeof(sh[i].sh_flags), 1, fp); 33 | } 34 | } 35 | free(ph); 36 | free(sh); 37 | } else { 38 | Elf32_Ehdr eh; 39 | fread(&eh, sizeof(eh), 1, fp); 40 | Elf32_Phdr* ph = malloc(eh.e_phnum * eh.e_phentsize); 41 | Elf32_Shdr* sh = malloc(eh.e_shnum * eh.e_shentsize); 42 | fseek(fp, eh.e_phoff, SEEK_SET); 43 | fread(ph, eh.e_phentsize, eh.e_phnum, fp); 44 | fseek(fp, eh.e_shoff, SEEK_SET); 45 | fread(sh, eh.e_shentsize, eh.e_shnum, fp); 46 | for (int i = 0; i < eh.e_phnum; i++) { 47 | if (ph[i].p_vaddr <= eh.e_entry && ph[i].p_vaddr + ph[i].p_memsz > eh.e_entry) { 48 | fseek(fp, eh.e_phoff + i * eh.e_phentsize + (unsigned int)&((Elf32_Phdr*)0)->p_flags, SEEK_SET); 49 | ph[i].p_flags |= PF_W; 50 | fwrite(&ph[i].p_flags, sizeof(ph[i].p_flags), 1, fp); 51 | } 52 | } 53 | for (int i = 0; i < eh.e_shnum; i++) { 54 | if (sh[i].sh_addr <= eh.e_entry && sh[i].sh_addr + sh[i].sh_size > eh.e_entry) { 55 | fseek(fp, eh.e_shoff + i * eh.e_shentsize + (unsigned int)&((Elf32_Shdr*)0)->sh_flags, SEEK_SET); 56 | sh[i].sh_flags |= SHF_WRITE; 57 | fwrite(&sh[i].sh_flags, sizeof(sh[i].sh_flags), 1, fp); 58 | } 59 | } 60 | free(ph); 61 | free(sh); 62 | } 63 | fflush(fp); 64 | fclose(fp); 65 | return 0; 66 | } 67 | --------------------------------------------------------------------------------