Авторские статьи Splicing

Discussion in 'Статьи' started by _Great_, 27 Jan 2007.

  1. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    Article: Splicing
    Author: Great
    Date: 27.01.2007
    Lang: C/C++
    Note: Статья для новчиков с разъяснением перехвата функций методом сплайсинга.

    Splicing в переводе с английского - склеивание, сращивание. Суть этого метода перехвата функций заключается в замене первых байт функции на переход в функцию-перехватчик, которая сделает некоторые действия и вернет управление в программу. Функция-перехватчик может восстановить первые байты и запустить оригинальную функцию, обработав ее вывод перед возвращением в программу. Например, можно убрать "лишние" файлы в каталоге, "ненужные" процессы в системе и так далее, ну вы меня поняли =). Обычно эта методика применяется в пользовательском режиме, хотя некоторые фаерволы таким образом перехватывают функции ядра.
    Безусловно, этот метод не единственный. Другой распространенный метод - корректировка таблицы импорта приложения. Он обладает одним существенным недостатком - будет сложно (а порой и невозможно) ставить перехват, если приложение импортирует функции динамически и не ползуется импортом. Сплайсинг лишен таких недостатков, потому что не важно каким образом будет получен адрес функции, ведь функцию-то мы уже "пропатчили".
    Классический вариант сплайсинга - установка на место первых пяти байт инструкции JMP FAR HookProc (ровно столько занимает ее машинный код). Конечно, можно ставить и другие инструкции, например, CALL FAR addr, PUSH addr/RET FAT, MOV EDI, addr/CALL EDI - количество вариантов ограничено лишь фантазией программиста и возможностями ассемблера. Мы все же остановимся на классическом варианте - JMP FAR addr. Опкод этой команды - E9 XX YY ZZ QQ, где число QQZZYYXX (в 16ричном виде, конечно) предствляет собой разницу между адреом addr и адресом следующей за JMP инструкцией. То есть она равна addr-proc-5, т.к. JMP FAR занимает ровно 5 байт.
    Перед перезаписью функции нужно разрешить сперва запись в секцию кода, которая по умолчанию отключена, иначе мы рискуем нарваться на Access Violation, а в режиме ядра и вовсе на BSoD. С учетом всех поправок функции установки и снятия перехвата в адресном пространстве текущего процесса примут следущий вид:
    Code:
    // ставим локальный хук
    bool SetSplicingHook(void* pfnDst, void* pfnHook, UCHAR buffer[5])
    {
    	// проверка указателей
    	if(IsBadWritePtr(buffer, 5) || IsBadReadPtr(pfnDst, 5)) return false;
    	memcpy(buffer, pfnDst, 5); // бекап начала функции
    
    	// разрешаем запись в страницу с кодом
    	DWORD old = 0;
    	if(!VirtualProtect(pfnDst, 5, PAGE_READWRITE, &old)) return false; // мы запрашиваем 5 байт, но на самом деле изменятся права доступа всей страницы сразу
    	
    	// ставим JMP FAR
    	DWORD offset = (DWORD) pfnHook - (DWORD) pfnDst - 5;
    	*(BYTE*)pfnDst = 0xE9; // JMP FAR
    	*(DWORD*)((DWORD)pfnDst+1) = offset;
    
    	// восстанавливает старые атрибуты страницы
    	if(!VirtualProtect(pfnDst, 5, old, &old)) return false;
    
    	// ништяк
    	return true;
    }
    
    // снимаем локальный хук
    void UnsetSplicingHook(void* pfnDst, UCHAR buffer[5])
    {
    	DWORD old = 0;
    	if(!VirtualProtect(pfnDst, 5, PAGE_READWRITE, &old)) return;
    	memcpy(pfnDst, buffer, 5); // восстанавливаем первые 5 байт из бекапа
    	if(!VirtualProtect(pfnDst, 5, old, &old)) return;
    }
    
    Позаботиться о 5-байтовом буфере должен будет пользователь.
    С чужим процессом ситуация аналогичная, лишь VirtualProtect меняется на VirtualProtectEx, а прямое чтение/запись на ReadProcessMemory/WriteProcessMemory.
    Инъекция в чужой процесс:
    Code:
    // ставим хук на другой процесс
    bool SetSplicingHookEx(HANDLE hProcess, void* pfnDst, void* pfnHook, UCHAR buffer[5])
    { // аналогично SetSplicingHook
    	DWORD t = 0;
    	if(!ReadProcessMemory(hProcess, pfnDst, buffer, 5, &t) || t!=5) return false;
    
    	DWORD old = 0;
    	if(!VirtualProtectEx(hProcess, pfnDst, 5, PAGE_READWRITE, &old)) return false;
    
    	DWORD offset = (DWORD) pfnHook - (DWORD) pfnDst - 5;
    	if(!WriteProcessMemory(hProcess, pfnDst, "\xE9", 1, &t) || t!=1) return false;
    	if(!WriteProcessMemory(hProcess, (LPVOID)((DWORD)pfnDst+1), &offset, 4, &t) || t!=4) return false;
    	if(!VirtualProtectEx(hProcess, pfnDst, 5, old, &old)) return false;
    
    	return true;
    }
    
    // снимаем хук с другого процесса
    void UnsetSplicingHookEx(HANDLE hProcess, void* pfnDst, UCHAR buffer[5])
    { // аналогично UnsetSplicingHook
    	DWORD old = 0, t = 0;
    	if(!VirtualProtectEx(hProcess, pfnDst, 5, PAGE_READWRITE, &old)) return;
    	WriteProcessMemory(hProcess, pfnDst, buffer, 5, &t);
    	if(!VirtualProtectEx(hProcess, pfnDst, 5, old, &old)) return;
    }
    Чтобы удобно объявлять функции перехвата и не забывать про буфера для 5 байт, мы объявим макросы:
    Code:
    // макросы для удобного объявления/установки/снятия хуков
    
    // объявить хук
    #define DECLARE_HOOK(RET, FUNC, PARAMS) UCHAR buf##FUNC[5]; RET x##FUNC PARAMS
    
    int ErrMessageBox(int,char* e,int,int)
    {
    	char t[1024];
    	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), 0, t, sizeof(t), 0);
    	return MessageBox(0, t, e, MB_ICONERROR);
    }
    
    // поставить локальный хук
    #define SET_FUNC_HOOK(FUNC) if(!SetSplicingHook(FUNC, x##FUNC, buf##FUNC)) ExitProcess(ErrMessageBox(0, "Cannot set hook to " #FUNC, 0, MB_ICONERROR));
    #define UNSET_FUNC_HOOK(FUNC) UnsetSplicingHook(FUNC, buf##FUNC);
    
    // поставить хук на чужой процесс P
    #define SET_FUNC_HOOK_EX(P,FUNC) if(!SetSplicingHookEx(P,FUNC, x##FUNC, buf##FUNC)) ExitProcess(ErrMessageBox(0, "Cannot set hook to " #FUNC, 0, MB_ICONERROR));
    #define UNSET_FUNC_HOOK_EX(P,FUNC) UnsetSplicingHookEx(P,FUNC, buf##FUNC);
    
    // ставим хуки по указателю
    
    #define DECLARE_HOOK_LP(RET, FUNC, PARAMS) RET (WINAPI *lp##FUNC) PARAMS; UCHAR buf##FUNC[5]; RET WINAPI x##FUNC PARAMS
    
    #define GET_HOOK_LP(MOD, FUNC) 	*(FARPROC*)&lp##FUNC = GetProcAddress(LoadLibrary(MOD), #FUNC);
    
    #define SET_FUNC_HOOK_LP(FUNC) if(!SetSplicingHook(lp##FUNC, x##FUNC, buf##FUNC)) ExitProcess(ErrMessageBox(0, "Cannot set hook to " #FUNC, 0, MB_ICONERROR));
    #define UNSET_FUNC_HOOK_LP(FUNC) UnsetSplicingHook(lp##FUNC, buf##FUNC);
    
    #define SET_FUNC_HOOK_EX_LP(P,FUNC) if(!SetSplicingHookEx(P,lp##FUNC, x##FUNC, buf##FUNC)) ExitProcess(ErrMessageBox(0, "Cannot set hook to " #FUNC, 0, MB_ICONERROR));
    #define UNSET_FUNC_HOOK_EX_LP(P,FUNC) UnsetSplicingHookEx(P,lp##FUNC, buf##FUNC);
    
    Макрос DECLARE_HOOK объявляет глобальный буфер (его имя - "buf"+имя функции) и функцию-замену для перехватываемой функции (ее имя будет "x"+имя оригинальной функции).
    SET_FUNC_HOOK и UNSET_FUNC_HOOK ставят и снимают перехват с текущем процессе. Аналогично SET_FUNC_HOOK_EX и UNSET_FUNC_HOOK_EX ставят и снимают перехват с чужого процесса.
    Третий набор функций служит для перехвата функций в чужом процессе, если у нас есть ее адрес. (Первые пользовались лишь импортом текущего процесса).
    Адрес хранится в переменной с именем "lp"+имя функции, остальное аналогично за исключением того, что функция тут объявляется с модификатором WINAPI (тоже самое, что и __stdcall), потому что в основном мы будем перехватывать именно апишки, а писать несколько макросов для каждого типа функции будет жирновато.

    Рассмотрим, наконец, пример :) Мы напишем программу, которая запускает Task Manager и перехватывает ему ntdll!ZwQuerySystemInformation и подкорректируем список процессов. А именно, мы вернем лишь один процесс с именем "GR8 0wn3d u".
    Правда чтобы безнаказанно расставлять перехваты в чужом процессе, нам надо сначала перенести свой код в его адресное пространство. Чтобы не заморачиваться с переносом каждой функции, мы просто аккуратно страничку за страничкой перенем ВЕСЬ наш EXE-модуль в чужой процесс, вместе с MZ и PE-заголовками, кодом, данными, импортом и прочим. Чтобы быть уверенным, что по нашим адресам в чужом адресном пространстве ничего нет, мы поставим себе базу повыше - где-нибудь около 29A00000.
    Опишем функцию для переноса себя в чужой процесс (из статьи Gorl'а "Программа-невидимка". хотел написать сам, потом плюнул и взял готовую, только слегка модифицировал):
    Code:
    bool TransferProgramEx(HANDLE hProcess)
    {
    	// получаем свою базу загрузки и освобождаем память в чужом адресном пространстве по этому виртуальному адресу
    	HMODULE g_module = GetModuleHandle(0);
    	VirtualFreeEx(hProcess, g_module, 0, MEM_RELEASE);
    	
    	// получаем свой размер EXE
    	DWORD dwSize = ((PIMAGE_OPTIONAL_HEADER)((LPVOID)((BYTE *)(g_module) + ((PIMAGE_DOS_HEADER)(g_module))->e_lfanew + 
    		sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER))))->SizeOfImage;
    
    	// выделяем память под свое тело
    	char *pMem = (char *)VirtualAllocEx(hProcess, g_module, dwSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    	if(pMem == NULL) return FALSE;
    		
    	DWORD dwOldProt, dwNumBytes, i;
    	MEMORY_BASIC_INFORMATION mbi;
    	 
    	// постранично копируем себя в выделенную память по тем же виртуальным адресам, что и здесь
    	VirtualQueryEx(hProcess, pMem, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    	while (mbi.Protect!=PAGE_NOACCESS && mbi.RegionSize!=0)
    	{
    		if (!(mbi.Protect & PAGE_GUARD))
    		{
    			for (i = 0; i < mbi.RegionSize; i += 0x1000)
    			{
    				VirtualProtectEx(hProcess, pMem + i, 0x1000,PAGE_EXECUTE_READWRITE, &dwOldProt);
    				WriteProcessMemory(hProcess, pMem + i, pMem + i, 0x1000, &dwNumBytes);
    			}
    		}
    		pMem += mbi.RegionSize;
    		VirtualQueryEx(hProcess, pMem, &mbi, sizeof(MEMORY_BASIC_INFORMATION));	
    	}
    
    	// все ок ;)
    	return true;
    }
    Теперь можно смело рваться в бой, то есть перехватывать ZwQuerySystemInformation. Сначала опишем структуры и типы, которые она использует:
    Code:
    typedef LONG    NTSTATUS;
    typedef LONG    KPRIORITY;
    
    #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
    
    #define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)
    #define STATUS_INFO_LENGTH_MISMATCH      ((NTSTATUS)0xC0000004L)
    
    #define SystemProcessesAndThreadsInformation    5
    
    typedef struct _UNICODE_STRING {
        USHORT        Length;
        USHORT        MaximumLength;
        PWSTR         Buffer;
    } UNICODE_STRING;
    
    typedef struct _SYSTEM_PROCESSES {
        ULONG             NextEntryDelta;
        ULONG             ThreadCount;
        ULONG             Reserved1[6];
        LARGE_INTEGER     CreateTime;
        LARGE_INTEGER     UserTime;
        LARGE_INTEGER     KernelTime;
        UNICODE_STRING    ProcessName;
        KPRIORITY         BasePriority;
        ULONG             ProcessId;
        ULONG             InheritedFromProcessId;
        ULONG             HandleCount;
        ULONG             Reserved2[2];
    //    VM_COUNTERS       VmCounters;
    //    SYSTEM_THREADS    Threads[1];
    } SYSTEM_PROCESSES, * PSYSTEM_PROCESSES;
    Собственно сам обработчик будет очень простым:
    Code:
    DECLARE_HOOK_LP(NTSTATUS, ZwQuerySystemInformation, 
    				(UINT SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength))
    {
    	UNSET_FUNC_HOOK_LP(ZwQuerySystemInformation);
    	NTSTATUS ret = lpZwQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
    	SET_FUNC_HOOK_LP(ZwQuerySystemInformation);
    	if(ret != STATUS_SUCCESS)
    		return ret;
    
    	if(SystemInformationClass == SystemProcessesAndThreadsInformation)
    	{
    		PSYSTEM_PROCESSES pProcesses = (PSYSTEM_PROCESSES)SystemInformation;
    
    		memset(pProcesses, 0, sizeof(SYSTEM_PROCESSES));
    		pProcesses->NextEntryDelta = 0;
    		pProcesses->ProcessId = 1;
    		pProcesses->ProcessName.Buffer = L"GR8 0wn3d u";
    		pProcesses->ProcessName.Length = 100;
    	}
    
    	return ret;
    }
    Если вызывают его с параметром SystemProcessesAndThreadsInformation, возвращаем всего лишь один процесс "GR8 0wn3d u", выставляя такое значение имени процесса. Длинну ставим 100 с рассчетом на то, что этого точно хватит. Обнуляем поле NextEntryDelta в списке процессов, чтобы показать, что это первый и последний процесс в списке.
    Собственно и сама установка перехвата - стартуем таск менеджер, ждем, пока он подготовится к вводу, останавливаем поток, ставим хук, переносим себя в его адресное пространство и восстанавливаем поток:
    Code:
    INT APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, INT)
    {
    	// стартуем exe'шник
    	STARTUPINFO si = {sizeof(si)};
    	PROCESS_INFORMATION pi = {0};
    	if(!CreateProcess(0, "taskmgr.exe", 0, 0, TRUE, 0, 0, 0, &si, &pi))
    		return MessageBox(0, "Cannot create process", 0, MB_ICONERROR);
    	HANDLE hProcess = pi.hProcess;
    	HANDLE hThread = pi.hThread;
    	
    	// ждем пока он запустится
    	WaitForInputIdle(hProcess, 100);
    
    	SuspendThread(hThread);
    
    	// получаем адреса
    	GET_HOOK_LP("ntdll.dll", ZwQuerySystemInformation);
    
    	// ставим хуки
    	SET_FUNC_HOOK_EX_LP(hProcess, ZwQuerySystemInformation);
    
    	// переносим свою тушу
    	if(!TransferProgramEx(hProcess))
    		return TerminateProcess(hProcess, 0), MessageBox(0, "Cannot copy module", 0, MB_ICONERROR);
    
    	ResumeThread(hThread);
    
    	return 0;
    }
    Результат не заставит себя ждать :)
    [​IMG]

    Позволю себе на этом откланиться. Пока )
     
    22 people like this.
  2. Arin

    Arin Elder - Старейшина

    Joined:
    20 Dec 2006
    Messages:
    67
    Likes Received:
    29
    Reputations:
    6
    За статью действительно респект. Хотя я и мало понял, но интуиция подсказывает - это интересно!
     
  3. 4xks

    4xks Banned

    Joined:
    2 Mar 2007
    Messages:
    9
    Likes Received:
    0
    Reputations:
    -3
    Да палицо ето дело фаерами модными теперь, или я неправ?
     
  4. Nevazno

    Nevazno New Member

    Joined:
    13 May 2010
    Messages:
    1
    Likes Received:
    0
    Reputations:
    0
    Здравствуйте.
    Прошу помощи в хуке ZwQuerySystemInformation.
    Следующий код аналогичен приведённому выше (переписан без макросов, т.к. с макрасами был невозможен дебаг).
    Но почему-то не работает.
    Параметр VirtualProtect() PAGE_READWRITE был изменён на PAGE_EXECUTE_READWRITE, т.к. с PAGE_READWRITE вылетал accsess violation.
    Прога считает вызовы ZwQuerySystemInformation. В пошаговой отладке видно, что память по указателю исходной функции изменяется но почему-то диспетчер задач показывает все процессы и счётчик вызовов не изменяется.
    Работаю в MSVC 2008, OC windows 7 (слас другу с XP - ситуация аналогична).

    Заранее спасибо.

    PHP:
    #include <windows.h>

    LRESULT CALLBACK WndProc(HWNDUINTWPARAMLPARAM);

    WCHAR wch[60];
    int i;

    typedef LONG    NTSTATUS;
    typedef LONG    KPRIORITY;

    #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)

    #define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)
    #define STATUS_INFO_LENGTH_MISMATCH      ((NTSTATUS)0xC0000004L)

    #define SystemProcessesAndThreadsInformation    5

    typedef struct _UNICODE_STRING {
        
    USHORT        Length;
        
    USHORT        MaximumLength;
        
    PWSTR         Buffer;
    UNICODE_STRING;

    typedef struct _SYSTEM_PROCESSES {
        
    ULONG             NextEntryDelta;
        
    ULONG             ThreadCount;
        
    ULONG             Reserved1[6];
        
    LARGE_INTEGER     CreateTime;
        
    LARGE_INTEGER     UserTime;
        
    LARGE_INTEGER     KernelTime;
        
    UNICODE_STRING    ProcessName;
        
    KPRIORITY         BasePriority;
        
    ULONG             ProcessId;
        
    ULONG             InheritedFromProcessId;
        
    ULONG             HandleCount;
        
    ULONG             Reserved2[2];
    //    VM_COUNTERS       VmCounters;
    //    SYSTEM_THREADS    Threads[1];
    SYSTEM_PROCESSES, * PSYSTEM_PROCESSES;

    UCHAR bufZwQSI[5]; //буфер
    typedef NTSTATUS (WINAPI *pWinApiF) (UINT SystemInformationClassPVOID SystemInformationULONG SystemInformationLengthPULONG ReturnLength);

    pWinApiF lpZwQSI//указатель на ZwQuerySystemInformation

    NTSTATUS WINAPI xZwQSI(UINT SystemInformationClassPVOID SystemInformationULONG SystemInformationLengthPULONG ReturnLength);

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // ставим локальный хук
    bool SetSplicingHook(pWinApiF pfnDstpWinApiF pfnHookUCHAR buffer[5])
    {
        
    // проверка указателей
        
    if(IsBadWritePtr(buffer5) || IsBadReadPtr(pfnDst5)) return false;
        
    memcpy(bufferpfnDst5); // бекап начала функции
        // разрешаем запись в страницу с кодом
        
    DWORD old 0;
        if(!
    VirtualProtect(pfnDst5PAGE_EXECUTE_READWRITE, &old)) return false// мы запрашиваем 5 байт, но на самом деле изменятся права доступа всей страницы сразу
        
        // ставим JMP FAR
        
    DWORD offset = (DWORDpfnHook - (DWORDpfnDst 5;
        *(
    BYTE*)pfnDst 0xE9// JMP FAR
        
    *(DWORD*)((DWORD)pfnDst+1) = offset;

        
    // восстанавливает старые атрибуты страницы
        
    if(!VirtualProtect(pfnDst5old, &old)) return false;

        
    // ништяк
        
    return true;
    }

    // снимаем локальный хук
    void UnsetSplicingHook(pWinApiF pfnDstUCHAR buffer[5])
    {
        
    DWORD old 0;
        if(!
    VirtualProtect(pfnDst5PAGE_EXECUTE_READWRITE, &old)) return;
        
    memcpy(pfnDstbuffer5); // восстанавливаем первые 5 байт из бекапа
        
    if(!VirtualProtect(pfnDst5old, &old)) return;
    }

    NTSTATUS WINAPI xZwQSI(UINT SystemInformationClassPVOID SystemInformationULONG SystemInformationLengthPULONG ReturnLength)
    {
        
    wsprintf(wch,L"%d",++i);
        
    UnsetSplicingHook(lpZwQSIbufZwQSI);
        
    NTSTATUS ret lpZwQSI(SystemInformationClass,   SystemInformationSystemInformationLengthReturnLength);
        if(!
    SetSplicingHook(lpZwQSIxZwQSIbufZwQSI))
        {
            
    MessageBox(NULLL"Cannot set hook to ZwQuerySystemInformation"L"Error"MB_OK);
            
    ExitProcess(0);
        }
        if(
    ret != STATUS_SUCCESS)
            return 
    ret;

        if(
    SystemInformationClass == SystemProcessesAndThreadsInformation)
        {
            
    PSYSTEM_PROCESSES pProcesses = (PSYSTEM_PROCESSES)SystemInformation;

            
    memset(pProcesses0sizeof(SYSTEM_PROCESSES));
            
    pProcesses->NextEntryDelta 0;
            
    pProcesses->ProcessId 1;
            
    pProcesses->ProcessName.Buffer L"GR8 0wn3d u";
            
    pProcesses->ProcessName.Length 100;
        }

        return 
    ret;
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////
    int WINAPI WinMain(HINSTANCE hInstanceHINSTANCE hPrevInstanceLPSTR lpCmdLineint nCmdShow)
    {
        
    wsprintf(wch,L"none");
        
    i=0;
    // получаем адреса
        
    *(FARPROC*)&lpZwQSI GetProcAddress(LoadLibrary(L"ntdll.dll"), "ZwQuerySystemInformation");
        if(!
    SetSplicingHook(lpZwQSIxZwQSIbufZwQSI))
        {
            
    MessageBox(NULLL"Cannot set hook to ZwQuerySystemInformation"L"Error"MB_OK);
            
    ExitProcess(0);
        }
    ////////////////////////////////////////////////////////
        
    HWND hMainWnd;  
        
    WCHAR szClassName[] = L"Hide";
        
    MSG msg;
        
    WNDCLASSEX wc;
        
    wc.cbSize        sizeof(wc);        
        
    wc.style         CS_HREDRAW CS_VREDRAW;
        
    wc.lpfnWndProc   WndProc;
        
    wc.cbClsExtra     0;
        
    wc.cbWndExtra    0;
        
    wc.hInstance     hInstance;
        
    wc.hIcon         LoadIcon(NULLIDI_APPLICATION);
        
    wc.hCursor       LoadCursor(NULLIDC_ARROW);
        
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        
    wc.lpszMenuName  NULL;
        
    wc.lpszClassName szClassName;
        
    wc.hIconSm       LoadIcon(NULLIDI_APPLICATION);

        if (!
    RegisterClassEx(&wc)) {
            
    MessageBox(NULLL"Cannot register class"L"Error"MB_OK);
            return 
    0;
        }

        
    hMainWnd CreateWindowEx
            
    NULL,szClassNameL"Hide",
            
    WS_CAPTION WS_SYSMENU,
            
    1000400150150,
            (
    HWND)NULL, (HMENU)NULL,
            (
    HINSTANCE)hInstanceNULL
        
    );
        
        if (!
    hMainWnd) {
            
    MessageBox(NULLL"Cannot create main window"L"Error"MB_OK);
            return 
    0;
        }

        
    ShowWindow(hMainWndnCmdShow);

        while (
    GetMessage(&msgNULL00))  {
            
    TranslateMessage(&msg);
            
    DispatchMessage(&msg);
        }

        return 
    msg.wParam;
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////
    LRESULT CALLBACK WndProc(HWND hWndUINT msgWPARAM wParamLPARAM lParam)
    {
        
    HDC hDC;
        
    PAINTSTRUCT ps;
        
    RECT rect;
        switch (
    msg)
        {
        case 
    WM_CREATE:
            
    SetTimer(hWnd,NULL,500,NULL);
            return 
    0;

        case 
    WM_TIMER:
                
    InvalidateRect(hWnd,NULL,TRUE);
            return 
    0;

        case 
    WM_PAINT:
            
    hDC BeginPaint(hWnd, &ps);
            
    GetClientRect(hWnd,&rect);
            
    DrawText(hDCwch, -1, &rect,
                
    DT_SINGLELINE DT_CENTER DT_VCENTER );
            
    EndPaint(hWnd, &ps);
            return 
    0;

        case 
    WM_CLOSE:
            
    DestroyWindow(hWnd);
            return 
    0;
        case 
    WM_DESTROY:
            
    PostQuitMessage(0);
            return 
    0;
        default:
            return 
    DefWindowProc(hWndmsgwParamlParam);
        }
        return 
    0;
    }