При первом знакомстве с кодом, вместо занудной трассировки, можно сразу ставить бряки на нужные нам api, и дальше двигаться в нужном направлении и делать выводы о том, как же работает бацила. При анализе семпла (Andromeda) таким способом, сработал трюк, о котором я совершенно не мог подумать, и не происходи работа в виртуальной среде, моя система стала бы достоянием ботнета
Допустим есть какая-то полезная нагрузка, ей передается, или непосредственно в коде заполняется, участок с адресами необходимых для работы api. Трюк заключается в том, что выполнение api, по родному месту, начинается ПОСЛЕ первой инструкции, т.е. бряк на первой совершенно бесполезен. Как же так происходит? Элементарно, на примере MessageBoxW:
мы копируем себе "mov edi, edi", а потом прыгаем на следующую и-ю в dll.
Мы можем тупо скопировать методу(код), но так неправильно. Попробуем воссоздать фокус.
Сперва были проанализированы (при помощи capstone) функции в следующих dll на win7 x32, для определения возможных подвохов и неточностей:
Итого, вышел такой простенький но хитрый каркас:
Допустим есть какая-то полезная нагрузка, ей передается, или непосредственно в коде заполняется, участок с адресами необходимых для работы api. Трюк заключается в том, что выполнение api, по родному месту, начинается ПОСЛЕ первой инструкции, т.е. бряк на первой совершенно бесполезен. Как же так происходит? Элементарно, на примере MessageBoxW:
мы копируем себе "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 будет бесполезен
Итого, вышел такой простенький но хитрый каркас:
Код:
#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;
}