Хитрое заполнение API-таблицы

Тема в разделе "Вирусология", создана пользователем Admin, 14 ноя 2016.

  1. Admin

    Admin Голова лаборатории

    Сообщения:
    521
    Симпатии:
    321
    При первом знакомстве с кодом, вместо занудной трассировки, можно сразу ставить бряки на нужные нам api, и дальше двигаться в нужном направлении и делать выводы о том, как же работает бацила. При анализе семпла (Andromeda) таким способом, сработал трюк, о котором я совершенно не мог подумать, и не происходи работа в виртуальной среде, моя система стала бы достоянием ботнета [​IMG]

    Допустим есть какая-то полезная нагрузка, ей передается, или непосредственно в коде заполняется, участок с адресами необходимых для работы api. Трюк заключается в том, что выполнение api, по родному месту, начинается ПОСЛЕ первой инструкции, т.е. бряк на первой совершенно бесполезен. Как же так происходит? Элементарно, на примере MessageBoxW:
    [​IMG]
    мы копируем себе "mov edi, edi", а потом прыгаем на следующую и-ю в dll.

    Код:
    my_MessageBoxW: ; (место в нашей памяти, my_MessageBoxW вместо прямого адреса на оригинал)
        mov edi, edi ; первая инструкция, скопированная из orig_MessageBoxW
        jmp orig_MessageBoxW + sizeof(mov edi, edi)
    
    orig_MessageBoxW: ; (оригинал, в user32.dll)
        mov edi, edi ; <- копируем себе, вычисляем размер, тут стоит бряк
        push ebp ; <- прыгаем сюда
        ...
    Мы можем тупо скопировать методу(код), но так неправильно. Попробуем воссоздать фокус.

    Сперва были проанализированы (при помощи capstone) функции в следующих dll на win7 x32, для определения возможных подвохов и неточностей:
    • ntdll.dll
    • kernel32.dll
    • user32.dll
    • ws2_32.dll
    • gdi32.dll
    • gdiplus.dll
    • crypt32.dll
    • shell32.dll
    • advapi32.dll
    • ole32.dll
    • urlmon.dll
    • wininet.dll
    После этой проверки, был сделан вывод, что должна уметь функция:
    • обработка единичных инструкций типа ret N, int3 - просто их копируем, прыгать некуда
    • обработка проксей типа "NTDLL.RtlAcquireSRWLockExclusive" - нужно определить и пропарсить с последующей загрузкой (GetProcAddress(LoadLibraryA("NTDLL"), "RtlAcquireSRWLockExclusive"))
    • обработка прыжков - можно вычислить куда он прыгает, иначе НАШ jmp будет бесполезен
    Нам также необходим дизассемблер длин. Используем мал да удал Catchy32.

    Итого, вышел такой простенький но хитрый каркас:
    Код:
    #include <windows.h>
    #include <stdio.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include <string.h>
    
    extern int __stdcall c_Catchy(uint8_t*);
    
    
    #define RVATOVA(base, offset) ((DWORD)base + (DWORD)offset)
    
    void GetApi(char *szDllName, char *szApiName, uint8_t *pBlock)
    {
        HMODULE hModule = LoadLibraryA(szDllName);
    
        if (hModule == NULL) {
            printf("hModule error!\n");
            exit(EXIT_FAILURE);
        }
    
        PIMAGE_OPTIONAL_HEADER poh = (PIMAGE_OPTIONAL_HEADER)(
            (char*)hModule +
            ((PIMAGE_DOS_HEADER)hModule)->e_lfanew +
            sizeof(DWORD) +
            sizeof(IMAGE_FILE_HEADER)
        );
    
        PIMAGE_EXPORT_DIRECTORY ped = (IMAGE_EXPORT_DIRECTORY*)RVATOVA(
            hModule, poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
        );
    
        DWORD *pdwNamePtr = (DWORD*)RVATOVA(hModule, ped->AddressOfNames);
        WORD *pwOrdinalPtr = (WORD*)RVATOVA(hModule, ped->AddressOfNameOrdinals);
    
        for (size_t i = 0; i < ped->NumberOfNames; ++i, ++pdwNamePtr, ++pwOrdinalPtr) {
            if (lstrcmpA((char*)RVATOVA(hModule, *pdwNamePtr), szApiName) == 0) {
    
                PDWORD pAddrTable = (PDWORD)RVATOVA(hModule, ped->AddressOfFunctions);
                DWORD dwRVA = pAddrTable[*pwOrdinalPtr];
                DWORD dwApiAddr = (DWORD)RVATOVA(hModule, dwRVA);
    
                int catchy = c_Catchy((uint8_t*)dwApiAddr);
    
                // копируем первую инструкцию
                memcpy(pBlock, (uint8_t*)dwApiAddr, catchy);
    
                // пишем опкод jmp
                pBlock[catchy] = 0xE9;
    
                //считаем прыжок
                DWORD offset = dwApiAddr - (DWORD)pBlock - 5;
    
                // заполняем
                memcpy(pBlock + catchy + 1, &offset, sizeof(DWORD));
            }
        }
    }
    
    int main(void)
    {
        /*
            Функций, с начальной инструкцией в 15 байт при проверке не встречалось, поэтому места нам должно хватить. Выравниваем.
        */
        uint8_t *apis_block = malloc(16 * 2);
        if (apis_block == NULL) {
            printf("malloc error!\n");
            exit(EXIT_FAILURE);
        }
    
        GetApi("user32.dll", "MessageBoxW", apis_block);
        GetApi("kernel32.dll", "ExitProcess", apis_block + 16);
    
    
        // MessageBoxW
        (*(void*(*)())apis_block)(0, L"text", L"caption", MB_OK);
    
        // ExitProcess
        (*(void*(*)())(apis_block + 16))(0);
    
    
        free(apis_block);
    
        return 0;
    }