├── .gitignore ├── Makefile ├── README.md ├── add_code.c ├── entry_helper_32.nasm ├── entry_helper_64.nasm ├── entry_test_32.nasm ├── entry_test_64.nasm ├── link_o.py ├── test-entry.sh ├── test.c ├── test.sh ├── test_dumb.c ├── test_multisection_32.nasm ├── test_multisection_64.nasm ├── test_new_code.nasm ├── utils.h └── utils.nasm /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.stderr 3 | /add_code_32 4 | /add_code_64 5 | /test 6 | /test_static 7 | /test_32 8 | /test_static_32 9 | /test_dumb 10 | /test_dumb_static 11 | /test_dumb_32 12 | /test_dumb_static_32 13 | /test_new_code 14 | /test_multisection_32 15 | /test_multisection_64 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS ?= -march=native -g3 -gdwarf-4 -Wall -Wextra $(EXTRA_CFLAGS) 2 | CFLAGS += -std=gnu11 3 | 4 | # Basic functionality 5 | PGMS = add_code_32 add_code_64 6 | PGMS += test test_static test_32 test_static_32 7 | NEWCODES = test_new_code test_multisection_32 test_multisection_64 test_multisection_32.o test_multisection_64.o test_multisection_origentry_32.o test_multisection_origentry_64.o 8 | 9 | # Entry-point replacement helpers 10 | NEWCODES += entry_helper_64.o entry_helper_32.o 11 | PGMS += test_dumb test_dumb_static test_dumb_32 test_dumb_static_32 12 | NEWCODES += entry_test_32.o entry_test_64.o 13 | 14 | 15 | all: $(PGMS) $(NEWCODES) 16 | 17 | add_code_32: add_code.c utils.h 18 | $(CC) -DADD_CODE_32 $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 19 | add_code_64: add_code.c utils.h 20 | $(CC) -DADD_CODE_64 $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 21 | 22 | 23 | test: test.c utils.h 24 | $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 25 | test_static: test.c utils.h 26 | $(CC) $(CPPFLAGS) $(CFLAGS) -static -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 27 | test_32: test.c utils.h 28 | $(CC) $(CPPFLAGS) $(CFLAGS) -m32 -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 29 | test_static_32: test.c utils.h 30 | $(CC) $(CPPFLAGS) $(CFLAGS) -m32 -static -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 31 | test_dumb: test_dumb.c 32 | $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 33 | test_dumb_static: test_dumb.c 34 | $(CC) $(CPPFLAGS) $(CFLAGS) -static -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 35 | test_dumb_32: test_dumb.c 36 | $(CC) $(CPPFLAGS) $(CFLAGS) -m32 -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 37 | test_dumb_static_32: test_dumb.c 38 | $(CC) $(CPPFLAGS) $(CFLAGS) -m32 -static -o $@ $< $(LDFLAGS) $(LOADLIBES) $(LDLIBS) 39 | 40 | 41 | TEST_CODE_CFLAGS = -nostdlib -static -Wl,-Ttext=0x066000000 -Wl,-Tdata=0x066200000 -Wl,-Tbss=0x066400000 -Wl,--build-id=none 42 | 43 | test_new_code: test_new_code.nasm 44 | # Make sure it's still valid 64-bit code! 45 | nasm -fbin -Wall -o $@ $< 46 | test_multisection_32.o: test_multisection_32.nasm 47 | nasm -felf32 -Wall -o $@ $< 48 | test_multisection_32: test_multisection_32.o 49 | $(CC) $(TEST_CODE_CFLAGS) -m32 -o $@ $< 50 | test_multisection_64.o: test_multisection_64.nasm 51 | nasm -felf64 -Wall -o $@ $< 52 | test_multisection_64: test_multisection_64.o 53 | $(CC) $(TEST_CODE_CFLAGS) -o $@ $< 54 | test_multisection_origentry_64.o: test_multisection_64.nasm 55 | nasm -DPRINT_ORIGINAL_ENTRYPOINT -felf64 -Wall -o $@ $< 56 | test_multisection_origentry_32.o: test_multisection_32.nasm 57 | nasm -DPRINT_ORIGINAL_ENTRYPOINT -felf32 -Wall -o $@ $< 58 | 59 | entry_helper_64.o: entry_helper_64.nasm 60 | nasm -felf64 -Wall -o $@ $< 61 | entry_helper_32.o: entry_helper_32.nasm 62 | nasm -felf32 -Wall -o $@ $< 63 | 64 | entry_test_64.o: entry_test_64.nasm 65 | nasm -felf64 -Wall -o $@ $< 66 | entry_test_32.o: entry_test_32.nasm 67 | nasm -felf32 -Wall -o $@ $< 68 | 69 | 70 | 71 | check: all 72 | ./test.sh ./add_code_64 test test_new_code 73 | ./test.sh ./add_code_64 test_static test_new_code 74 | ./test.sh ./add_code_64 test test_new_code 0x13330000 75 | ./test.sh ./add_code_64 test_static test_new_code 0x13330000 76 | ./test.sh ./add_code_32 test_32 test_new_code 77 | ./test.sh ./add_code_32 test_static_32 test_new_code 78 | ./test.sh ./add_code_32 test_32 test_new_code 0x13330000 79 | ./test.sh ./add_code_32 test_static_32 test_new_code 0x13330000 80 | @echo "Basic tests passed, let's try injecting full ELF executables..." 81 | ./test.sh ./add_code_64 test test_multisection_64 82 | ./test.sh ./add_code_64 test_static test_multisection_64 83 | ./test.sh ./add_code_32 test_32 test_multisection_32 84 | ./test.sh ./add_code_32 test_static_32 test_multisection_32 85 | @echo "ELF executable tests passed, let's try linking in ELF objects..." 86 | ./test.sh ./add_code_64 test test_multisection_origentry_64.o 87 | ./test.sh ./add_code_64 test_static test_multisection_origentry_64.o 88 | ./test.sh ./add_code_64 test test_multisection_origentry_64.o 0x13330000 89 | ./test.sh ./add_code_64 test_static test_multisection_origentry_64.o 0x13330000 90 | ./test.sh ./add_code_32 test_32 test_multisection_origentry_32.o 91 | ./test.sh ./add_code_32 test_static_32 test_multisection_origentry_32.o 92 | ./test.sh ./add_code_32 test_32 test_multisection_origentry_32.o 0x13330000 93 | ./test.sh ./add_code_32 test_static_32 test_multisection_origentry_32.o 0x13330000 94 | @echo "Non-entry code injection tests passed, let's now test entry-point replacement helpers..." 95 | ./test-entry.sh ./add_code_64 --before-entry test_dumb_static entry_test_64.o 96 | ./test-entry.sh ./add_code_64 --before-entry test_dumb_static entry_test_64.o 0x13330000 97 | ./test-entry.sh ./add_code_32 --before-entry test_dumb_static_32 entry_test_32.o 98 | ./test-entry.sh ./add_code_32 --before-entry test_dumb_static_32 entry_test_32.o 0x13330000 99 | @echo "before-entry doesn't work on dynamic executables, not testing it" 100 | #./test-entry.sh ./add_code_64 --before-entry test_dumb entry_test_64.o 101 | #./test-entry.sh ./add_code_64 --before-entry test_dumb entry_test_64.o 0x13330000 102 | #./test-entry.sh ./add_code_32 --before-entry test_dumb_32 entry_test_32.o 103 | #./test-entry.sh ./add_code_32 --before-entry test_dumb_32 entry_test_32.o 0x13330000 104 | @echo "All tests passed :)" 105 | 106 | .PHONY: clean all check 107 | clean: 108 | rm -f $(PGMS) $(NEWCODES) ./modified_test 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Astonishingly flexible code injection into an ELF executable. Helps you write as little as possible to intercept the entry point. 2 | 3 | Use `add_code_32` to manipulate and ELF32 file, `add_code_64` for ELF64. 4 | 5 | 6 | Adding raw assembly code 7 | ------------------------ 8 | 9 | nasm -fbin -o new_code_bin new_code.nasm 10 | add_code_xx original_program new_code_bin [new_code_vaddr=0x16660000] > modified_program 11 | 12 | 13 | Adding a full ELF executable 14 | ---------------------------- 15 | 16 | (Don't use this mode directly, it's mainly a helper for the next one.) 17 | 18 | gcc -nostdlib -static -Wl,-Ttext=0x166000000 -Wl,-Tdata=0x166200000 -Wl,-Tbss=0x166400000 -Wl,--build-id=none -o exec_to_add something.c 19 | add_code_xx program exec_to_add > out_program 20 | 21 | The program's LOAD commands ("segments") will be merged into a single one. Its load address is determined by what is in `exec_to_add`, so make sure there is no overlap. Check with `readelf -l`. 22 | 23 | 24 | 25 | Linking in an ELF object / replacing the entry point 26 | ---------------------------------------------------- 27 | 28 | gcc -nostdlib -static -Wl,--build-id=none -o xxx.o -c xxx.c 29 | add_code_xx [--before-entry] program xxx.o [new_code_vaddr=0x16660000] > out_program 30 | 31 | Will take care of linking xxx.o. You will be able to use `extern uintptr_t original_entrypoint` to refer to stuff in the main program. 32 | 33 | **Entrypoint replacement helper:** Write a `before_entry` function and specify `--before-entry`. 34 | A helper function will be linked in and will replace the program's entry point. It saves the initial registers (access them as `initial_eax`, `initial_ebx`, ...), calls your function, restores the initial registers, and jumps to `original_entrypoint`. 35 | 36 | 37 | 38 | Limitations, TODOs 39 | ------------------ 40 | 41 | - Replaces the NOTE program header. Should extend it to manipulate existing LOAD headers in case NOTE is missing or cannot be removed. 42 | - No PIE handling. Not very problematic, at the moment, since current PIEs are always dynamic at the moment due to limitations of libc loaders (musl is working on making static PIE possible, though). 43 | - Just exposes `original_entrypoint`. Should find a way to expose all original symbols. 44 | - Can handle a single object file. Use `ld -r` to combine multiple ones if necessary. 45 | - Tested on x86 and x64 only. 46 | 47 | 48 | Note: Always `make check` :) 49 | -------------------------------------------------------------------------------- /add_code.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "utils.h" 14 | 15 | #include 16 | 17 | /* Used to find the helper scripts and objects. 18 | * If NULL, will be set to the program's own directory. */ 19 | char* MY_PATH = NULL; 20 | 21 | #if defined(ADD_CODE_32) 22 | # define Ehdr Elf32_Ehdr 23 | # define Phdr Elf32_Phdr 24 | # define Shdr Elf32_Shdr 25 | static const int ELFCLASS = ELFCLASS32; 26 | static const char* ENTRY_HELPER = "entry_helper_32.o"; 27 | #elif defined(ADD_CODE_64) 28 | # define Ehdr Elf64_Ehdr 29 | # define Phdr Elf64_Phdr 30 | # define Shdr Elf64_Shdr 31 | static const int ELFCLASS = ELFCLASS64; 32 | static const char* ENTRY_HELPER = "entry_helper_64.o"; 33 | static_assert(sizeof(void*) == 8, "I mangle 64-bit ELFs only as a 64-bit program. This thing is already complicated enough."); 34 | #else 35 | #error "Do you want to mangle 32-bit or 64-bit ELF files?" 36 | #endif 37 | 38 | #define info(args...) fprintf(stderr, "[INFO] " args) 39 | #define warning(args...) fprintf(stderr, "\n*WARNING*: " args) 40 | 41 | static void check_elf_file_header(const Ehdr *file_header) 42 | { 43 | int i; 44 | V((file_header->e_ident[0] == 0x7f) && (file_header->e_ident[1] == 'E') && (file_header->e_ident[2] == 'L') && (file_header->e_ident[3] == 'F')); 45 | V(file_header->e_ident[EI_CLASS] == ELFCLASS); 46 | V(file_header->e_ident[EI_DATA] == ELFDATA2LSB); 47 | V(file_header->e_ident[EI_VERSION] == EV_CURRENT); 48 | for (i = EI_PAD; i < EI_NIDENT; ++i) 49 | V(file_header->e_ident[i] == 0); 50 | V((file_header->e_type == ET_EXEC) || (file_header->e_type == ET_DYN)); 51 | V(file_header->e_version == 1); /* EV_CURRENT */ 52 | V(file_header->e_ehsize == sizeof(Ehdr)); 53 | V(file_header->e_phentsize == sizeof(Phdr)); 54 | V(file_header->e_phnum >= 1); 55 | V(file_header->e_shentsize == sizeof(Shdr)); 56 | 57 | /* These depend on the architecture / version / etc. */ 58 | // V(file_header->e_ident[EI_OSABI] == ELFOSABI_NONE); 59 | // V(file_header->e_machine == EM_386); 60 | // V(file_header->e_flags == 0); 61 | } 62 | 63 | 64 | static unsigned long exec_to_bin(uint8_t **p_new_code, size_t *p_new_code_size, unsigned long *p_new_code_vaddr) 65 | { 66 | uint8_t *file_start = *p_new_code; 67 | Ehdr *file_header = (Ehdr *) file_start; 68 | check_elf_file_header(file_header); 69 | V(file_header->e_type == ET_EXEC); // TODO: PIE 70 | 71 | uint8_t *new_code = malloc(100); // So that we can realloc() 72 | size_t size = 0; 73 | unsigned long base = 0; 74 | 75 | Phdr *phdr = (Phdr*) (file_start + file_header->e_phoff); 76 | for (int i = 0; i < file_header->e_phnum; i++, phdr++) { 77 | if (phdr->p_type == PT_NOTE) 78 | continue; /* No need to warn */ 79 | if (phdr->p_type == PT_GNU_STACK) { 80 | info("Ignoring the PT_GNU_STACK program header, your code will inherit the original program's one\n"); 81 | continue; 82 | } 83 | if (phdr->p_type != PT_LOAD) { 84 | warning("Ignoring non-load program header %d/%d, type 0x%x\n", 85 | i+1, file_header->e_phnum, phdr->p_type); 86 | continue; 87 | } 88 | 89 | if (phdr->p_vaddr == 0) 90 | errx(1, "ERROR: your code is asking to be loaded starting from address 0. MOST LIKELY, THE BINARY WOULD NOT LOAD, due to vm.mmap_min_addr. Note that ld aligns segments, so your load address should be higher than the alignment mask used by ld. (Also, my code does not handle it :()\n"); 91 | else if (phdr->p_vaddr <= (base + size)) 92 | errx(1, "ERROR: The PT_LOAD headers are out of order: PT_LOAD header %d/%d has a smaller vaddr than last one's end (0x%lx <= 0x%lx).", 93 | i+1, file_header->e_phnum, (unsigned long) phdr->p_vaddr, base + size); 94 | 95 | V(phdr->p_vaddr == phdr->p_paddr); 96 | size_t misalignment = phdr->p_vaddr % 4096; 97 | V((phdr->p_offset % 4096) == misalignment); 98 | 99 | if (base == 0) { 100 | base = phdr->p_vaddr; 101 | V(misalignment == 0); // TODO: not sure how to behave in that case 102 | } else { 103 | size_t diff = phdr->p_vaddr - (base + size); 104 | info("0x%lx -> 0x%lx Zero-padding\n", 105 | (unsigned long) base + size, (unsigned long) phdr->p_vaddr); 106 | new_code = realloc(new_code, size + diff); VE(new_code != NULL); 107 | memset(new_code + size, 0, diff); 108 | size += diff; 109 | } 110 | 111 | info("0x%lx -> 0x%lx Load from file 0x%lx to 0x%lx\n", 112 | base + size, base + size + phdr->p_memsz, 113 | (unsigned long) phdr->p_offset, (unsigned long) phdr->p_offset + phdr->p_filesz); 114 | new_code = realloc(new_code, size + phdr->p_memsz); VE(new_code != NULL); 115 | memcpy(new_code + size, file_start + phdr->p_offset, phdr->p_filesz); 116 | size_t extra_load_size = phdr->p_memsz - phdr->p_filesz; 117 | if (extra_load_size) 118 | memset(new_code + size + phdr->p_filesz, 0, extra_load_size); 119 | size += phdr->p_memsz; 120 | } 121 | 122 | unsigned long added_code_entry = (unsigned long) file_header->e_entry; 123 | info("New stuff will be loaded from 0x%lx to 0x%lx\n", 124 | base, base+size); 125 | info("The entry point for the ELF we added was 0x%lx\n", added_code_entry); 126 | 127 | *p_new_code = new_code; 128 | *p_new_code_size = size; 129 | *p_new_code_vaddr = base; 130 | free((void*) file_header); 131 | return added_code_entry; 132 | } 133 | 134 | //static char* link_obj(const char *objects[], int n_objects, const char *original_program, unsigned long requested_vaddr, bool before_entry) 135 | static char* link_obj(const char *object, const char *original_program, unsigned long requested_vaddr, bool before_entry) 136 | { 137 | char exec_filename[255] = "/tmp/add_elf_code_XXXXXX"; 138 | V(mkdtemp(exec_filename) != NULL); 139 | strcat(exec_filename, "/exec_to_add"); 140 | 141 | char cmdline[500]; 142 | int cmdlen = snprintf(cmdline, sizeof(cmdline), 143 | "'%s/link_o.py' --original-program '%s' -o '%s' --start-address=0x%lx", 144 | MY_PATH, original_program, exec_filename, requested_vaddr); 145 | VS(cmdlen); V(cmdlen < ((int) sizeof(cmdline))); 146 | // for (int i; i < n_objects; i++) { 147 | // safe_strcat(cmdline, " '", sizeof(cmdline)); 148 | // safe_strcat(cmdline, objects[i], sizeof(cmdline)); 149 | // safe_strcat(cmdline, "'", sizeof(cmdline)); 150 | // } 151 | safe_strcat(cmdline, " '", sizeof(cmdline)); 152 | safe_strcat(cmdline, object, sizeof(cmdline)); 153 | safe_strcat(cmdline, "'", sizeof(cmdline)); 154 | 155 | if (before_entry) { 156 | safe_strcat(cmdline, " '", sizeof(cmdline)); 157 | safe_strcat(cmdline, MY_PATH, sizeof(cmdline)); 158 | safe_strcat(cmdline, "/", sizeof(cmdline)); 159 | safe_strcat(cmdline, ENTRY_HELPER, sizeof(cmdline)); 160 | safe_strcat(cmdline, "'", sizeof(cmdline)); 161 | } 162 | info("Running %s\n", cmdline); 163 | V(system(cmdline) == 0); 164 | 165 | return strdup(exec_filename); 166 | } 167 | 168 | int main(int argc, char *argv[]) 169 | { 170 | if (argc != 3 && argc != 4 && argc != 5) 171 | errx(10, "Usage: %s [--before-entry] program new_code [new_code_vaddr=0x16660000] > out_program", argv[0]); 172 | 173 | if (MY_PATH == NULL) { 174 | MY_PATH = dirname(strdup(argv[0])); 175 | DIR *d = opendir(MY_PATH); 176 | if (d == NULL) 177 | err(1, "I couldn't determine the path of my helper files. I tried '%s' from argv[0]='%s'.", MY_PATH, argv[0]); 178 | closedir(d); 179 | } 180 | 181 | int argbase = 0; // XXX: yeah, yeah 182 | 183 | bool before_entry = false; 184 | if (strcmp(argv[1], "--before-entry") == 0) { 185 | before_entry = true; 186 | argbase++; 187 | } 188 | 189 | unsigned long new_code_vaddr = 0x16660000u; 190 | static_assert(sizeof(unsigned long) == sizeof(void*), ""); /* Unclean, but good and simple */ 191 | if (argc == (4+argbase)) 192 | new_code_vaddr = explicit_hex_conv(argv[3+argbase]); 193 | 194 | char* elf_filename = argv[1+argbase]; 195 | char* new_code_filename = argv[2+argbase]; 196 | 197 | size_t original_size, new_code_size; 198 | uint8_t *elf = read_file(elf_filename, &original_size); 199 | uint8_t *new_code = read_file(new_code_filename, &new_code_size); 200 | 201 | if (before_entry && ((Ehdr*) new_code)->e_type != ET_REL) 202 | errx(1, "You need to pass an object file to take advantage of the entry point replacement helper."); 203 | 204 | /* Can also add an elf file, if requested, attempting a segment merge. 205 | * If using an object file, it will link it, also making original_entrypoint available to it as a symbol. */ 206 | unsigned long new_code_entry = 0; 207 | if (*((uint32_t*) new_code) == 0x464C457f) { // \x7fELF 208 | if (((Ehdr*) new_code)->e_type == ET_EXEC) { 209 | exec_to_bin(&new_code, &new_code_size, &new_code_vaddr); 210 | } else if (((Ehdr*) new_code)->e_type == ET_REL) { 211 | char* execname = link_obj(new_code_filename, elf_filename, new_code_vaddr, before_entry); 212 | free(new_code); 213 | new_code = read_file(execname, &new_code_size); 214 | char *tmpdir = dirname(execname); 215 | char rm_cmdline[500]; 216 | int cmdlen = snprintf(rm_cmdline, sizeof(rm_cmdline), "rm -rf \"%s\"", tmpdir); 217 | VS(cmdlen); V(cmdlen < ((int) sizeof(rm_cmdline))); 218 | system(rm_cmdline); 219 | new_code_entry = exec_to_bin(&new_code, &new_code_size, &new_code_vaddr); 220 | } else errx(1, "Can't handle PIE (or whatever that was)"); 221 | } 222 | 223 | /* General checks */ 224 | Ehdr *file_header = (Ehdr *) elf; 225 | check_elf_file_header(file_header); 226 | 227 | V(file_header->e_phoff == sizeof(Ehdr)); /* No surprises in the middle */ 228 | Phdr *phdr = (Phdr*) (elf + file_header->e_phoff); 229 | 230 | /* Let's find the (first) NOTE */ 231 | Phdr* note_phdr = NULL; 232 | for (int i = 0; i < file_header->e_phnum; i++, phdr++) { 233 | if (phdr->p_type == PT_NOTE) 234 | note_phdr = phdr; 235 | if (phdr->p_type == PT_PHDR) { 236 | /* This guy specifies the program headers themselves */ 237 | /* I'm not sure why it exists, but let's do some consistency checks */ 238 | V(i == 0); 239 | V((phdr->p_flags & PF_W) == 0); 240 | V(phdr->p_offset >= sizeof(Ehdr)); 241 | V(phdr->p_filesz == (sizeof(Phdr) * file_header->e_phnum)); 242 | V(phdr->p_filesz == phdr->p_memsz); 243 | } 244 | if (phdr->p_type == PT_LOAD) { 245 | V(overlap(phdr->p_vaddr, phdr->p_vaddr+phdr->p_memsz, new_code_vaddr, new_code_vaddr+new_code_size) == false); 246 | 247 | /* PT_LOAD headers should be ordered by vaddr */ 248 | if ((note_phdr == NULL) && (phdr->p_vaddr > new_code_vaddr)) /* PT_LOAD before ours, but larger vaddr */ 249 | warning("PT_LOAD header %d/%d comes before the NOTE one, but has a larger vaddr than your code's (0x%lx > 0x%lx). With PT_LOAD headers out of order THE NEW FILE MAY NOT LOAD.\n", i+1, file_header->e_phnum, (unsigned long) phdr->p_vaddr, new_code_vaddr); 250 | if ((note_phdr != NULL) && (phdr->p_vaddr < new_code_vaddr)) /* PT_LOAD after ours, but smaller vaddr */ 251 | warning("PT_LOAD header %d/%d comes after the NOTE one, but has a smaller vaddr than your code's (0x%lx < 0x%lx). With PT_LOAD headers out of order THE NEW FILE MAY NOT LOAD.\n", i+1, file_header->e_phnum, (unsigned long) phdr->p_vaddr, new_code_vaddr); 252 | } 253 | } 254 | phdr = note_phdr; 255 | if (phdr->p_type != PT_NOTE) 256 | errx(1, "There was no NOTE program header!"); 257 | 258 | 259 | phdr->p_type = PT_LOAD; 260 | phdr->p_flags = (PF_R | PF_W | PF_X); 261 | phdr->p_vaddr = phdr->p_paddr = new_code_vaddr; 262 | phdr->p_filesz = phdr->p_memsz = new_code_size; 263 | phdr->p_align = 1; /* Not sure if it matters or not */ 264 | 265 | /* If requested, change the entry point */ 266 | if (before_entry) 267 | file_header->e_entry = new_code_entry; 268 | 269 | /* Pads to make sure that the code is loaded right at new_code_vaddr */ 270 | V(sysconf(_SC_PAGESIZE) == 4096u); 271 | unsigned long pad_len = 4096u - (original_size % 4096u) + (new_code_vaddr % 4096u); 272 | phdr->p_offset = original_size + pad_len; 273 | 274 | do_write(1, elf, original_size); 275 | if (pad_len) { 276 | uint8_t *pad = (uint8_t*) calloc(1, pad_len); 277 | VE(pad != NULL); 278 | do_write(1, pad, pad_len); 279 | } 280 | do_write(1, new_code, new_code_size); 281 | return 0; 282 | } 283 | 284 | -------------------------------------------------------------------------------- /entry_helper_32.nasm: -------------------------------------------------------------------------------- 1 | BITS 32 2 | 3 | ; Helps isolating your code from the original program, restoring the 4 | ; original stack, registers, and flags when before_entry returns. 5 | ; 6 | ; Your code is called with the same stack as on program start, (plus 7 | ; the saved instruction pointer) so you can read argv & co. It may not 8 | ; be 16-byte aligned, though, so SSE instructions may not like it. 9 | ; 10 | ; You can also access the initial registers as initial_xxx. 11 | ; 12 | ; Note: the used stack portion is not zeroed back, so the program 13 | ; could detect changes based on that. One would need to know 14 | ; how much before_entry used to properly clean it back. 15 | ; (Also, CS is not checked.) 16 | 17 | 18 | section .bss 19 | %macro initial_reg 1 20 | global initial_ %+ %1:data 21 | initial_ %+ %1: resd 1 22 | %endmacro 23 | %macro initial_segment_selector 1 24 | global initial_ %+ %1:data 25 | initial_ %+ %1: resw 1 26 | %endmacro 27 | 28 | initial_reg esp 29 | initial_reg ebp 30 | initial_reg eax 31 | initial_reg ebx 32 | initial_reg ecx 33 | initial_reg edx 34 | initial_reg esi 35 | initial_reg edi 36 | initial_reg eflags 37 | initial_segment_selector ss 38 | initial_segment_selector ds 39 | initial_segment_selector es 40 | initial_segment_selector fs 41 | initial_segment_selector gs 42 | 43 | 44 | 45 | section .text 46 | extern original_entrypoint 47 | extern before_entry 48 | global _start:function 49 | 50 | _start: 51 | mov [initial_esp], esp 52 | mov [initial_ebp], ebp 53 | mov [initial_eax], eax 54 | mov [initial_ebx], ebx 55 | mov [initial_ecx], ecx 56 | mov [initial_edx], edx 57 | mov [initial_esi], esi 58 | mov [initial_edi], edi 59 | pushf 60 | pop dword [initial_eflags] 61 | mov word [initial_ss], ss 62 | mov word [initial_ds], ds 63 | mov word [initial_es], es 64 | mov word [initial_fs], fs 65 | mov word [initial_gs], gs 66 | 67 | call before_entry 68 | 69 | mov esp, [initial_esp] 70 | mov ebp, [initial_ebp] 71 | mov eax, [initial_eax] 72 | mov ebx, [initial_ebx] 73 | mov ecx, [initial_ecx] 74 | mov edx, [initial_edx] 75 | mov esi, [initial_esi] 76 | mov edi, [initial_edi] 77 | push dword [initial_eflags] 78 | popf 79 | mov ss, word [initial_ss] 80 | mov ds, word [initial_ds] 81 | mov es, word [initial_es] 82 | mov fs, word [initial_fs] 83 | mov gs, word [initial_gs] 84 | 85 | jmp original_entrypoint 86 | -------------------------------------------------------------------------------- /entry_helper_64.nasm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | ; Helps isolating your code from the original program, restoring the 4 | ; original stack, registers, and flags when before_entry returns. 5 | ; 6 | ; Your code is called with the same stack as on program start, so you 7 | ; can read argv & co. It may not be 16-byte aligned, though, so SSE 8 | ; instructions may not like it. 9 | ; 10 | ; You can also access the initial registers as initial_xxx. 11 | ; 12 | ; Note: the used stack portion is not zeroed back, so the program 13 | ; could detect changes based on that. One would need to know 14 | ; how much before_entry used to properly clean it back. 15 | ; (Also, CS is not checked.) 16 | 17 | 18 | section .bss 19 | %macro initial_reg 1 20 | global initial_ %+ %1:data 21 | initial_ %+ %1: resq 1 22 | %endmacro 23 | %macro initial_segment_selector 1 24 | global initial_ %+ %1:data 25 | initial_ %+ %1: resw 1 26 | %endmacro 27 | 28 | initial_reg rsp 29 | initial_reg rbp 30 | initial_reg rax 31 | initial_reg rbx 32 | initial_reg rcx 33 | initial_reg rdx 34 | initial_reg rsi 35 | initial_reg rdi 36 | initial_reg r8 37 | initial_reg r9 38 | initial_reg r10 39 | initial_reg r11 40 | initial_reg r12 41 | initial_reg r13 42 | initial_reg r14 43 | initial_reg r15 44 | initial_reg rflags 45 | initial_segment_selector ss 46 | initial_segment_selector ds 47 | initial_segment_selector es 48 | initial_segment_selector fs 49 | initial_segment_selector gs 50 | 51 | 52 | 53 | section .text 54 | extern original_entrypoint 55 | extern before_entry 56 | global _start:function 57 | 58 | _start: 59 | mov [initial_rsp], rsp 60 | mov [initial_rbp], rbp 61 | mov [initial_rax], rax 62 | mov [initial_rbx], rbx 63 | mov [initial_rcx], rcx 64 | mov [initial_rdx], rdx 65 | mov [initial_rsi], rsi 66 | mov [initial_rdi], rdi 67 | mov [initial_r8 ], r8 68 | mov [initial_r9 ], r9 69 | mov [initial_r10], r10 70 | mov [initial_r11], r11 71 | mov [initial_r12], r12 72 | mov [initial_r13], r13 73 | mov [initial_r14], r14 74 | mov [initial_r15], r15 75 | pushf 76 | pop qword [initial_rflags] 77 | mov word [initial_ss], ss 78 | mov word [initial_ds], ds 79 | mov word [initial_es], es 80 | mov word [initial_fs], fs 81 | mov word [initial_gs], gs 82 | 83 | call before_entry 84 | 85 | mov rsp, [initial_rsp] 86 | mov rbp, [initial_rbp] 87 | mov rax, [initial_rax] 88 | mov rbx, [initial_rbx] 89 | mov rcx, [initial_rcx] 90 | mov rdx, [initial_rdx] 91 | mov rsi, [initial_rsi] 92 | mov rdi, [initial_rdi] 93 | mov r8 , [initial_r8 ] 94 | mov r9 , [initial_r9 ] 95 | mov r10, [initial_r10] 96 | mov r11, [initial_r11] 97 | mov r12, [initial_r12] 98 | mov r13, [initial_r13] 99 | mov r14, [initial_r14] 100 | mov r15, [initial_r15] 101 | push qword [initial_rflags] 102 | popf 103 | mov ss, word [initial_ss] 104 | mov ds, word [initial_ds] 105 | mov es, word [initial_es] 106 | mov fs, word [initial_fs] 107 | mov gs, word [initial_gs] 108 | 109 | jmp original_entrypoint 110 | -------------------------------------------------------------------------------- /entry_test_32.nasm: -------------------------------------------------------------------------------- 1 | BITS 32 2 | 3 | %include "utils.nasm" 4 | 5 | section .data 6 | s: db `XXXXXXXX\n` 7 | s_end: 8 | 9 | desc_ip: db 'in before_entry: IP: 0x' 10 | desc_ip_end: 11 | desc_argc: db 'in before_entry: argc: 0x' 12 | desc_argc_end: 13 | desc_argv: db 'in before_entry: argv: 0x' 14 | desc_argv_end: 15 | desc_argv0: db 'in before_entry: argv[0]: 0x' 16 | desc_argv0_end: 17 | desc_argv00: db 'in before_entry: argv[0][0:3]: ' 18 | desc_argv00_end: 19 | 20 | write_errmsg: db `write error\n` 21 | write_errmsg_end: 22 | esp_errmsg: db `initial_esp does not match the actual esp\n` 23 | esp_errmsg_end: 24 | 25 | 26 | 27 | section .text 28 | global before_entry:function 29 | before_entry: 30 | 31 | ; Simple version that does not use initial_esp 32 | ;mov eax, dword [esp+8] 33 | ;mov [eax], byte 'Q' 34 | ;mov [eax+1], byte 0 35 | ;ret 36 | 37 | 38 | extern initial_esp 39 | 40 | ; Let's check that initial_esp matches the one 41 | ; that we can observe 42 | mov eax, [initial_esp] 43 | sub eax, 4 ; Account for our call 44 | cmp esp, eax 45 | je .esp_ok 46 | print esp_errmsg 47 | mov eax, 252 ; __NR_exit_group 48 | mov ebx, 20 49 | int 0x80 50 | .esp_ok: 51 | 52 | 53 | ; OK, let's print stuff from the original stack 54 | mov eax, [initial_esp] 55 | mov [s], eax 56 | dword_to_hex s 57 | print desc_argc 58 | print s 59 | mov eax, [initial_esp] 60 | add eax, 4 61 | mov [s], eax 62 | dword_to_hex s 63 | print desc_argv 64 | print s 65 | mov eax, [initial_esp] 66 | mov eax, [eax+4] 67 | mov [s], eax 68 | dword_to_hex s 69 | print desc_argv0 70 | print s 71 | mov eax, [initial_esp] 72 | mov eax, [eax+4] 73 | mov eax, [eax] 74 | mov [s], eax 75 | mov [s+4], dword ' ' 76 | print desc_argv00 77 | print s 78 | 79 | ; Changing argv[0] to "Q" 80 | mov eax, [initial_esp] 81 | mov eax, [eax+4] 82 | mov [eax], byte 'Q' 83 | mov [eax+1], byte 0 84 | 85 | 86 | ; A generic print that uses the data section 87 | ;call n 88 | ;n: pop dword [s] 89 | ;dword_to_hex s 90 | ;print desc_ip 91 | ;print s 92 | 93 | 94 | ; The helper will take care of restoring registers and jumping to 95 | ; the original entrypoint 96 | ret 97 | -------------------------------------------------------------------------------- /entry_test_64.nasm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | global before_entry:function 4 | before_entry: 5 | ; Simple version that does not use initial_rsp 6 | ;mov rax, qword [rsp+16] ; char *rax = argv[0] 7 | 8 | extern initial_rsp 9 | mov rax, [initial_rsp] 10 | mov rax, [rax+8] 11 | 12 | 13 | mov [rax], byte 'Q' 14 | mov [rax+1], byte 0 15 | ret 16 | -------------------------------------------------------------------------------- /link_o.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | 3 | LD = "ld" 4 | OBJDUMP = "objdump" 5 | NM = "nm" 6 | ALIGN = 0x200000 7 | 8 | import re 9 | from collections import defaultdict 10 | from subprocess import * 11 | 12 | def get_syms(original_program): 13 | syms = {} 14 | t = check_output([NM,"--defined-only","-a","--synthetic",original_program]) 15 | for l in t.splitlines(): 16 | fields = l.split() 17 | if len(fields) == 2: continue # Nameless (filename) symbol 18 | val, t, name = fields 19 | 20 | if t == 'a': # Filename (repeated, useless) 21 | continue 22 | 23 | val = int(re.match(r'[0-9a-fA-F]+$',val).group(), 16) 24 | 25 | name = name.replace('.','_DOT_') 26 | name = name.replace('@','_AT_') 27 | name = name.replace('-','_DASH_') 28 | assert re.match('\w+$', name) 29 | 30 | name = "original_"+name 31 | if name in syms: 32 | # Omit duplicated symbols 33 | # TODO: What to do with them? What do they mean? 34 | del syms[name] 35 | else: syms[name] = val 36 | return syms 37 | 38 | def get_section_sizes(obj): 39 | size = {} 40 | t = check_output([OBJDUMP,'-h',obj]) 41 | for l in t.splitlines(): 42 | m = re.match(r'\s*[0-9]+\s+(\S+)\s+([0-9a-fA-F]+)\s+[0-9a-fA-F]+\s+[0-9a-fA-F]+\s+', l) 43 | if not m: continue 44 | size[m.group(1)] = int(m.group(2),16) 45 | return size 46 | 47 | 48 | def align_addr(addr): 49 | return (addr + ALIGN - 1) & ~(ALIGN - 1) 50 | 51 | 52 | def link_o(objects, original_program, out, start_address): 53 | header = check_output([OBJDUMP,"-f",original_program]) 54 | arch = re.search(r"architecture: ([^,]+)", header).group(1) 55 | 56 | emul = None 57 | if arch == 'i386': # TODO: necessary on other archs? 58 | emul = 'elf_i386' 59 | 60 | entry = int(re.search(r'start address 0x([0-9a-fA-F]+)', header).group(1), 16) 61 | 62 | syms = get_syms(original_program) 63 | syms["original_entrypoint"] = entry 64 | 65 | section_start = {} 66 | section_size = defaultdict(int) 67 | for o in objects: 68 | for name,size in get_section_sizes(o).iteritems(): 69 | section_size[name] += size 70 | cur = start_address 71 | for name,size in section_size.iteritems(): 72 | cur += size 73 | cur = align_addr(cur) 74 | section_start[name] = cur 75 | 76 | 77 | cmd = [ LD,"-nostdlib","--fatal-warnings","--gc-sections","-o",out ] 78 | if emul: cmd.extend(["-m",emul]) 79 | for sym,val in syms.iteritems(): 80 | cmd.append("--defsym={}={}".format(sym,hex(val))) 81 | for name,addr in section_start.iteritems(): 82 | cmd.append("--section-start={}={}".format(name,hex(addr))) 83 | cmd.append("--start-group") 84 | cmd.extend(objects) 85 | cmd.append("--end-group") 86 | return cmd 87 | 88 | if __name__ == "__main__": 89 | import argparse 90 | parser = argparse.ArgumentParser() 91 | parser.add_argument("--original-program", required=True, help="Get the symbols from this guy") 92 | parser.add_argument("-o", "--out", required=True, help="Output file name") 93 | parser.add_argument("--start-address", default="0x16660000", help="Your sections will be loaded starting at that address (format: 0xaddr)") 94 | parser.add_argument("objects", nargs="+") 95 | args = parser.parse_args() 96 | 97 | assert args.start_address[:2] == '0x' 98 | args.start_address = int(args.start_address[2:], 16) 99 | 100 | cmd = link_o(**vars(args)) 101 | check_call(cmd) 102 | -------------------------------------------------------------------------------- /test-entry.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo '[ ... ] Testing' "$@" 6 | 7 | for p in "$@"; do 8 | if [[ ${p:0:2} == "0x" ]]; then 9 | addr=$p 10 | fi 11 | done 12 | 13 | "$@" > ./modified_test 2>./add_code.stderr || { cat ./add_code.stderr; exit 1; } 14 | grep -q WARNING ./add_code.stderr && { echo "ERROR: got a warning, probably wrong test parameters"; exit 1; } 15 | addr=`grep 'entry point for the ELF we added' ./add_code.stderr | grep -o -E '0x[[:alnum:]]+'` 16 | chmod a+x ./modified_test 17 | 18 | set -o pipefail 19 | ./modified_test $addr | tee ./modified_test.stdout 20 | grep -q -F 'main: I was called with argv[0]=Q' ./modified_test.stdout 21 | rm -f ./modified_test ./modified_test.stdout ./add_code.stderr 22 | 23 | echo '[ :-) ] Returned success! Good!' 24 | echo 25 | echo 26 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "utils.h" 15 | 16 | static inline void print_self_maps() 17 | { 18 | // It seems /proc/self/maps can only be read character-by-character 19 | printf("/proc/self/maps:\n"); 20 | int fd; 21 | VS(fd = open("/proc/self/maps", O_RDONLY)); 22 | while (true) { 23 | unsigned char c; 24 | if (do_read_partial(fd, &c, 1) == 0) 25 | break; 26 | VE(putchar(c) != EOF); 27 | } 28 | VS(close(fd)); 29 | printf("\n\n"); 30 | } 31 | 32 | int main(int argc, char *argv[]) 33 | { 34 | if (!(argc == 1 || argc == 2 || (argc == 3 && strcmp(argv[1], "nofilecheck") == 0))) 35 | errx(10, "Usage: %s [nofilecheck] [new_code_vaddr=0x16660000]", argv[0]); 36 | 37 | bool nofilecheck = false; 38 | if (argc >= 2 && strcmp(argv[1], "nofilecheck") == 0) { 39 | nofilecheck = true; 40 | argv[1] = argv[2]; 41 | argc--; 42 | } 43 | 44 | 45 | unsigned long new_code_ul = 0x16660000u; 46 | static_assert(sizeof(unsigned long) == sizeof(void*), ""); /* Unclean, but good and simple */ 47 | if (argc == 2) 48 | new_code_ul = explicit_hex_conv(argv[1]); 49 | uint8_t* new_code = (uint8_t*) new_code_ul; 50 | 51 | 52 | /* 53 | #define P(x) do { extern uint8_t x; printf(#x "\t= %p\n", &x); } while (0) 54 | P(__executable_start); 55 | P(_etext); 56 | P(_edata); 57 | P(_end); 58 | print_self_maps(); 59 | 60 | 61 | printf("First four bites of new code's entry point (%p):\n", new_code); 62 | printf("%02x %02x %02x %02x", (unsigned) new_code[0], (unsigned) new_code[1], (unsigned) new_code[2], (unsigned) new_code[3]); 63 | printf("\n"); 64 | */ 65 | 66 | if (!nofilecheck) { 67 | printf("Checking it matches './test_new_code'...\n"); 68 | size_t new_code_size; 69 | uint8_t *from_file = read_file("./test_new_code", &new_code_size); 70 | V(memcmp(new_code, from_file, new_code_size) == 0); 71 | } 72 | 73 | 74 | printf("Jumping there...\n"); fflush(NULL); 75 | 76 | typedef int (*pfunc)(); 77 | unsigned int ret = ((pfunc) new_code)(); 78 | printf("new code returned 0x%x\n", ret); fflush(NULL); 79 | V(ret == 0x41414141u); 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo '[ ... ] Testing' "$@" 6 | 7 | for p in "$@"; do 8 | if [[ ${p:0:2} == "0x" ]]; then 9 | addr=$p 10 | elif [[ $p == *"multisection"* ]]; then 11 | nofilecheck="nofilecheck" 12 | fi 13 | done 14 | 15 | origentry=`readelf --file-header "$2" | grep Entry | grep -o -E '0x[[:alnum:]]+'` 16 | 17 | "$@" > ./modified_test 2>./add_code.stderr || { cat ./add_code.stderr; exit 1; } 18 | grep -q WARNING ./add_code.stderr && { echo "ERROR: got a warning, probably wrong test parameters"; exit 1; } 19 | if [[ $nofilecheck != "" ]]; then 20 | addr=`grep 'entry point for the ELF we added' ./add_code.stderr | grep -o -E '0x[[:alnum:]]+'` 21 | fi 22 | chmod a+x ./modified_test 23 | 24 | set -o pipefail 25 | ./modified_test $nofilecheck $addr | tee ./modified_test.stdout 26 | 27 | if [[ $3 == *"origentry"* ]]; then 28 | # readelf prints it without leading zeros, so we need to remove them here too 29 | printed_origentry=`grep -E 'in new code: original entry point:' ./modified_test.stdout | grep -o -E '0x[[:alnum:]]+' | tail -c+3` 30 | printed_origentry=`echo $printed_origentry | grep -o -E '[^0].*'` 31 | printed_origentry="0x$printed_origentry" 32 | if [[ $origentry != $printed_origentry ]]; then 33 | echo "The original entry point was not passed correctly" 34 | echo "Real: $origentry" 35 | echo "Printed: $printed_origentry" 36 | exit 1 37 | fi 38 | fi 39 | 40 | rm -f ./modified_test ./modified_test.stdout ./add_code.stderr 41 | 42 | echo '[ :-) ] Returned success! Good!' 43 | echo 44 | echo 45 | -------------------------------------------------------------------------------- /test_dumb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | if (argc == 0) 8 | errx(10, "argc == 0? WTF?"); 9 | printf("main: I was called with argv[0]=%s\n", argv[0]); 10 | return strcmp(argv[0], "Q"); 11 | } 12 | -------------------------------------------------------------------------------- /test_multisection_32.nasm: -------------------------------------------------------------------------------- 1 | BITS 32 2 | 3 | %include "utils.nasm" 4 | 5 | section .data 6 | a: dd 'AAAA' 7 | s: db `XXXXXXXX\n` 8 | s_end: 9 | 10 | desc_ip: db 'in new code: IP: 0x' 11 | desc_ip_end: 12 | desc_origentry: db 'in new code: original entry point: 0x' 13 | desc_origentry_end: 14 | write_errmsg: db `write error\n` 15 | write_errmsg_end: 16 | 17 | 18 | section .text 19 | global _start:function 20 | _start: 21 | ; Note: it is called as a regular function in test.c 22 | pusha 23 | 24 | call n 25 | n: pop dword [s] 26 | dword_to_hex s 27 | print desc_ip 28 | print s 29 | 30 | %ifdef PRINT_ORIGINAL_ENTRYPOINT 31 | extern original_entrypoint 32 | mov eax, original_entrypoint 33 | mov [s], eax 34 | dword_to_hex s 35 | print desc_origentry 36 | print s 37 | %endif 38 | 39 | popa 40 | mov eax, [a] 41 | ret 42 | -------------------------------------------------------------------------------- /test_multisection_64.nasm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | %include "utils.nasm" 4 | 5 | section .data 6 | a: dq 'AAAA' 7 | s: db `XXXXXXXXXXXXXXXX\n` 8 | s_end: 9 | 10 | desc_ip: db 'in new code: IP: 0x' 11 | desc_ip_end: 12 | desc_origentry: db 'in new code: original entry point: 0x' 13 | desc_origentry_end: 14 | 15 | write_errmsg: db `write error\n` 16 | write_errmsg_end: 17 | 18 | 19 | section .text 20 | global _start:function 21 | _start: 22 | ; Note: it is called as a regular function in test.c, so 23 | ; RBP, RBX, and R12-R15 would need to be saved 24 | call n 25 | n: pop qword [s] 26 | qword_to_hex s 27 | print desc_ip 28 | print s 29 | 30 | %ifdef PRINT_ORIGINAL_ENTRYPOINT 31 | extern original_entrypoint 32 | mov rax, original_entrypoint 33 | mov [s], rax 34 | qword_to_hex s 35 | print desc_origentry 36 | print s 37 | %endif 38 | 39 | mov rax, [a] 40 | ret 41 | -------------------------------------------------------------------------------- /test_new_code.nasm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | 3 | ; Believe it or not, this code is "amphibious": same decoding in 32-bit and 4 | ; 64-bit mode, and the same behavior (well, uses the entire rcx, obviously) 5 | 6 | mov eax, 'AAAA' 7 | xor ecx, ecx 8 | 9 | call n 10 | n: pop rcx 11 | 12 | ret 13 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define likely(x) __builtin_expect(!!(x), 1) 16 | #define unlikely(x) __builtin_expect(!!(x), 0) 17 | 18 | #define VAL_TO_STR(x) __STRING(x) 19 | #define V(x) if (unlikely(!(x))) errx(-9, __FILE__ ":" VAL_TO_STR(__LINE__) " %s, it's not %s", __PRETTY_FUNCTION__, #x) 20 | #define VE(x) if (unlikely(!(x))) err(-9, __FILE__ ":" VAL_TO_STR(__LINE__) " %s, it's not %s", __PRETTY_FUNCTION__, #x) 21 | #define VS(x) if (unlikely((x) == -1)) err(-9, __FILE__ ":" VAL_TO_STR(__LINE__) " %s, %s failed (returned -1)", __PRETTY_FUNCTION__, #x) 22 | 23 | #define max(a,b) ({typeof(a) _a = (a), _b = (b); _a > _b ? _a : _b; }) 24 | #define min(a,b) ({typeof(a) _a = (a), _b = (b); _a < _b ? _a : _b; }) 25 | 26 | // It's in unistd.h, but for some reason it is not always included 27 | #ifndef TEMP_FAILURE_RETRY 28 | # define TEMP_FAILURE_RETRY(expression) \ 29 | (__extension__ \ 30 | ({ long int __result; \ 31 | do __result = (long int) (expression); \ 32 | while (__result == -1L && errno == EINTR); \ 33 | __result; })) 34 | #endif 35 | 36 | 37 | static inline void do_sleep(unsigned int seconds) 38 | { 39 | // Note: libc takes care of the madness, and does not use SIGALRM 40 | while (seconds > 0) 41 | seconds = sleep(seconds); 42 | } 43 | 44 | __attribute__ ((__warn_unused_result__)) __attribute__ ((__nonnull__)) 45 | static inline ssize_t do_read_partial(int fd, uint8_t* buf, size_t len) 46 | { 47 | V(len <= SSIZE_MAX); 48 | ssize_t r; 49 | uint8_t *rbuf = buf; 50 | do { 51 | VS(r = TEMP_FAILURE_RETRY(read(fd, rbuf, len))); 52 | if (r == 0) 53 | break; 54 | len -= r; 55 | rbuf += r; 56 | } while (len != 0); 57 | return rbuf - buf; 58 | } 59 | 60 | __attribute__ ((__warn_unused_result__)) __attribute__ ((__nonnull__)) 61 | static inline ssize_t do_write_partial(int fd, const uint8_t* buf, size_t len) 62 | { 63 | V(len <= SSIZE_MAX); 64 | ssize_t w; 65 | const uint8_t *wbuf = buf; 66 | do { 67 | VS(w = TEMP_FAILURE_RETRY(write(fd, wbuf, len))); 68 | if (w == 0) 69 | break; 70 | len -= w; 71 | wbuf += w; 72 | } while (len != 0); 73 | return wbuf - buf; 74 | } 75 | 76 | 77 | __attribute__((__nonnull__)) 78 | static inline void do_read(int fd, uint8_t *buf, size_t len) 79 | { 80 | V(do_read_partial(fd, buf, len) == (ssize_t) len); 81 | } 82 | 83 | __attribute__((__nonnull__)) 84 | static inline void do_write(int fd, const uint8_t *buf, size_t len) 85 | { 86 | V(do_write_partial(fd, buf, len) == (ssize_t) len); 87 | } 88 | 89 | 90 | 91 | __attribute__((__nonnull__)) 92 | static inline uint8_t* read_file(const char *filename, size_t *size) 93 | { 94 | int fd; 95 | VS(fd = open(filename, O_RDONLY)); 96 | 97 | struct stat st; 98 | VS(fstat(fd, &st)); 99 | V(st.st_size <= SSIZE_MAX); 100 | *size = (size_t) st.st_size; 101 | 102 | uint8_t *buf = (uint8_t*) malloc(*size); 103 | VE(buf != NULL); 104 | do_read(fd, buf, *size); 105 | VS(close(fd)); 106 | return buf; 107 | } 108 | 109 | 110 | __attribute__((__nonnull__)) 111 | static inline unsigned long explicit_hex_conv(const char *p) 112 | { 113 | /* We just want a 0xAbC123 string, nothing more, nothing less. */ 114 | /* Man, checking this correctly _is_ annoying. */ 115 | 116 | /* We don't want confusion on the base. */ 117 | if (strncmp("0x", p, 2) != 0) 118 | errx(1, "Wrong parameter (%s). Must start with '0x'", p); 119 | if (p[2] == '\0') 120 | errx(1, "Wrong parameter (%s). Just passed '0x'!", p); 121 | p += 2; 122 | 123 | char *endptr; 124 | errno = 0; 125 | int ret = strtoul(p, &endptr, 16); 126 | if (errno != 0) 127 | err(1, "Wrong parameter (%s). Must be 0xAb...", p); 128 | if (*endptr != '\0') 129 | errx(1, "Wrong parameter (%s). Must be 0xAb... (invalid characters start at: %s)", p, endptr); 130 | return ret; 131 | } 132 | 133 | static inline bool overlap(unsigned a1, unsigned a2, unsigned b1, unsigned b2) 134 | { 135 | assert(a1 <= a2); assert(b1 <= b2); 136 | return max(a1,b1) <= min(a2,b2); 137 | } 138 | 139 | static inline void safe_strcat(char *dest, const char *src, size_t dest_size) 140 | { 141 | V(dest_size > strlen(src)); 142 | strcat(dest, src); 143 | } 144 | -------------------------------------------------------------------------------- /utils.nasm: -------------------------------------------------------------------------------- 1 | 2 | %if __BITS__ == 32 3 | 4 | ; printlen x => write(1, x, x_end-x) 5 | %macro print 1 6 | mov eax, 4 ; __NR_write 7 | mov ebx, 1 8 | mov ecx, %1 9 | mov edx, %1 %+ _end - %1 10 | int 0x80 11 | cmp eax, %1 %+ _end - %1 12 | je %%ok 13 | mov eax, 4 14 | mov ebx, 1 15 | mov ecx, write_errmsg 16 | mov edx, write_errmsg_end-write_errmsg 17 | int 0x80 18 | %%ok: 19 | %endmacro 20 | 21 | %macro al_to_hex 1 22 | mov ah, al 23 | and al, 0x0f ; low nibble 24 | cmp al, 10 25 | jb %%oknum 26 | add al, byte 39 ; 'a'-'0'-10 27 | %%oknum: add al, byte '0' 28 | mov [%1+1], al 29 | shr ah, 4 ; high nibble 30 | cmp ah, 10 31 | jb %%oknumh 32 | add ah, byte 39 ; 'a'-'0'-10 33 | %%oknumh: add ah, byte '0' 34 | mov [%1], ah 35 | %endmacro 36 | 37 | %macro dword_to_hex 1 38 | mov ecx, [%1] ; ecx = dword 39 | mov eax, ecx 40 | shr eax, 24 41 | al_to_hex %1 42 | mov eax, ecx 43 | shr eax, 16 44 | al_to_hex %1+2 45 | mov eax, ecx 46 | shr eax, 8 47 | al_to_hex %1+4 48 | mov eax, ecx 49 | al_to_hex %1+6 50 | %endmacro 51 | 52 | 53 | %elif __BITS__ == 64 54 | 55 | %macro print 1 56 | mov rax, 1 ; __NR_write 57 | mov rdi, 1 58 | mov rsi, %1 59 | mov rdx, %1 %+ _end - %1 60 | syscall 61 | cmp rax, %1 %+ _end - %1 62 | je %%ok 63 | mov rax, 1 64 | mov rdi, 1 65 | mov rsi, write_errmsg 66 | mov rdx, write_errmsg_end - write_errmsg 67 | syscall 68 | %%ok: 69 | %endmacro 70 | 71 | 72 | %macro al_to_hex 1 73 | mov ah, al 74 | and al, 0x0f ; low nibble 75 | cmp al, 10 76 | jb %%oknum 77 | add al, byte 39 ; 'a'-'0'-10 78 | %%oknum: add al, byte '0' 79 | mov [%1+1], al 80 | shr ah, 4 ; high nibble 81 | cmp ah, 10 82 | jb %%oknumh 83 | add ah, byte 39 ; 'a'-'0'-10 84 | %%oknumh: add ah, byte '0' 85 | mov [%1], ah 86 | %endmacro 87 | 88 | %macro qword_to_hex 1 89 | mov rcx, [%1] ; rcx = dword 90 | mov rax, rcx 91 | shr rax, 56 92 | al_to_hex %1 93 | mov rax, rcx 94 | shr rax, 48 95 | al_to_hex %1+2 96 | mov rax, rcx 97 | shr rax, 40 98 | al_to_hex %1+4 99 | mov rax, rcx 100 | shr rax, 32 101 | al_to_hex %1+6 102 | mov rax, rcx 103 | shr rax, 24 104 | al_to_hex %1+8 105 | mov rax, rcx 106 | shr rax, 16 107 | al_to_hex %1+10 108 | mov rax, rcx 109 | shr rax, 8 110 | al_to_hex %1+12 111 | mov rax, rcx 112 | al_to_hex %1+14 113 | %endmacro 114 | 115 | %endif ; __BITS__ 116 | --------------------------------------------------------------------------------