Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
313 views
in Technique[技术] by (71.8m points)

polymorphism - Metamorphic Code Examples

I understand the concept of Polymorphic and Metamorphic code but I recently read the Wikipedia page on both (for what ever reason I hadn't done this previously!). Now I really want to have a go at writing some metamorphic code for myself.

I am a master of no language, dabbler of many. I know some PHP, MySQL, c/c++, Java, Bash scripting, Visual Basic 6, VBScripting, Perl, JavaScript.

Can anyone provide an example of metamorphic code in any of these languages. I would like to see a working example, even where the output of the program is just "Hello World", to understand through example how this is happening (I am struggling to theorise how these techniques can be achieved through mental thought alone). Any language would do really, those are just preferred ones.

Additionally, searching the Internet has only returned a limited number of examples in c/c++ (not even complete working examples, more partial snippets of code), is that because the other languages I have suggested aren't low level enough to have the power/flexibility required to make metamorphic code?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Below is an example of what I believe would classify as metamorphic code written in C. I'm afraid I don't have a great deal of experience writing portable C code, so it may require some modification to compile on other platforms (I'm using an old version of Borland on Windows). Also, it relies on the target platform being x86 since it involves some machine code generation. In theory it should compile on any x86 OS though.

How it works

Each time the program is run, it generates a randomly modified copy of itself, with a different filename. It also prints out a list of offsets that have been modified so you can see it actually doing something.

The modification process is very simplistic. The source code is just interpreted with sequences of assembly instructions that effectively do nothing. When the program is run, it finds these sequences and randomly replaces them with different code (which obviously also does nothing).

Hardcoding a list of offsets obviously isn't realistic for something that other people need to be able to compile, so the sequences are generated in a way that makes them easy to identify in a search through the object code, hopefully without matching any false positives.

Each sequence starts with a push operation on a certain register, a set of instructions that modify that register, and then a pop operation to restore the register to its initial value. To keep things simple, in the original source all of the sequences are just PUSH EAX, eight NOPs, and POP EAX. In all subsequent generations of the app, though, the sequences will be entirely random.

Explaining the code

I've split the code up into multiple parts so I can try to explain it step by step. If you want to compile it yourself, you'll just need to join all the parts together.

First some fairly standard includes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

Next we have defines for various x86 opcodes. These will typically be combined with other values to generate a full instruction. For example, the PUSH define (0x50) by itself is PUSH EAX, but you can derive the values for other registers by adding an offset in the range 0 to 7. Same thing for POP and MOV.

#define PUSH 0x50
#define POP  0x58
#define MOV  0xB8
#define NOP  0x90

The last six are the prefix bytes of several two-byte opcodes. The second byte encodes the operands and will be explained in more detail later.

#define ADD  0x01
#define AND  0x21
#define XOR  0x31
#define OR   0x09
#define SBB  0x19
#define SUB  0x29

const unsigned char prefixes[] = { ADD,AND,XOR,OR,SBB,SUB,0 };

JUNK is a macro that inserts our sequence of junk operations anywhere we want in the code. As I explained before, it's initially just writing out PUSH EAX, NOP, and POP EAX. JUNKLEN is the number of NOPs in that sequence - not the full length of the sequence.

And in case you're not aware, __emit__ is a pseudo-function that injects literal values directly into the object code. I suspect it may be something you need to port if you're using a different compiler.

#define JUNK __emit__(PUSH,NOP,NOP,NOP,NOP,NOP,NOP,NOP,NOP,POP)
#define JUNKLEN 8

Some global variables where our code will be loaded. Global variables are bad, but I'm not a particularly good coder.

unsigned char *code;
int codelen;

Next we have a simple function that will read our object code into memory. I never free that memory because I just don't care.

Notice the JUNK macro calls inserted at random points. You're going to see a lot more of these throughout the code. You can insert them almost anywhere, but if you're using a real C compiler (as opposed to C++) it'll complain if you try to put them before or in-between variable declarations.

void readcode(const char *filename) {
  FILE *fp = fopen(filename, "rb");    JUNK;
  fseek(fp, 0L, SEEK_END);             JUNK;
  codelen = ftell(fp);
  code = malloc(codelen);              JUNK;
  fseek(fp, 0L, SEEK_SET);
  fread(code, codelen, 1, fp);         JUNK;
}

Another simple function to write the application out again after it has been modified. For the new filename we just replace the last character of the original filename with a digit that is incremented each time. No attempt is made to check whether the file already exists and that we're not overwriting a crucial piece of the operating system.

void writecode(const char *filename) {
  FILE *fp;
  int lastoffset = strlen(filename)-1;
  char lastchar = filename[lastoffset];
  char *newfilename = strdup(filename);  JUNK;
  lastchar = '0'+(isdigit(lastchar)?(lastchar-'0'+1)%10:0);
  newfilename[lastoffset] = lastchar;
  fp = fopen(newfilename, "wb");         JUNK;
  fwrite(code, codelen, 1, fp);          JUNK;
  fclose(fp);
  free(newfilename);
}

This next function writes out a random instruction for our junk sequence. The reg parameter represents the register we're working with - what will be pushed and popped at either end of the sequence. The offset is the offset in the code where the instruction will be written. And space gives the number of bytes we have left in our sequence.

Depending on how much space we have, we may be restricted to which instructions we can write out, otherwise we choose at random whether it's a NOP, MOV or one of the others. NOP is just a single byte. MOV is five bytes: our MOV opcode (with the reg parameter added), and 4 random bytes representing the number moved into the register.

For the two byte sequences, the first is just one of our prefixes chosen at random. The second is a byte in the range 0xC0 to 0xFF where the least significant 3 bits represent the primary register - i.e. that must be set to the value of our reg parameter.

int writeinstruction(unsigned reg, int offset, int space) {
  if (space < 2) {
    code[offset] = NOP;                         JUNK;
    return 1;
  }
  else if (space < 5 || rand()%2 == 0) {
    code[offset] = prefixes[rand()%6];          JUNK;
    code[offset+1] = 0xC0 + rand()%8*8 + reg;   JUNK;
    return 2;
  }
  else {
    code[offset] = MOV+reg;                     JUNK;
    *(short*)(code+offset+1) = rand();
    *(short*)(code+offset+3) = rand();          JUNK;
    return 5;
  }
}

Now we have the equivalent function for reading back one of these instructions. Assuming we've already identified the reg from the PUSH and POP operations at either end of the sequence, this function can attempt to validate whether the instruction at the given offset is one of our junk operations and that the primary register matches the given reg parameter.

If it finds a valid match, it returns the instruction length, otherwise it returns zero.

int readinstruction(unsigned reg, int offset) {
  unsigned c1 = code[offset];
  if (c1 == NOP)
    return 1;                     JUNK;
  if (c1 == MOV+reg)
    return 5;                     JUNK;
  if (strchr(prefixes,c1)) {
    unsigned c2 = code[offset+1]; JUNK;
    if (c2 >= 0xC0 && c2 <= 0xFF && (c2&7) == reg)
      return 2;                   JUNK;
  }                               JUNK;
  return 0;
}

This next function is the main loop the searches for and replaces the junk sequences. It starts by looking for a PUSH opcode followed by a POP opcode on the same register eight bytes later (or whatever JUNKLEN was set to).

void replacejunk(void) {
  int i, j, inc, space;
  srand(time(NULL));                                 JUNK;

  for (i = 0; i < codelen-JUNKLEN-2; i++) {
    unsigned start = code[i];
    unsigned end = code[i+JUNKLEN+1];
    unsigned reg = start-PUSH;

    if (start < PUSH || start >= PUSH+8) continue;   JUNK;
    if (end != POP+reg) continue;                    JUNK;

If the register turns out to be ESP, we can safely skip it because we'll never use ESP in our generated code (stack operations on ESP need special consideration that isn't worth the effort).

    if (reg == 4) continue; /* register 4 is ESP */

Once we've matched a likely looking PUSH and POP combination, we then try to read the instructions in-between. If we successfully match the length of bytes we're expecting, we consider that a match that can be replaced.

    j = 0;                                           JUNK;
    while (inc = readinstruction(reg,i+1+j)) j += inc;
    if (j != JUNKLEN) continue;                      JUNK;

We then pick one of 7 registers at random (as explained before we don't consider ESP), and write out the PUSH and POP operations for that register at either end of the sequence.

    reg = rand()%7;                                  JUNK;
    reg += (reg >= 4);
    code[i] = PUSH+reg;                              JUNK;
    code[i+JUNKLEN+1] = POP+reg;                     JUNK;

Then all we need to do is fill in the space in-between using our writeinstruction function.

    space = JUNKLEN;
    j = 0;                                           JUNK;
    while (space) {
      inc = writeinstruction(reg,i+1+j,space);       JUNK;
      j += inc;
      space -= inc;                                  JUNK;
    }

And here's where we display the offset that we just patched.

    printf("%d
",i);                                JUNK;
  }
}                                                                             

Finally we have the main function. This just calls the functions previously described. We read in the code, replace the junk, then write it out again. The argv[0] argument contains the application filename.

int main(int argc, char* argv[]) {

  readcode(argv[0]);     JUNK;
  replacejunk();         JUNK;
  writecode(argv[0]);    JUNK;

  return 0;
}

And that's all there is to it.

Some final notes

When running this code, obviously you need to make sure the user has the appropriate permissions to write out a file in the same location as the original code. Then once the new file has been generated, you'll typically need to rename it if you're on a system where the file extension is important, or set its execute attributes if that is needed.

Finally, I suspect you may want to run the generated code through a debugger rather than just executing it directly and hoping for the best. I found that if I copied the generated file over the original executable, the debugger was happy to let me step through it while still viewing the original source code. Then whenever you get to a point in the code that says JUNK, you can pop into the assembly view and look at the code that has been generated.

A


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...