Last active
August 12, 2025 09:23
-
-
Save timosarkar/eb82564d0d03f2fdb956ff3f3f2d49bd to your computer and use it in GitHub Desktop.
metamorphic engine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //gcc full.c -lcapstone -lelf -lm -lstdc++ -lkeystone -lm -lstdc++ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <fcntl.h> | |
| #include <unistd.h> | |
| #include <sys/mman.h> | |
| #include <capstone/capstone.h> | |
| #include <keystone/keystone.h> | |
| #include <elf.h> | |
| #include <string.h> | |
| #include <inttypes.h> | |
| #include <time.h> | |
| typedef struct { | |
| const char *original; | |
| const char *alternatives[4]; // NULL-terminated | |
| } SubRule; | |
| SubRule rules[] = { | |
| { "xor eax, eax", { "sub eax, eax", "mov eax, 0", NULL } }, | |
| { "nop", { "xchg eax, eax", NULL } }, | |
| { "inc eax", { "add eax, 1", NULL } }, | |
| { "dec eax", { "sub eax, 1", NULL } }, | |
| { NULL, { NULL } } | |
| }; | |
| const char *get_substitute(const char *mnemonic, const char *op_str) { | |
| char full[64]; | |
| snprintf(full, sizeof(full), "%s %s", mnemonic, op_str); | |
| for (int i = 0; rules[i].original; i++) { | |
| if (strcmp(rules[i].original, full) == 0) { | |
| int count = 0; | |
| while (rules[i].alternatives[count]) count++; | |
| return rules[i].alternatives[rand() % count]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| int main() { | |
| srand(time(NULL)); | |
| int fd = open("/proc/self/exe", O_RDONLY); | |
| off_t size = lseek(fd, 0, SEEK_END); | |
| uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); | |
| close(fd); | |
| Elf64_Ehdr *ehdr = (Elf64_Ehdr *)data; | |
| Elf64_Shdr *shdr = (Elf64_Shdr *)(data + ehdr->e_shoff); | |
| const char *shstrtab = (char *)(data + shdr[ehdr->e_shstrndx].sh_offset); | |
| for (int i = 0; i < ehdr->e_shnum; i++) { | |
| if (strcmp(&shstrtab[shdr[i].sh_name], ".text") == 0) { | |
| uint8_t *code = data + shdr[i].sh_offset; | |
| size_t text_size = shdr[i].sh_size; | |
| uint64_t text_addr = shdr[i].sh_addr; | |
| // Make .text writable | |
| uintptr_t page_start = (uintptr_t)code & ~(uintptr_t)(0xFFF); | |
| size_t page_size = ((text_size + 0xFFF) & ~(size_t)(0xFFF)); | |
| if (mprotect((void *)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { | |
| perror("mprotect"); | |
| return 1; | |
| } | |
| csh handle; | |
| cs_insn *insn; | |
| size_t count; | |
| cs_open(CS_ARCH_X86, CS_MODE_64, &handle); | |
| count = cs_disasm(handle, code, text_size, text_addr, 0, &insn); | |
| ks_engine *ks; | |
| ks_open(KS_ARCH_X86, KS_MODE_64, &ks); | |
| for (size_t j = 0; j < count; j++) { | |
| const char *sub = get_substitute(insn[j].mnemonic, insn[j].op_str); | |
| if (sub) { | |
| unsigned char *encode; | |
| size_t enc_size, enc_count; | |
| if (ks_asm(ks, sub, insn[j].address, &encode, &enc_size, &enc_count) == KS_ERR_OK) { | |
| if (enc_size <= insn[j].size) { | |
| size_t offset = insn[j].address - text_addr; | |
| memcpy(code + offset, encode, enc_size); | |
| memset(code + offset + enc_size, 0x90, insn[j].size - enc_size); // NOP padding | |
| printf("0x%"PRIx64": %s %s → %s\n", insn[j].address, insn[j].mnemonic, insn[j].op_str, sub); | |
| } else { | |
| printf("Skipping substitution at 0x%"PRIx64": new instruction too large\n", insn[j].address); | |
| } | |
| ks_free(encode); | |
| } | |
| } | |
| } | |
| ks_close(ks); | |
| cs_free(insn, count); | |
| cs_close(&handle); | |
| break; | |
| } | |
| } | |
| munmap(data, size); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment