wow, this turned out to be a lot more painful than I expected. 100% of the pain was linux protecting the program from being overwritten and/or executing data.
Two solutions shown below. And a lot of googling was involved so the somewhat simple put some instruction bytes and execute them was mine, the mprotect and aligning on page size was culled from google searches, stuff I had to learn for this example.
The self modifying code is straight forward, if you take the program or at least just the two simple functions, compile and then disassemble you will get the opcodes for those instructions. or use nasm to compile blocks of assembler, etc. From this I determined the opcode to load an immediate into eax then return.
Ideally you simply put those bytes in some ram and execute that ram. To get linux to do that you have to change the protection, which means you have to send it a pointer that is aligned on a mmap page. So allocate more than you need, find the aligned address within that allocation that is on a page boundary and mprotect from that address and use that memory to put your opcodes and then execute.
the second example takes an existing function compiled into the program, again because of the protection mechanism you cannot simply point at it and change bytes, you have to unprotect it from writes. So you have to back up to the prior page boundary call mprotect with that address and enough bytes to cover the code to be modified. Then you can change the bytes/opcodes for that function in any way you want (so long as you don't spill over into any function you want to continue to use) and execute it. In this case you can see that fun()
works, then I change it to simply return a value, call it again and now it has been modified.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
unsigned char * testfun;
unsigned int fun(unsigned int a) {
return (a + 13);
}
unsigned int fun2(void) {
return (13);
}
int main(void) {
unsigned int ra;
unsigned int pagesize;
unsigned char * ptr;
unsigned int offset;
pagesize = getpagesize();
testfun = malloc(1023 + pagesize + 1);
if (testfun == NULL) return (1);
//need to align the address on a page boundary
printf("%p\n", testfun);
testfun = (unsigned char * )(((long) testfun + pagesize - 1) & ~(pagesize - 1));
printf("%p\n", testfun);
if (mprotect(testfun, 1024, PROT_READ | PROT_EXEC | PROT_WRITE)) {
printf("mprotect failed\n");
return (1);
}
//400687: b8 0d 00 00 00 mov $0xd,%eax
//40068d: c3 retq
testfun[0] = 0xb8;
testfun[1] = 0x0d;
testfun[2] = 0x00;
testfun[3] = 0x00;
testfun[4] = 0x00;
testfun[5] = 0xc3;
ra = ((unsigned int( * )()) testfun)();
printf("0x%02X\n", ra);
testfun[0] = 0xb8;
testfun[1] = 0x20;
testfun[2] = 0x00;
testfun[3] = 0x00;
testfun[4] = 0x00;
testfun[5] = 0xc3;
ra = ((unsigned int( * )()) testfun)();
printf("0x%02X\n", ra);
printf("%p\n", fun);
offset = (unsigned int)(((long) fun) & (pagesize - 1));
ptr = (unsigned char * )((long) fun & (~(pagesize - 1)));
printf("%p 0x%X\n", ptr, offset);
if (mprotect(ptr, pagesize, PROT_READ | PROT_EXEC | PROT_WRITE)) {
printf("mprotect failed\n");
return (1);
}
//for(ra=0;ra<20;ra++) printf("0x%02X,",ptr[offset+ra]); printf("\n");
ra = 4;
ra = fun(ra);
printf("0x%02X\n", ra);
ptr[offset + 0] = 0xb8;
ptr[offset + 1] = 0x22;
ptr[offset + 2] = 0x00;
ptr[offset + 3] = 0x00;
ptr[offset + 4] = 0x00;
ptr[offset + 5] = 0xc3;
ra = 4;
ra = fun(ra);
printf("0x%02X\n", ra);
return (0);
}
__builtin___clear_cache
on the range (actually just syncs I-cache however is required on the target ISA.) – Ku