• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Молчи и скрывайся. Прячем IAT от антивируса

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
Зоркий глаз антивируса так и норовит обнаружить наши пейлоады! Давай покажу, как скрыть все импорты, чтобы спрятаться от назойливого внимания антивирусных программ. Мы попробуем мимикрировать под легитимное приложение с помощью IAT Camouflage.

Все написанные под Windows программы имеют IAT (Import Address Table) — это таблица, которая содержит функции, импортируемые программой из DLL-библиотек. Зачастую нам, как атакующим, следует скрывать импорты, чтобы антивирусным приложениям было сложнее идентифицировать используемые в программе функции. В этой статье разобраны некоторые способы скрытия таблицы импортов.

ПРОСТЕЙШЕЕ СКРЫТИЕ​

Итак, пусть у нас есть простенькая программа, которая, например, выводит MessageBox.
C++:
#include <Windows.h>
int main() {
    MessageBox(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Программа с MessageBox

Если мы посмотрим на то, что лежит у нее в IAT, то будем неприятно удивлены. Здесь и CRT, и какие‑то левые функции, которые мы даже не вызываем.

Код:
dumpbin /imports .\Article.exe

Огромный IAT

Предлагаю сразу же избавиться от CRT. Common Language Runtime — набор функций и макросов для программ на языке С. Функции обычно связаны с управлением памятью (memcpy()), открытием и закрытием файлов (fopen()) и работой со строками (strcpy()).

Библиотеки DLL, которые реализуют CRT, называются vcruntimeXXX.dll, где XXX — номер версии используемой библиотеки CRT. Это правило применяется не ко всем библиотекам CRT, встречаются также DLL c именами api-ms-win-crt-stdio-l1-1-0.dll, api-ms-win-crt-runtime-l1-1-0.dll и api-ms-win-crt-locale-l1-1-0.dll. Каждая DLL служит для определенной цели и экспортирует несколько функций. Эти библиотеки DLL компонуются компилятором во время компиляции и поэтому находятся в IAT сгенерированных программ.

Чтобы заставить компилятор сделать статическую линковку (в этом случае библиотека не импортируется, а уже как бы зашита в программу), следует изменить свойства проекта. Сначала открываем раздел в «Проект → Свойства».

img3.png

Оттуда переходим в «C/C++ → Создание кода → Библиотека времени выполнения → Многопоточная (/MT) → Применить → ОК».

Настройки проекта

Затем перекомпилируем проект и проверяем IAT.

IAT без CRT

Отлично, от CRT в IAT мы избавились. Теперь пришло время поговорить о скрытии импорта функции MessageBoxW(). Сама функция представлена в библиотеке user32.dll, поэтому мы можем использовать API GetProcAddress() для получения адреса этой функции и ее вызова.
Код:
FARPROC GetProcAddress(
  [in] HMODULE hModule,
  [in] LPCSTR  lpProcName
);
Здесь hModule — хендл на DLL, в которой реализована функция, а lpProcName — имя этой функции. Для успешного вызова MessageBoxW() нужно лишь создать прототип функции в нашем коде.
C++:
#include <Windows.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
int main() {
    HMODULE user32Module = LoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(GetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Компилируем, проверяем IAT и видим, что функция пропала.
Пропавшая MessageBox

Казалось бы, неплохо? Всем спасибо, конец статьи.

Но не тут‑то было. Давай настроим компилятор так, чтобы он импортировал не все функции из kernel32.dll, а лишь нужные, то есть те, которые в явном виде присутствуют в коде. Для этого сначала отключаем SDL.

img7.png

Затем отключаем оптимизацию программы.

img8.png

Отключаем исключения C++, делаем статическую линковку, отключаем проверку безопасности.

img9.png

Включаем игнорирование стандартных библиотек.

img10.png

Отключаем файл манифеста, убираем создание дебаг‑информации.

img11.png


img12.png



Затем устанавливаем точку входа.

img13.png

Перекомпилируем проект, проверяем IAT и видим, что любой желающий сможет определить использование LoadLibrary() и GetProcAddress() в нашем коде.

img14.png

От этих функций никак не избавиться (через GetProcAddress() тем более не вызвать — рекурсия), поэтому придется придумать какую‑то альтернативу.

СОБСТВЕННЫЕ LOADLIBRARY() И GETPROCADDRESS()​

Начнем с написания собственного GetProcAddress(). У каждой DLL-библиотеки есть раздел EAT (Export Address Table), в котором содержатся экспортируемые из этой библиотеки функции. Буквально — методы, которые могут быть вызваны при включении этой DLL в программу.

Просмотреть экспорты позволяет тот же dumpbin, но с флагом /exports.
Код:
dumpbin /exports C:\Windows\System32\user32.dll
Экспорты user32.dll

Нам нужно лишь как‑то получить базовый адрес загрузки библиотеки, а затем от него можно добраться до EAT. Пока для простоты эксперимента базовый адрес загрузки предлагаю получать через LoadLibrary(). Эта функция позволяет загрузить библиотеку в текущий процесс, а затем получить на нее хендл.
Код:
HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);
Этот хендл является базовым адресом загрузки DLL в память процесса. После получения базового адреса следует начать парсить EAT, до него можно добраться по пути IMAGE_DOS_HEADER → IMAGE_NT_HEADERS → IMAGE_OPTIONAL_HEADER → IMAGE_DATA_DIRECTORY → IMAGE_EXPORT_DIRECTORY. Подробнее о парсинге PE можно прочитать в различных публикациях, вот несколько ссылок.

WWW​

Теперь нырнем в пучины интернета и поищем кастомную реализацию GetProcAddress(). Я выбрал самый аккуратный и милый вариант.
C++:
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
  PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
  PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
  PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
  ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
  DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
  WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
  DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
  for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
    if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
      return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
    }
  }
  return NULL;
}
Функция достаточно проста: принимает базовый адрес загрузки библиотеки, а также имя функции, адрес которой надо получить. Работает так, как нам и нужно, — путем парсинга EAT.

Помнишь, мы отключили CRT? Поэтому использование функции strcmp() из этого кода невозможно. К счастью, сравнение двух строк — базовый алгоритм, который пишут еще на паскале в седьмом классе. На C++ я вынес его в отдельную функцию custom_strcmp().
C++:
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
Разобравшись, как и что делать, получаем следующий код.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
int main() {
    HMODULE user32Module = LoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Компилируем, запускаем. Видим, что остался лишь один импорт.

Лишь один импорт

Чем же заменить LoadLibrary()? Если мы глянем на последовательность вызовов функций, то увидим, что LoadLibrary() вызывает LdrLoadDll(), она — LdrpLoadDll(), а та — еще одну функцию... Процесс, скажу честно, достаточно сложный. Вот картинка, которая хорошо описывает последовательность вызовов при загрузке DLL.

Последовательность вызовов при загрузке DLL

Предлагаю не распутывать весь этот клубок, а просто вызвать LdrLoadDll(). Функция представлена в ntdll.dll, а эта библиотека присутствует в любом процессе в Windows. Абсолютно в любом. Теперь проблема другая — откуда взять базовый адрес загрузки ntdll.dll? Здесь нам на помощь придет функция GetModuleHandle(). Эта функция принимает лишь один параметр — имя DLL, а возвращает хендл. Хендл как раз таки будет базовым адресом загрузки библиотеки.
Код:
HMODULE GetModuleHandleA(
  [in, optional] LPCSTR lpModuleName
);
Что ж, сказано — сделано, прототип функции LdrLoadDll() небольшой, параметры практически те же, что и у LoadLibrary(). Их просто чуть больше.
Код:
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
В этом коде мы сначала получаем базовый адрес ntdll.dll, а затем через наш кастомный GetProcAddress() находим адреса нужных функций, после чего загружаем DLL в процесс. Итоговый код программы стал чуть больше.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
    PWCHAR PathToFile,
    ULONG Flags,
    PUNICODE_STRING ModuleFileName,
    PHANDLE ModuleHandle
    );
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
int main() {
    HMODULE user32Module = MyLoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Вновь компилируем, запускаем.

Опять один импорт

Вот незадача, я снова забыл, что нельзя напрямую вызывать никакие функции. Продолжаем мозговой штурм. Что делает GetModuleHandle()? Функция принимает лишь имя библиотеки, а возвращает ее базовый адрес. Такую информацию можно достать из PEB (Process Environment Block). Вновь обращаемся к интернету и находим там нужный код.
C++:
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
  PEB* pPeb = (PEB*)__readgsqword(0x60);
  // For x86
  // PEB* pPeb = (PEB*)__readgsqword(0x30);
  PEB_LDR_DATA* Ldr = pPeb->Ldr;
  LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
  LIST_ENTRY* pStartListEntry = ModuleList->Flink;
  WCHAR mystr[MAX_PATH] = { 0 };
  WCHAR substr[MAX_PATH] = { 0 };
  for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
    LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
    memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
    memset(substr, 0, MAX_PATH * sizeof(WCHAR));
    wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
    wcscpy_s(substr, MAX_PATH, lModuleName);
    if (cmpUnicodeStr(substr, mystr)) {
      return (HMODULE)pEntry->DllBase;
    }
  }
  // The needed DLL wasn’t found
  printf("failed to get a handle to %s\n", lModuleName);
  return NULL;
}
Проблема в том, что здесь используются функции либо из CRT, либо те, которые будут отражаться в IAT. Заменяем memset().
C++:
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
    unsigned char* p = (unsigned char*)Destination;
    while (Size > 0) {
        *p = (unsigned char)Value;
        p++;
        Size--;
    }
    return Destination;
}
Код моей кастомной функции прост: идет копирование в память нужного значения. Теперь заменяем wcscpy_s().
C++:
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
    wchar_t* originalDest = dest;
    while (count > 0) {
        *dest = *src;
        if (*src == L'\0') {
            break;
        }
        dest++;
        src++;
        count--;
    }
    while (count > 0) {
        *dest = L'\0';
        dest++;
        count--;
    }
    return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
    if (str == nullptr) {
        return 0;
    }
    size_t length = 0;
    while (*str != L'\0') {
        length++;
        str++;
    }
    return length;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
    if (dest == nullptr || src == nullptr) {
        return EINVAL;
    }
    size_t srcLength = custom_wcslen(src);
    if (destSize < srcLength + 1) {
        return ERANGE;
    }
    custom_wcsncpy(dest, src, destSize);
    dest[srcLength] = L'\0';
    return 0;
}
Здесь чуть сложнее: сначала идет проверка на размер, после чего подсчет длины (увеличение счетчика до тех пор, пока не будет встречен null byte — конец строки), а затем копирование в память.

Для сравнения двух Unicode-строк автор использует функцию из Shlwapi.
C++:
int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {
  _wcslwr_s(substr, MAX_PATH);
  _wcslwr_s(mystr, MAX_PATH);
  int result = 0;
  if (StrStrW(mystr, substr) != NULL) {
    result = 1;
  }
  return result;
}
Мы такой роскошью не обладаем, поэтому вновь пишем код самостоятельно. Предлагаю просто по аналогии со strcmp() заменить wcscmp().
C++:
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
Эта функция вернет 0, если строки равны. Немного подебажив, я увидел, что в PEB ntdll представлен как C:\Windows\SYSTEM32\ntdll.dll, поэтому такое же значение и передаем нашей функции.

Код:
HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");

Итоговый код проекта стал еще больше. Теперь для стандартного MessageBox() нужно целых 176 строк.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
    PWCHAR PathToFile,
    ULONG Flags,
    PUNICODE_STRING ModuleFileName,
    PHANDLE ModuleHandle
    );
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
    wchar_t* originalDest = dest;
    while (count > 0) {
        *dest = *src;
        if (*src == L'\0') {
            break;
        }
        dest++;
        src++;
        count--;
    }
    while (count > 0) {
        *dest = L'\0';
        dest++;
        count--;
    }
    return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
    if (str == nullptr) {
        return 0;
    }
    size_t length = 0;
    while (*str != L'\0') {
        length++;
        str++;
    }
    return length;
}
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
    if (dest == nullptr || src == nullptr) {
        return EINVAL;
    }
    size_t srcLength = custom_wcslen(src);
    if (destSize < srcLength + 1) {
        return ERANGE;
    }
    custom_wcsncpy(dest, src, destSize);
    dest[srcLength] = L'\0';
    return 0;
}
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
    unsigned char* p = (unsigned char*)Destination;
    while (Size > 0) {
        *p = (unsigned char)Value;
        p++;
        Size--;
    }
    return Destination;
}
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
    PEB* pPeb = (PEB*)__readgsqword(0x60);
    // For x86
    // PEB* pPeb = (PEB*)__readgsqword(0x30);
    PEB_LDR_DATA* Ldr = pPeb->Ldr;
    LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
    LIST_ENTRY* pStartListEntry = ModuleList->Flink;
    WCHAR mystr[MAX_PATH] = { 0 };
    WCHAR substr[MAX_PATH] = { 0 };
    for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
        LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
        custom_memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
        custom_memset(substr, 0, MAX_PATH * sizeof(WCHAR));
        custom_wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
        custom_wcscpy_s(substr, MAX_PATH, lModuleName);
        if (custom_wcscmp(substr, mystr) == 0) {
            // Returning the DLL base address
            return (HMODULE)pEntry->DllBase;
        }
    }
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
int main() {
    HMODULE user32Module = MyLoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Наше чудо заработало!

Наконец в IAT стало пусто.

Пустой IAT

Причем обрати внимание, что у тебя компилятор может выдавать ошибку LNK1120.
Код:
1>Source.obj : error LNK2001: неразрешенный внешний символ memset.
1>A:\SSD\ProjectsVS\Article\x64\Release\Article.exe : fatal error LNK1120: неразрешенных внешних элементов: 1
Если это произошло, открывай раздел «Проект → Свойства» и отключай оптимизацию.
Отключение оптимизации

Осталось лишь навести немного красоты. Ведь это очень подозрительно, если у программы пустой IAT. Займемся IAT Camouflage.

IAT CAMOUFLAGE​

Некоторые антивирусы могут повесить на нас детект просто потому, что мы не вызываем никаких функций, поскольку у нас пустой IAT. Предлагаю добавить парочку фиктивных вызовов функций, которые ни к чему не приводят. При этом в IAT, само собой, появятся записи о том, что используются те или иные функции.

Добавим, например, код, который никогда не выполнится.
C++:
int main() {
  HMODULE user32Module = MyLoadLibrary(L"user32.dll");
  MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
  MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
  int m = 4;
  if (m > 5) {
      unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
      i = GetLastError();
      i = SetCriticalSectionSpinCount(NULL, NULL);
      i = GetWindowContextHelpId(NULL);
      i = GetWindowLongPtrW(NULL, NULL);
      i = RegisterClassW(NULL);
      i = IsWindowVisible(NULL);
      i = ConvertDefaultLocale(NULL);
      i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
      i = IsDialogMessageW(NULL, NULL);
  }
  return 0;
}
Обрати внимание, что компиляторы нынче достаточно умные, поэтому нужно будет отключить оптимизацию, как описано выше. В противном случае компилятор увидит, что этот кусок кода никогда не срабатывает, и исключит его из исполняемого файла.

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


ПСЕВДОХЕНДЛЫ​

Наконец, на десерт хочется упомянуть псевдохендлы. Псевдохендлами считаются фиктивные хендлы на какой‑либо текущий ресурс. Например, вызов GetCurrentProcess() не получится ничем заменить, но вместо него можно использовать псевдохендл. Система Windows, получив такой хендл, поймет, что ей нужно обратиться к процессу, из которого была вызвана функция. Таким образом, для получения хендла, например, на текущий процесс, достаточно передавать значение -1. И Windows определит, что это хендл на текущий процесс.

Можно даже в проекте создать маленькие макросы, которые будут выполнять эту работу автоматически.
Код:
#define NtCurrentProcess() ((HANDLE)-1) // Получить хендл на текущий процесс
#define NtCurrentThread()  ((HANDLE)-2) // Получить хендл на текущий поток

ВЫВОДЫ​

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

Автор @MichelleVermishelle | TG: @Michaelzhm
Источник xakep.ru
 
Зоркий глаз антивируса так и норовит обнаружить наши пейлоады! Давай покажу, как скрыть все импорты, чтобы спрятаться от назойливого внимания антивирусных программ. Мы попробуем мимикрировать под легитимное приложение с помощью IAT Camouflage.

Все написанные под Windows программы имеют IAT (Import Address Table) — это таблица, которая содержит функции, импортируемые программой из DLL-библиотек. Зачастую нам, как атакующим, следует скрывать импорты, чтобы антивирусным приложениям было сложнее идентифицировать используемые в программе функции. В этой статье разобраны некоторые способы скрытия таблицы импортов.

ПРОСТЕЙШЕЕ СКРЫТИЕ​

Итак, пусть у нас есть простенькая программа, которая, например, выводит MessageBox.
C++:
#include <Windows.h>
int main() {
    MessageBox(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Программа с MessageBox

Если мы посмотрим на то, что лежит у нее в IAT, то будем неприятно удивлены. Здесь и CRT, и какие‑то левые функции, которые мы даже не вызываем.

Код:
dumpbin /imports .\Article.exe

Огромный IAT

Предлагаю сразу же избавиться от CRT. Common Language Runtime — набор функций и макросов для программ на языке С. Функции обычно связаны с управлением памятью (memcpy()), открытием и закрытием файлов (fopen()) и работой со строками (strcpy()).

Библиотеки DLL, которые реализуют CRT, называются vcruntimeXXX.dll, где XXX — номер версии используемой библиотеки CRT. Это правило применяется не ко всем библиотекам CRT, встречаются также DLL c именами api-ms-win-crt-stdio-l1-1-0.dll, api-ms-win-crt-runtime-l1-1-0.dll и api-ms-win-crt-locale-l1-1-0.dll. Каждая DLL служит для определенной цели и экспортирует несколько функций. Эти библиотеки DLL компонуются компилятором во время компиляции и поэтому находятся в IAT сгенерированных программ.

Чтобы заставить компилятор сделать статическую линковку (в этом случае библиотека не импортируется, а уже как бы зашита в программу), следует изменить свойства проекта. Сначала открываем раздел в «Проект → Свойства».

img3.png

Оттуда переходим в «C/C++ → Создание кода → Библиотека времени выполнения → Многопоточная (/MT) → Применить → ОК».

Настройки проекта

Затем перекомпилируем проект и проверяем IAT.

IAT без CRT

Отлично, от CRT в IAT мы избавились. Теперь пришло время поговорить о скрытии импорта функции MessageBoxW(). Сама функция представлена в библиотеке user32.dll, поэтому мы можем использовать API GetProcAddress() для получения адреса этой функции и ее вызова.
Код:
FARPROC GetProcAddress(
  [in] HMODULE hModule,
  [in] LPCSTR  lpProcName
);
Здесь hModule — хендл на DLL, в которой реализована функция, а lpProcName — имя этой функции. Для успешного вызова MessageBoxW() нужно лишь создать прототип функции в нашем коде.
C++:
#include <Windows.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
int main() {
    HMODULE user32Module = LoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(GetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Компилируем, проверяем IAT и видим, что функция пропала.
Пропавшая MessageBox

Казалось бы, неплохо? Всем спасибо, конец статьи.

Но не тут‑то было. Давай настроим компилятор так, чтобы он импортировал не все функции из kernel32.dll, а лишь нужные, то есть те, которые в явном виде присутствуют в коде. Для этого сначала отключаем SDL.

img7.png

Затем отключаем оптимизацию программы.

img8.png

Отключаем исключения C++, делаем статическую линковку, отключаем проверку безопасности.

img9.png

Включаем игнорирование стандартных библиотек.

img10.png

Отключаем файл манифеста, убираем создание дебаг‑информации.

img11.png


img12.png



Затем устанавливаем точку входа.

img13.png

Перекомпилируем проект, проверяем IAT и видим, что любой желающий сможет определить использование LoadLibrary() и GetProcAddress() в нашем коде.

img14.png

От этих функций никак не избавиться (через GetProcAddress() тем более не вызвать — рекурсия), поэтому придется придумать какую‑то альтернативу.

СОБСТВЕННЫЕ LOADLIBRARY() И GETPROCADDRESS()​

Начнем с написания собственного GetProcAddress(). У каждой DLL-библиотеки есть раздел EAT (Export Address Table), в котором содержатся экспортируемые из этой библиотеки функции. Буквально — методы, которые могут быть вызваны при включении этой DLL в программу.

Просмотреть экспорты позволяет тот же dumpbin, но с флагом /exports.
Код:
dumpbin /exports C:\Windows\System32\user32.dll
Экспорты user32.dll

Нам нужно лишь как‑то получить базовый адрес загрузки библиотеки, а затем от него можно добраться до EAT. Пока для простоты эксперимента базовый адрес загрузки предлагаю получать через LoadLibrary(). Эта функция позволяет загрузить библиотеку в текущий процесс, а затем получить на нее хендл.
Код:
HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);
Этот хендл является базовым адресом загрузки DLL в память процесса. После получения базового адреса следует начать парсить EAT, до него можно добраться по пути IMAGE_DOS_HEADER → IMAGE_NT_HEADERS → IMAGE_OPTIONAL_HEADER → IMAGE_DATA_DIRECTORY → IMAGE_EXPORT_DIRECTORY. Подробнее о парсинге PE можно прочитать в различных публикациях, вот несколько ссылок.

WWW​

Теперь нырнем в пучины интернета и поищем кастомную реализацию GetProcAddress(). Я выбрал самый аккуратный и милый вариант.
C++:
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
  PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
  PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
  PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
  ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
  DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
  WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
  DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
  for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
    if (strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
      return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
    }
  }
  return NULL;
}
Функция достаточно проста: принимает базовый адрес загрузки библиотеки, а также имя функции, адрес которой надо получить. Работает так, как нам и нужно, — путем парсинга EAT.

Помнишь, мы отключили CRT? Поэтому использование функции strcmp() из этого кода невозможно. К счастью, сравнение двух строк — базовый алгоритм, который пишут еще на паскале в седьмом классе. На C++ я вынес его в отдельную функцию custom_strcmp().
C++:
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
Разобравшись, как и что делать, получаем следующий код.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
int main() {
    HMODULE user32Module = LoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Компилируем, запускаем. Видим, что остался лишь один импорт.

Лишь один импорт

Чем же заменить LoadLibrary()? Если мы глянем на последовательность вызовов функций, то увидим, что LoadLibrary() вызывает LdrLoadDll(), она — LdrpLoadDll(), а та — еще одну функцию... Процесс, скажу честно, достаточно сложный. Вот картинка, которая хорошо описывает последовательность вызовов при загрузке DLL.

Последовательность вызовов при загрузке DLL

Предлагаю не распутывать весь этот клубок, а просто вызвать LdrLoadDll(). Функция представлена в ntdll.dll, а эта библиотека присутствует в любом процессе в Windows. Абсолютно в любом. Теперь проблема другая — откуда взять базовый адрес загрузки ntdll.dll? Здесь нам на помощь придет функция GetModuleHandle(). Эта функция принимает лишь один параметр — имя DLL, а возвращает хендл. Хендл как раз таки будет базовым адресом загрузки библиотеки.
Код:
HMODULE GetModuleHandleA(
  [in, optional] LPCSTR lpModuleName
);
Что ж, сказано — сделано, прототип функции LdrLoadDll() небольшой, параметры практически те же, что и у LoadLibrary(). Их просто чуть больше.
Код:
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
В этом коде мы сначала получаем базовый адрес ntdll.dll, а затем через наш кастомный GetProcAddress() находим адреса нужных функций, после чего загружаем DLL в процесс. Итоговый код программы стал чуть больше.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
    PWCHAR PathToFile,
    ULONG Flags,
    PUNICODE_STRING ModuleFileName,
    PHANDLE ModuleHandle
    );
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
int main() {
    HMODULE user32Module = MyLoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Вновь компилируем, запускаем.

Опять один импорт

Вот незадача, я снова забыл, что нельзя напрямую вызывать никакие функции. Продолжаем мозговой штурм. Что делает GetModuleHandle()? Функция принимает лишь имя библиотеки, а возвращает ее базовый адрес. Такую информацию можно достать из PEB (Process Environment Block). Вновь обращаемся к интернету и находим там нужный код.
C++:
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
  PEB* pPeb = (PEB*)__readgsqword(0x60);
  // For x86
  // PEB* pPeb = (PEB*)__readgsqword(0x30);
  PEB_LDR_DATA* Ldr = pPeb->Ldr;
  LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
  LIST_ENTRY* pStartListEntry = ModuleList->Flink;
  WCHAR mystr[MAX_PATH] = { 0 };
  WCHAR substr[MAX_PATH] = { 0 };
  for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
    LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
    memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
    memset(substr, 0, MAX_PATH * sizeof(WCHAR));
    wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
    wcscpy_s(substr, MAX_PATH, lModuleName);
    if (cmpUnicodeStr(substr, mystr)) {
      return (HMODULE)pEntry->DllBase;
    }
  }
  // The needed DLL wasn’t found
  printf("failed to get a handle to %s\n", lModuleName);
  return NULL;
}
Проблема в том, что здесь используются функции либо из CRT, либо те, которые будут отражаться в IAT. Заменяем memset().
C++:
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
    unsigned char* p = (unsigned char*)Destination;
    while (Size > 0) {
        *p = (unsigned char)Value;
        p++;
        Size--;
    }
    return Destination;
}
Код моей кастомной функции прост: идет копирование в память нужного значения. Теперь заменяем wcscpy_s().
C++:
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
    wchar_t* originalDest = dest;
    while (count > 0) {
        *dest = *src;
        if (*src == L'\0') {
            break;
        }
        dest++;
        src++;
        count--;
    }
    while (count > 0) {
        *dest = L'\0';
        dest++;
        count--;
    }
    return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
    if (str == nullptr) {
        return 0;
    }
    size_t length = 0;
    while (*str != L'\0') {
        length++;
        str++;
    }
    return length;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
    if (dest == nullptr || src == nullptr) {
        return EINVAL;
    }
    size_t srcLength = custom_wcslen(src);
    if (destSize < srcLength + 1) {
        return ERANGE;
    }
    custom_wcsncpy(dest, src, destSize);
    dest[srcLength] = L'\0';
    return 0;
}
Здесь чуть сложнее: сначала идет проверка на размер, после чего подсчет длины (увеличение счетчика до тех пор, пока не будет встречен null byte — конец строки), а затем копирование в память.

Для сравнения двух Unicode-строк автор использует функцию из Shlwapi.
C++:
int cmpUnicodeStr(WCHAR substr[], WCHAR mystr[]) {
  _wcslwr_s(substr, MAX_PATH);
  _wcslwr_s(mystr, MAX_PATH);
  int result = 0;
  if (StrStrW(mystr, substr) != NULL) {
    result = 1;
  }
  return result;
}
Мы такой роскошью не обладаем, поэтому вновь пишем код самостоятельно. Предлагаю просто по аналогии со strcmp() заменить wcscmp().
C++:
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
Эта функция вернет 0, если строки равны. Немного подебажив, я увидел, что в PEB ntdll представлен как C:\Windows\SYSTEM32\ntdll.dll, поэтому такое же значение и передаем нашей функции.

Код:
HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");

Итоговый код проекта стал еще больше. Теперь для стандартного MessageBox() нужно целых 176 строк.
C++:
#include <Windows.h>
#include <winternl.h>
typedef int (WINAPI* MessageBoxWFunc)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );
typedef NTSTATUS(NTAPI* pLdrLoadDll) (
    PWCHAR PathToFile,
    ULONG Flags,
    PUNICODE_STRING ModuleFileName,
    PHANDLE ModuleHandle
    );
typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
wchar_t* custom_wcsncpy(wchar_t* dest, const wchar_t* src, size_t count) {
    wchar_t* originalDest = dest;
    while (count > 0) {
        *dest = *src;
        if (*src == L'\0') {
            break;
        }
        dest++;
        src++;
        count--;
    }
    while (count > 0) {
        *dest = L'\0';
        dest++;
        count--;
    }
    return originalDest;
}
size_t custom_wcslen(const wchar_t* str) {
    if (str == nullptr) {
        return 0;
    }
    size_t length = 0;
    while (*str != L'\0') {
        length++;
        str++;
    }
    return length;
}
int custom_wcscmp(const wchar_t* str1, const wchar_t* str2) {
    while (*str1 == *str2 && *str1 != L'\0') {
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
errno_t custom_wcscpy_s(wchar_t* dest, size_t destSize, const wchar_t* src) {
    if (dest == nullptr || src == nullptr) {
        return EINVAL;
    }
    size_t srcLength = custom_wcslen(src);
    if (destSize < srcLength + 1) {
        return ERANGE;
    }
    custom_wcsncpy(dest, src, destSize);
    dest[srcLength] = L'\0';
    return 0;
}
int custom_strcmp(const char* str1, const char* str2) {
    while (*str1 || *str2) {
        if (*str1 < *str2) {
            return -1;
        }
        else if (*str1 > *str2) {
            return 1;
        }
        str1++;
        str2++;
    }
    return 0;
}
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule +
        ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD* addressOfFunctions = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions);
    WORD* addressOfNameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals);
    DWORD* addressOfNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames);
    for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
        if (custom_strcmp(lpProcName, (const char*)hModule + addressOfNames[i]) == 0) {
            return (FARPROC)((BYTE*)hModule + addressOfFunctions[addressOfNameOrdinals[i]]);
        }
    }
    return NULL;
}
void* __cdecl custom_memset(void* Destination, int Value, size_t Size) {
    unsigned char* p = (unsigned char*)Destination;
    while (Size > 0) {
        *p = (unsigned char)Value;
        p++;
        Size--;
    }
    return Destination;
}
HMODULE myGetModuleHandle(LPCWSTR lModuleName) {
    PEB* pPeb = (PEB*)__readgsqword(0x60);
    // For x86
    // PEB* pPeb = (PEB*)__readgsqword(0x30);
    PEB_LDR_DATA* Ldr = pPeb->Ldr;
    LIST_ENTRY* ModuleList = &Ldr->InMemoryOrderModuleList;
    LIST_ENTRY* pStartListEntry = ModuleList->Flink;
    WCHAR mystr[MAX_PATH] = { 0 };
    WCHAR substr[MAX_PATH] = { 0 };
    for (LIST_ENTRY* pListEntry = pStartListEntry; pListEntry != ModuleList; pListEntry = pListEntry->Flink) {
        LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)((BYTE*)pListEntry - sizeof(LIST_ENTRY));
        custom_memset(mystr, 0, MAX_PATH * sizeof(WCHAR));
        custom_memset(substr, 0, MAX_PATH * sizeof(WCHAR));
        custom_wcscpy_s(mystr, MAX_PATH, pEntry->FullDllName.Buffer);
        custom_wcscpy_s(substr, MAX_PATH, lModuleName);
        if (custom_wcscmp(substr, mystr) == 0) {
            // Returning the DLL base address
            return (HMODULE)pEntry->DllBase;
        }
    }
}
HMODULE MyLoadLibrary(LPCWSTR lpFileName) {
    UNICODE_STRING ustrModule;
    HANDLE hModule = NULL;
    HMODULE hNtdll = myGetModuleHandle(L"C:\\Windows\\SYSTEM32\\ntdll.dll");
    pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)myGetProcAddress(hNtdll, "RtlInitUnicodeString");
    RtlInitUnicodeString(&ustrModule, lpFileName);
    pLdrLoadDll myLdrLoadDll = (pLdrLoadDll)myGetProcAddress(hNtdll, "LdrLoadDll");
    if (!myLdrLoadDll) {
        return NULL;
    }
    NTSTATUS status = myLdrLoadDll(NULL, 0, &ustrModule, &hModule);
    return (HMODULE)hModule;
}
int main() {
    HMODULE user32Module = MyLoadLibrary(L"user32.dll");
    MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
    MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
    return 0;
}
Наше чудо заработало!

Наконец в IAT стало пусто.

Пустой IAT

Причем обрати внимание, что у тебя компилятор может выдавать ошибку LNK1120.
Код:
1>Source.obj : error LNK2001: неразрешенный внешний символ memset.
1>A:\SSD\ProjectsVS\Article\x64\Release\Article.exe : fatal error LNK1120: неразрешенных внешних элементов: 1
Если это произошло, открывай раздел «Проект → Свойства» и отключай оптимизацию.
Отключение оптимизации

Осталось лишь навести немного красоты. Ведь это очень подозрительно, если у программы пустой IAT. Займемся IAT Camouflage.

IAT CAMOUFLAGE​

Некоторые антивирусы могут повесить на нас детект просто потому, что мы не вызываем никаких функций, поскольку у нас пустой IAT. Предлагаю добавить парочку фиктивных вызовов функций, которые ни к чему не приводят. При этом в IAT, само собой, появятся записи о том, что используются те или иные функции.

Добавим, например, код, который никогда не выполнится.
C++:
int main() {
  HMODULE user32Module = MyLoadLibrary(L"user32.dll");
  MessageBoxWFunc MessageBoxWPtr = (MessageBoxWFunc)(myGetProcAddress(user32Module, "MessageBoxW"));
  MessageBoxWPtr(NULL, L"HI", L"HI", MB_OK);
  int m = 4;
  if (m > 5) {
      unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
      i = GetLastError();
      i = SetCriticalSectionSpinCount(NULL, NULL);
      i = GetWindowContextHelpId(NULL);
      i = GetWindowLongPtrW(NULL, NULL);
      i = RegisterClassW(NULL);
      i = IsWindowVisible(NULL);
      i = ConvertDefaultLocale(NULL);
      i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
      i = IsDialogMessageW(NULL, NULL);
  }
  return 0;
}
Обрати внимание, что компиляторы нынче достаточно умные, поэтому нужно будет отключить оптимизацию, как описано выше. В противном случае компилятор увидит, что этот кусок кода никогда не срабатывает, и исключит его из исполняемого файла.

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


ПСЕВДОХЕНДЛЫ​

Наконец, на десерт хочется упомянуть псевдохендлы. Псевдохендлами считаются фиктивные хендлы на какой‑либо текущий ресурс. Например, вызов GetCurrentProcess() не получится ничем заменить, но вместо него можно использовать псевдохендл. Система Windows, получив такой хендл, поймет, что ей нужно обратиться к процессу, из которого была вызвана функция. Таким образом, для получения хендла, например, на текущий процесс, достаточно передавать значение -1. И Windows определит, что это хендл на текущий процесс.

Можно даже в проекте создать маленькие макросы, которые будут выполнять эту работу автоматически.
Код:
#define NtCurrentProcess() ((HANDLE)-1) // Получить хендл на текущий процесс
#define NtCurrentThread()  ((HANDLE)-2) // Получить хендл на текущий поток

ВЫВОДЫ​

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

Автор @MichelleVermishelle | TG: @Michaelzhm
Источник xakep.ru

Невероятные усилия и преданность делу, отличный коллега по работе.
Incredible effort and dedication, great work mate.

10/10
 
Ну раз уж про свой GetProcAddress то где здесь про фрварды и аписет?
Не годно к использованию.
Почему , не годно к использованию. Объясни поподробнее, вопрос от новичка
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх