
Biblioteca header-only em C++17 para desenvolvimento evasivo no Windows: hashing de API em tempo de compilação, unhooking de DLL, limpeza de breakpoints de hardware, remoção de VEH e supressão de callbacks de notificação de DLL para evadir os hooks de EDR.
Esta é uma continuação do meu projeto original WTSRM e da correspondente palestra.
Este projeto foi reescrito para utilizar C++17 e criar um template que nos permite alterar a funcionalidade necessária em tempo de compilação com facilidade! Ele é implementado como um arquivo somente de cabeçalho, para que você possa adicioná-lo facilmente aos seus próprios projetos.
Alguns dos recursos atualmente implementados incluem:
Podemos configurar esse comportamento da seguinte forma
#define HASHING_ALGORITHM hash_string_fnv1a // constexpr algorithm
namespace config
{
...
//
// configure behaviour
//
inline constexpr unsigned unhooking = system32 | write_process_memory;
inline constexpr auto work_item_load_library = true; // use a work item for LL
//
// configure evasive features
//
inline constexpr auto clear_and_restore_veh = true; // clear and restore veh
inline constexpr auto unregister_dll_notifs = true; // LdrRegisterDllNotification
inline constexpr auto clear_thread_hwbps = true; // clear the hwbps
}
Como utilizamos constexpr, não pagamos pelos recursos que não usamos, pois eles são código morto e são otimizados para fora do programa.
Para importar DLLs, primeiro devemos criar o hash usando a macro DLL_HASH
DLL_HASH(DBGHELP, L"DBGHELP.DLL")
Em seguida, devemos adicioná-lo à module_address_table, que atua como nossa IAT interna. A constante hash_ é criada pela macro DLL_HASH. Para DLLs não comuns que nem sempre estão carregadas, devemos especificar seus nomes para que possamos carregá-las.
inline addr_struct<decltype(HASHING_ALGORITHM(TOKENIZEA(T))), wchar_t> module_address_table[] =
{
{hash_NTDLL, nullptr,},
{hash_KERNEL32, nullptr,},
{hash_DBGHELP, nullptr, L"DBGHELP.DLL"}
};
Para importar funções, primeiro devemos criar o hash do nome da função e o typedef usando a macro HASH_FUNC
HASH_FUNC(NtUnmapViewOfSection, NTSTATUS, HANDLE, PVOID)
O primeiro parâmetro é o nome da função, o próximo é o tipo de retorno e, em seguida, os argumentos da função. Em seguida, devemos adicioná-la à nossa api_address_table, que será preenchida com os endereços atuais das funções
inline addr_struct<decltype(HASHING_ALGORITHM(TOKENIZEA(T))), void> api_address_table[] =
{
{hash_GetProcAddress, nullptr},
{hash_NtUnmapViewOfSection, nullptr},
Se você não adicionar essa função a essa tabela, o programa não irá falhar, mas retornará -1 na tentativa de aumentar a estabilidade.
Para usar uma API, use a macro API()() como demonstrado
API(NtWaitForSingleObject)(NtCurrentProcess(), FALSE, &timeout);
Para adicionar sua própria função de hashing de API, você deve usar este template para trabalhar com os tipos necessários.
template <typename T>
constexpr auto hash_string_(const T* buffer)
{
TRIVIAL_TYPE_CHECK()
}
Agradecimentos a estas pessoas, em nenhuma ordem específica, por suas ideias e contribuições @peterwintrsmith @modexpblog @jonaslyk @JustasMasiulis