• Привет !

    На форуме есть зеркало в ТОРе:rusfwz3cukdej7do.onion

    Обратная связь:info@ru-sfera.org

    Всего доброго !

Малварь как искусство Самомодификация и генерация кода на лету в си. Часть 1. Вкуриваем суть. (1 Viewer)

Кто просматривает этот контент: "Тема" (Всего пользователей: 0; Гостей: 1)

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
5 412
Репутация
7 901
Telegram


Всем привет ! :)

Осень, дерьмовая погода и настроение жесть.

Что-бы как-то поднять настроение, решил выпить ведро водки глянуть что-тут у нас с модификацией и генерацией кода на "лету".

Гуглил, находил книги Криса, который говорил про исполнение кода на стеке, но чот так и не получилось у меня.

Ибо грёбанная винда, блокирует доступ к стеку, хотя возможно руки вырасли из жопы. Но не суть в принципе можно сделать что-то такое в кучи.

Давайте разбираться, итак как её, ах-да теория. Короче немного тут будет:

В си есть прикольная штука, называется указатель на функцию. Смысл такой, ты делаешь какой-то абстрактный тип данных, и говоришь, "Это функция" и далее можешь использовать этот тип данных вызывая различные функции, с разным функционалом, например:
Код:
typedef LONG (*FunctionPointer) (LONG, LONG);
FunctionPointer наш тип данных, который указывает на функцию с параметрами (LONG, LONG).

Далее например если у вас есть функция например функция сложения а+в, вы можете вызвать её. При помощи этого указателя, вот пример:

FunctionPointer Summa; //Объявляем переменную с типом FunctionPointer (наш указатель на функцию)

Допустим есть функция, которая складывает два числа:

int tst_summ (LONG a, LONG b)
{
return a+b;
}

Далее мы можем вызвать нашу функцию например так:

Summa = tst_summ;
Result = Summa (1,2);

Ну думаю понятно, что это нужно когда например в разных ситуациях выполнялся разный код, например я это использую как абстрактный тип данных при написании драйверов для различных девайсов. Например, функции чтения с различных устройств (флешки, диски и т.д.) всегда разные, но при этом можно сделать удобный интерфейс для взаимодействия, т.е. пользователь будет дёргать одну функцию, например read, а она уже будет вызывать нужную функцию чтения для нужно флешки. :)

Ну это ладно с теорией почти закончено, а что если мы вообще не будет никак заполнять функцию, а заполним её в процессе работы программы, или например пошифруем тело нашей функции, а далее выполним код в процессе выполнения программы ?

Как можно это сделать:

Итак, давайте вначале в этой статье, немного о простом. А в следующей части уже зашифруем код и исполним его в своей программе. Это нужно например для скрытия своей вредоносной нагрузки, или шеллов. А может и вовсе просто хотите скрыть код от палева. :)

Итак теперь немного про ассемблер и машинный код:

Все вы знаете, что ассемблерные команды преобразуются в машинный код после компиляции, это можно посмотреть в любом отладчике, открыв нативную программу, например в оледбг:

upload_2017-11-11_16-16-56.png


Тут левая крайняя колонка, это адрес, правее как раз машинный код, право крайняя колонка это ассемблерный код.

Итак как вы думаете что будет, если мы создадим указатель на функцию, в которой генерируются эти машинные коды ? :)

Ога, они исполнятся в программе.

Давайте попробуем это реализовать, простая функция сложения двух чисел:

1)Создаём абстрактный тип данных:
Код:
typedef LONG (*FunctionPointer) (LONG, LONG);
2)Выделяем память для функции, которую будем генерировать "на лету":
Код:
FunctionPointer Summa;

Summa = (FunctionPointer) malloc (11);
3)Заполняем выделенную память опкодами:
Код:
    ((LPBYTE) Summa)[0] =  0x55; // push  bp
    ((LPBYTE) Summa)[1] =  0x8B; // mov ebp, esp
    ((LPBYTE) Summa)[2] =  0xEC;
    ((LPBYTE) Summa)[3] =  0x8B; // mov eax, [bp+8]
    ((LPBYTE) Summa)[4] =  0x45;
    ((LPBYTE) Summa)[5] =  0x08;
    ((LPBYTE) Summa)[6] =  0x03; // add eax, [bp+12]
    ((LPBYTE) Summa)[7] =  0x45;
    ((LPBYTE) Summa)[8] =  0x0C;
    ((LPBYTE) Summa)[9] =  0x5D; // pop ebp
    ((LPBYTE) Summa)[10] = 0xC3; // ret eax
Наконец, можно вызвать функцию VirtualProtect(). Все наши команды на месте, нам нужно просто разрешить выполнить их. Следующая команда сделает это:
Код:
     DWORD oldprotect;
    VirtualProtect(Summa, 11, PAGE_EXECUTE, &oldprotect);
4)В общем-то и всё, можно запускать нашу функцию:
Код:
int sum = Summa(1, 2);
printf (" Summa = %d \n", sum);
Получаем:

upload_2017-11-11_16-25-1.png


Код программы на си (можно собрать как консольное приложение в вижуалке):

Код:
#include "stdafx.h"
#include "windows.h"

typedef LONG (*FunctionPointer) (LONG, LONG);

int _tmain(int argc, _TCHAR* argv[])
{
    FunctionPointer Summa;
    Summa = (FunctionPointer) malloc (11);
    
    ((LPBYTE) Summa)[0] =  0x55; // push ebp
    ((LPBYTE) Summa)[1] =  0x8B; // mov ebp, esp
    ((LPBYTE) Summa)[2] =  0xEC;
    ((LPBYTE) Summa)[3] =  0x8B; // mov eax, [bp+8]
    ((LPBYTE) Summa)[4] =  0x45;
    ((LPBYTE) Summa)[5] =  0x08;
    ((LPBYTE) Summa)[6] =  0x03; // add eax, [bp+12]
    ((LPBYTE) Summa)[7] =  0x45;
    ((LPBYTE) Summa)[8] =  0x0C;
    ((LPBYTE) Summa)[9] =  0x5D; // pop ebp
    ((LPBYTE) Summa)[10] = 0xC3; // ret eax
    
    DWORD oldprotect;
    VirtualProtect(Summa, 11, PAGE_EXECUTE, &oldprotect);

    int sum = Summa(1, 2);

    printf (" Summa = %d \n", sum);

    while (1);
    return 0;
}

Всё работает ! :)

В следующей части, зашифруем код и исполним его после расшифровки на "лету".

Всем удачи ! :)
 

Вложения

X-Shar

:)
Администрация
Регистрация
03.06.2012
Сообщения
5 412
Репутация
7 901
Telegram