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

Статья Скрытие вызовов WinAPI по хешу.

c0d3r_0f_shr0d13ng3r

ಥ_ಥ
Пользователь
Регистрация
01.04.2020
Сообщения
466
Реакции
555
Гарант сделки
1
Депозит
0.0086
Сегодня мы разберем один из самых простых и действенных методов скрытия вызовов WinAPI.Для полного понимания, как работает данный метод необходимо знать как устроен PE-формат.

Как работает данный метод?

Мы будем пробегаться по таблице экспорта системной библиотеки в которой содержится нужная API-функция, хешируя каждое название функции в таблице экспорта.Если хэши одной из функций и нашей функции будут совпадать - то мы нашли указатель на нужную нам функцию.В данном примере я буду использовать алгоритм хеширования CRC-32.У него есть пару минусов:
  • Скорость алгоритма не самая высокая.
  • Приходится таскать в бинарнике достаточно громоздкую таблицу:
1585992249587.png


В реальных условиях желательно использовать другой алгоритм.

Теперь приступим к реализации, напишем функцию получения функции по хешу(все комментарии в коде):

Заголовочный файл:

Код:
#pragma once
#include <Windows.h>

LPVOID GetFuncByHash(LPCSTR pszLibrary, UINT uHash);

CPP-файл:

Код:
#include "cApiHash.h"
#include "cCRC32.h"


LPVOID GetFuncByHash(LPCSTR pszLibrary, UINT uHash) {

    //Загружаем библиотеку
    HINSTANCE hLibrary = GetModuleHandleA(pszLibrary);

    if (!hLibrary)
        return NULL;

    //Получаем DOS-заголовок и проверяем его на валидность
    PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)hLibrary;

    if (pDOSHdr->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;


    //Получаем PE-заголовок и проверяем его на валидность
    PIMAGE_NT_HEADERS pNTHdr = (PIMAGE_NT_HEADERS)((LPBYTE)hLibrary + pDOSHdr->e_lfanew);

    if (pNTHdr->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    if ((pNTHdr->FileHeader.Characteristics & IMAGE_FILE_DLL) == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == NULL)
            return NULL;

    //Получаем смещение таблицы экспорта
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hLibrary +
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pdwAddress = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfFunctions);
    PDWORD pdwNames = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfNames);
    PWORD pwOrd = (PWORD)((LPBYTE)hLibrary + pIED->AddressOfNameOrdinals);

    //Разбираем таблицу экспорта
    for (DWORD i = 0; i < pIED->AddressOfFunctions; i++)
    {

        LPSTR pszFuncName = (LPSTR)((LPBYTE)hLibrary + pdwNames[i]);
        UINT32 u32FuncHash = cCRC32::Hash(pszFuncName, lstrlenA(pszFuncName));

        //Если хеши совпадают - возвращаем указатель
        if (u32FuncHash == uHash)
            return (LPVOID)((LPBYTE)hLibrary + pdwAddress[pwOrd[i]]);
      
    }
}

Теперь протестируем:

Код:
#include <Windows.h>
#include "cApiHash.h"

//Прототип функции которую хотим вызвать
typedef int(WINAPI* _MessageBoxA)(

    HWND hWND,
    LPCSTR pszText,
    LPCSTR pszCaption,
    UINT uType

);

VOID WINAPI Entry(VOID) {

    _MessageBoxA fpMessageBoxA = (_MessageBoxA)GetFuncByHash("user32.dll", 0x572D5D8E);
    fpMessageBoxA(NULL, "Hello xss.pro", "xss.pro", MB_OK);


}

Функция успешно вызвалась

1585994683986.png


Теперь взглянем на IAT

1585994553061.png


user32!MessageBoxA там не отображается.

Собственна все)
 

Вложения

  • CallApiByHash.zip
    6.9 КБ · Просмотры: 183
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Зачем делать LoadLibrary? Обычно берут загруженный модуль и парсят его экспорт. И твоя реализация не будет работать на х64 из-за проблем с адрессацией, кастани HMODULE в char* и работай с ним.
Плюс ты забыл про обработку функций, которые форвардятся.
 
Зачем делать LoadLibrary? Обычно берут загруженный модуль и парсят его экспорт. И твоя реализация не будет работать на х64 из-за проблем с адрессацией, кастани HMODULE в char* и работай с ним.
Плюс ты забыл про обработку функций, которые форвардятся.
На x64 все работает, вот EXE.
 

Вложения

  • CallApiByHash.zip
    2.4 КБ · Просмотры: 156
Пожалуйста, обратите внимание, что пользователь заблокирован
На x64 все работает, вот EXE.
Я имел ввиду то, что эта поделка не будет работать при передаче туда HMODULE напрямую, нужно будет кастануть. В целом, в текущей реализации это бессмысленно. Банально киддисы поставят бряк на LoadLibrary / lstrlen, и на этом всё закончится. Если что-то не понятно - пиши в ЛС, могу обьяснить подробнее.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Как минимум один
импорт будет виден - LoadLibraryA. Не знаю, хорошо это или плохо, но его тоже можно скрыть:
C:
HMODULE GetKernel32(void)
{
    __asm
    {
        mov eax, fs: [0x30]
        mov eax, [eax + 0xC]
        mov eax, [eax + 0xC]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 0x18]
    }
}

Немного поправим код:
C:
LPVOID GetFuncByHash(HMODULE hModule, UINT uHash)
{
    //Получаем DOS-заголовок и проверяем его на валидность
    PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)hModule;

    if (pDOSHdr->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;


    //Получаем PE-заголовок и проверяем его на валидность
    PIMAGE_NT_HEADERS pNTHdr = (PIMAGE_NT_HEADERS)((LPBYTE)hLibrary + pDOSHdr->e_lfanew);

    if (pNTHdr->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    if ((pNTHdr->FileHeader.Characteristics & IMAGE_FILE_DLL) == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == NULL)
            return NULL;

    //Получаем смещение таблицы экспорта
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hLibrary +
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pdwAddress = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfFunctions);
    PDWORD pdwNames = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfNames);
    PWORD pwOrd = (PWORD)((LPBYTE)hLibrary + pIED->AddressOfNameOrdinals);

    //Разбираем таблицу экспорта
    for (DWORD i = 0; i < pIED->AddressOfFunctions; i++)
    {

        LPSTR pszFuncName = (LPSTR)((LPBYTE)hLibrary + pdwNames[i]);
        UINT32 u32FuncHash = cCRC32::Hash(pszFuncName, lstrlenA(pszFuncName));

        //Если хеши совпадают - возвращаем указатель
        if (u32FuncHash == uHash)
            return (LPVOID)((LPBYTE)hLibrary + pdwAddress[pwOrd[i]]);
   
    }
}

Код:
typedef HMODULE(WINAPI* tLoadLibraryA)(LPCSTR lpFileName);

static HMODULE hKernel32 = NULL;
static LPVOID ptrLoadLibraryA = NULL;

LPVOID GetWinAPI(LPCSTR dllName, UINT funcHash)
{
    if (!hKernel32)
    {
        hKernel32 = GetKernel32();
    }
 
    if (!ptrLoadLibraryA)
    {
        ptrLoadLibraryA = (tLoadLibraryA)GetFuncByHash(hKernel32, /*тут crc32 хэш LoadLibraryA*/);
    }
 
    HMODULE hDll = (tLoadLibraryA)ptrLoadLibraryA)(dllName);

    if (!hDll)
    {
        return NULL;
    }

    return GetFuncByHash(hDlly, funcHash);

}

Ограничение: будет работать только на x86, можно исправить, но лень писать
 
Пожалуйста, обратите внимание, что пользователь заблокирован
C:
HMODULE pGetModuleHandle(uint32_t uHash)
{
    HMODULE hRet = NULL;

    if (!uHash)
    {
        return hRet;
    }

#ifdef _AMD64_
    PPEB pPEB = (PPEB)__readgsqword(0x60);
#else
    PPEB pPEB = (PPEB)__readfsdword(0x30);
#endif

    PPEB_LDR_DATA pLdr = pPEB->Ldr;
    PLIST_ENTRY pHead = &pLdr->InLoadOrderModuleList;
    PLIST_ENTRY pEntry = pHead->Flink;

    while (pEntry != pHead)
    {
        PLDR_DATA_TABLE_ENTRY pLdrEntry = (PLDR_DATA_TABLE_ENTRY)pEntry;
        uint32_t uModuleHash = 0;

        ToLower((PWCHAR)pLdrEntry->BaseDllName.Buffer);
        Hash(pLdrEntry->BaseDllName.Buffer, StringLength(pLdrEntry->BaseDllName.Buffer) * 2, &uModuleHash);

        if (uModuleHash == uHash)
        {
            hRet = (HMODULE)pLdrEntry->DllBase;
            break;
        }

        pEntry = pEntry->Flink;
    }

    return hRet;
}

Вот так будет лучше. Нужно использовать инстринктики, т.к на х64 нет инлайн ассемблера.
 
Как минимум один
импорт будет виден - LoadLibraryA. Не знаю, хорошо это или плохо, но его тоже можно скрыть:
C:
HMODULE GetKernel32(void)
{
    __asm
    {
        mov eax, fs: [0x30]
        mov eax, [eax + 0xC]
        mov eax, [eax + 0xC]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 0x18]
    }
}

Немного поправим код:
C:
LPVOID GetFuncByHash(HMODULE hModule, UINT uHash)
{
    //Получаем DOS-заголовок и проверяем его на валидность
    PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)hModule;

    if (pDOSHdr->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;


    //Получаем PE-заголовок и проверяем его на валидность
    PIMAGE_NT_HEADERS pNTHdr = (PIMAGE_NT_HEADERS)((LPBYTE)hLibrary + pDOSHdr->e_lfanew);

    if (pNTHdr->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    if ((pNTHdr->FileHeader.Characteristics & IMAGE_FILE_DLL) == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == NULL)
            return NULL;

    //Получаем смещение таблицы экспорта
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hLibrary +
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pdwAddress = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfFunctions);
    PDWORD pdwNames = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfNames);
    PWORD pwOrd = (PWORD)((LPBYTE)hLibrary + pIED->AddressOfNameOrdinals);

    //Разбираем таблицу экспорта
    for (DWORD i = 0; i < pIED->AddressOfFunctions; i++)
    {

        LPSTR pszFuncName = (LPSTR)((LPBYTE)hLibrary + pdwNames[i]);
        UINT32 u32FuncHash = cCRC32::Hash(pszFuncName, lstrlenA(pszFuncName));

        //Если хеши совпадают - возвращаем указатель
        if (u32FuncHash == uHash)
            return (LPVOID)((LPBYTE)hLibrary + pdwAddress[pwOrd[i]]);
  
    }
}

Код:
typedef HMODULE(WINAPI* tLoadLibraryA)(LPCSTR lpFileName);

static HMODULE hKernel32 = NULL;
static LPVOID ptrLoadLibraryA = NULL;

LPVOID GetWinAPI(LPCSTR dllName, UINT funcHash)
{
    if (!hKernel32)
    {
        hKernel32 = GetKernel32();
    }

    if (!ptrLoadLibraryA)
    {
        ptrLoadLibraryA = (tLoadLibraryA)GetFuncByHash(hKernel32, /*тут crc32 хэш LoadLibraryA*/);
    }

    HMODULE hDll = (tLoadLibraryA)ptrLoadLibraryA)(dllName);

    if (!hDll)
    {
        return NULL;
    }

    return GetFuncByHash(hDlly, funcHash);

}

Ограничение: будет работать только на x86, можно исправить, но лень писать

Тоже хотел сделать дополнение с inlin-овым asm-ом, но вспомнил что он для x86.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Тоже хотел сделать дополнение с inlin-овым asm-ом, но вспомнил что он для x86.
Выше скинули крутой пример, под x64 тоже должен пахать.
 
Как минимум один
импорт будет виден - LoadLibraryA. Не знаю, хорошо это или плохо, но его тоже можно скрыть:
C:
HMODULE GetKernel32(void)
{
    __asm
    {
        mov eax, fs: [0x30]
        mov eax, [eax + 0xC]
        mov eax, [eax + 0xC]
        mov eax, [eax]
        mov eax, [eax]
        mov eax, [eax + 0x18]
    }
}

Немного поправим код:
C:
LPVOID GetFuncByHash(HMODULE hModule, UINT uHash)
{
    //Получаем DOS-заголовок и проверяем его на валидность
    PIMAGE_DOS_HEADER pDOSHdr = (PIMAGE_DOS_HEADER)hModule;

    if (pDOSHdr->e_magic != IMAGE_DOS_SIGNATURE)
        return NULL;


    //Получаем PE-заголовок и проверяем его на валидность
    PIMAGE_NT_HEADERS pNTHdr = (PIMAGE_NT_HEADERS)((LPBYTE)hLibrary + pDOSHdr->e_lfanew);

    if (pNTHdr->Signature != IMAGE_NT_SIGNATURE)
        return NULL;

    if ((pNTHdr->FileHeader.Characteristics & IMAGE_FILE_DLL) == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == NULL ||
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == NULL)
            return NULL;

    //Получаем смещение таблицы экспорта
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hLibrary +
        pNTHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    PDWORD pdwAddress = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfFunctions);
    PDWORD pdwNames = (PDWORD)((LPBYTE)hLibrary + pIED->AddressOfNames);
    PWORD pwOrd = (PWORD)((LPBYTE)hLibrary + pIED->AddressOfNameOrdinals);

    //Разбираем таблицу экспорта
    for (DWORD i = 0; i < pIED->AddressOfFunctions; i++)
    {

        LPSTR pszFuncName = (LPSTR)((LPBYTE)hLibrary + pdwNames[i]);
        UINT32 u32FuncHash = cCRC32::Hash(pszFuncName, lstrlenA(pszFuncName));

        //Если хеши совпадают - возвращаем указатель
        if (u32FuncHash == uHash)
            return (LPVOID)((LPBYTE)hLibrary + pdwAddress[pwOrd[i]]);
  
    }
}

Код:
typedef HMODULE(WINAPI* tLoadLibraryA)(LPCSTR lpFileName);

static HMODULE hKernel32 = NULL;
static LPVOID ptrLoadLibraryA = NULL;

LPVOID GetWinAPI(LPCSTR dllName, UINT funcHash)
{
    if (!hKernel32)
    {
        hKernel32 = GetKernel32();
    }

    if (!ptrLoadLibraryA)
    {
        ptrLoadLibraryA = (tLoadLibraryA)GetFuncByHash(hKernel32, /*тут crc32 хэш LoadLibraryA*/);
    }

    HMODULE hDll = (tLoadLibraryA)ptrLoadLibraryA)(dllName);

    if (!hDll)
    {
        return NULL;
    }

    return GetFuncByHash(hDlly, funcHash);

}

Ограничение: будет работать только на x86, можно исправить, но лень писать
+еще будет lstrlenA виден, надо будет свой аналог написать
 
Пожалуйста, обратите внимание, что пользователь заблокирован
+еще будет lstrlenA виден, надо будет свой аналог написать
C:
size_t strLen(LPCSTR str)
{
    if (!str)
    {
        return 0;
    }

    size_t cnt = 0;
    for (; *str != '\0'; ++str)
    {
        ++cnt;
    }

    return cnt;
}

Ну и простенький алгоритм хэширвоания:

C:
unsigned int CalcHash(const byte* ptr, int c_ptr)
{
    unsigned int hash = 0;
    if (ptr && c_ptr > 0)
    {
        for (int i = 0; i < c_ptr; i++, ptr++)
        {
            hash = (hash << 4) + *ptr;
            unsigned t;

            if ((t = hash & 0xf0000000) != 0)
            {
                hash = ((hash ^ (t >> 24))& (0x0fffffff));
            }
        }
    }
    return hash;
}
 
Пожалуйста, обратите внимание, что пользователь заблокирован
И кстати, лучше по хэшу получать адрес GetProcAddress, а с помощью него все остальные апи.
Могут возникнуть проблемы с получением адреса некоторых винапи.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
И кстати, лучше по хэшу получать адрес GetProcAddress, а с помощью него все остальные апи.
Могут возникнуть проблемы с получением адреса некоторых винапи.
Не лучше. Лучше реализовать поддержку function forwarding и не делать костыли, тем более делается в десяток строк.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
А зачем скрывать вызовы винапи в рядовом софте? Я может чего то не понимаю, обьясните пожалуйста.
Чтобы скрыть палевные апи. Да и реверсить подобную шнягу сложнее.


Не лучше. Лучше реализовать поддержку function forwarding и не делать костыли, тем более делается в десяток строк.
Интересно бы было посмотреть, как это делается.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Чтобы скрыть палевные апи. Да и реверсить подобную шнягу сложнее.
Зачем скрывать апи, если софт потом накроется криптором? По поводу реверсинга - апи только реальный нуб не восстановит. Есть куча плагинов для иды и не только для этого.

Интересно бы было посмотреть, как это делается.


C:
if( addr > (SIZE_T)exportDir && addr < (SIZE_T)exportDir + exportSize ) //ïåðåíàïðàâëåíèå (NameDll.NameFunc)
    {
        char* s = (char*)addr;
        char nameDll[32];
        int i = 0;
        while( s[i] != '.' )
        {
            nameDll[i] = s[i];
            i++;
        }
        s += i + 1; //èìÿ ôóíêöèè
        nameDll[i++] = '.';
        nameDll[i++] = 'd';
        nameDll[i++] = 'l';
        nameDll[i++] = 'l';
        nameDll[i] = 0;
        int num = 0;
        if( *s == '#' ) //íîìåð ôóíêöèè
        {
            while( *++s ) num = num * 10 + *s - '0';
            s = (char*)&num;
        }
        HMODULE hdll = _LoadLibraryA(nameDll);
        return _GetProcAddress( hdll, s );
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Зачем скрывать апи, если софт потом накроется криптором?
Хз, как остальные, лично я не криптую свои бинари. Зачем это, когда есть исходники?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Хз, как остальные, лично я не криптую свои бинари. Зачем это, когда есть исходники?
Ну как бы, если ты будешь работать с чем-то - ты заебешься постоянно чистить свой софт, для нормальной работы нужен крипт в любом случае.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
т.к на х64 нет инлайн ассемблера.
Можно поставить компилятор Интел, интегрируется в студию, и там есть инлайн для х64.
Но толку вам с него, 64 бит почти никто не криптует , и он явно не подойдет для масс лоадов по понятным причинам (32 бита работает везде).
 


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