Skip to content

Instantly share code, notes, and snippets.

@timosarkar
Last active August 12, 2025 09:23
Show Gist options
  • Select an option

  • Save timosarkar/eb82564d0d03f2fdb956ff3f3f2d49bd to your computer and use it in GitHub Desktop.

Select an option

Save timosarkar/eb82564d0d03f2fdb956ff3f3f2d49bd to your computer and use it in GitHub Desktop.
metamorphic engine
//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