Ну хватит разглагольствовать, смотри MsgProc и ECreateRemoteThread:
ECreateRemoteThreadNt proc USES ebx ecx esi hWind: HANDLE, dwStackSize: DWORD, lpStartAddress: DWORD,  dwCreationFlags: DWORD
local ThreadID      : DWORD 
local HookH         : HHOOK 
local hEvent         : HANDLE 
local pLastBase   : DWORD 
local margs          : CREATE_THREAD_ARGS
local FM              : HANDLE 
local MVF            : DWORD
;  Заполняем структуру параметров CreateThread'у 
          push dwStackSize
          pop margs.dwStackSize
          push lpStartAddress
          pop margs.lpStartAddress
          push dwCreationFlags
          pop margs.dwCreationFlags
;  Создаем файл, проецируемый в память... 
          invoke CreateFileMapping, INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof CREATE_THREAD_ARGS, SADD("FM_CRT_HOOK")
          mov FM, eax 
          .if FM==0
          	mov eax, -3 
          	ret
          .endif
          invoke MapViewOfFile, FM, FILE_MAP_WRITE, 0, 0, sizeof CREATE_THREAD_ARGS
          mov MVF, eax
          .if MVF==NULL 
          	invoke CloseHandle, FM
          	mov eax, -4 
          	ret
          .endif 
;  Передаем параметры 
          invoke MemCopy, addr margs, MVF, sizeof CREATE_THREAD_ARGS 
;  Узнаем поток, в который будем внедряться 
	invoke GetWindowThreadProcessId, hWind, NULL 
	mov ThreadID, eax 
	.if ThreadID==NULL 
          	invoke CloseHandle, FM
		mov eax, -1
		ret 
	.endif 
	invoke GetModuleHandle, NULL
	mov pLastBase, eax 
; Для синхронизации, чтобы не выгрузиться раньше выполнения LoadLibrary в ловушке...
	invoke CreateEvent, NULL, FALSE, FALSE, SADD("EVENT_CRT_HOOK")
	mov hEvent, eax 
;  Ставим локальный хук... Параметр, в котором должен быть базовый адрес библиотеки
	invoke SetWindowsHookEx, WH_GETMESSAGE, addr MsgProc, pLastBase, ThreadID
	mov HookH, eax 
	.if HookH==NULL 
          	invoke CloseHandle, FM
		mov eax, -2
		ret
	.endif 
;  Отсылаем сообщение, чтобы вызвалась MsgProc 
	invoke PostMessage, hWind, WM_USER+50, lpStartAddress, pLastBase
;  Ждем пока не выполниться LoadLibrary 
	invoke WaitForSingleObject, hEvent, INFINITE
	invoke UnhookWindowsHookEx, HookH 
;  Узнаем результат из того же MMF'a 
	mov eax,[MVF]
	dodata <CRT_RES !<!>>
	mov esi, ecx
	invoke MemCopy, eax, esi, sizeof CRT_RES 
	mov eax, esi
	push eax 
;  Закрываем за собой дверь
	invoke UnmapViewOfFile, MVF
	invoke CloseHandle, FM 
	invoke CloseHandle, hEvent 
	pop eax
	ret
ECreateRemoteThreadNt endp 
Теперь MsgProc:
MsgProc proc STDCALL USES ebx esi edi ecx code: DWORD, wparam: WPARAM, lparam: LPARAM 
local LL               : DWORD
local szModName: DWORD
local CT              : DWORD
local FM             : HANDLE
local lpThreadID   : DWORD 
local hEvent        : HANDLE
local lpvar           : DWORD
	.if code==HC_ACTION 
		assume edi: PTR MSG 
		mov edi, lparam
		.if [edi].message==WM_USER+50 
;  Увеличиваем счетчик блокировок нашего модуля, дабы не произошло AV
;  Находим LoadLibrary... 
			dodata <db "LoadLibraryA",0>
			push ecx
			dodata <db "kernel32.dll",0>
			push ecx 
			call EGetProcAddress 
			mov LL, eax 
;  Находим GetModuleFileName 
			dodata <db "GetModuleFileNameA",0>
			push ecx
			dodata <db "kernel32.dll",0>
			push ecx
			call EGetProcAddress
			mov esi, eax
;  Вызываем GetModuleFileName
			push 126
			dodata <db 126 dup(0)>
			mov szModName, ecx
			push ecx
			call GetCurrModBase 
			push eax
			call esi 
;  Вызываем LoadLibrary на наш модуль... 
			push szModName
			call LL 
;  Для синхронизации
;  Находим OpenEvent 
	                    dodata <db "OpenEventA",0> 
	                    push ecx
	                    dodata <db "kernel32.dll",0>
	                    push ecx
	                    call EGetProcAddress 
;  Вызываем OpenEvent 
	                    dodata <db "EVENT_CRT_HOOK",0>
	                    push ecx
	                    push TRUE
	                    push EVENT_ALL_ACCESS
	                    call eax 
	                    mov hEvent, eax
;  Create'им thread 😉
;  Находим CreateThread
			dodata <db "CreateThread",0>
			push ecx
			dodata <db "kernel32.dll",0>
			push ecx 
			call EGetProcAddress 
			mov CT, eax 
;  Получаем параметры 
;  Находим OpenFileMapping
		          dodata <db "OpenFileMappingA",0>
		          push ecx
		          dodata <db "kernel32.dll",0>
		          push ecx
		          call EGetProcAddress
;  Вызываем OpenFileMapping
		          dodata <db "FM_CRT_HOOK",0>
		          push ecx
		          push FALSE
		          push FILE_MAP_WRITE 
		          call eax 
		          cmp eax, 0
		          jz exit
         		          mov ebx, eax
;  Находим MapViewOfFile
		  dodata <db "MapViewOfFile",0>
                              push ecx
                              dodata <db "kernel32.dll",0>
                              push ecx
                              call EGetProcAddress 
;  Вызываем MapViewOfFile 
                              push sizeof CREATE_THREAD_ARGS
                              push 0
                              push 0 
                              push FILE_MAP_WRITE
                              push ebx
                              call eax 
                              cmp eax, 0
                              jz exit 
                              mov esi, eax 
		          assume esi: PTR CREATE_THREAD_ARGS 
;  Вызываем CreateThread 
			lea eax, lpThreadID 
			push eax 
			push [esi].dwCreationFlags 
			push NULL
;  Узнаем смещение ThreadProc'a
;  Вычитаем из старого адреса старую базу 
			mov ebx, [edi].wParam
			sub ebx, [edi].lParam 
;  Теперь вычисляем новый адрес 
			call GetCurrModBase
			add ebx, eax 
			push ebx 
			push [esi].dwStackSize
			push NULL 
			call CT 
;  Возвращаем результат
			assume esi: PTR CRT_RES
			push lpThreadID 
			pop [esi].ThreadID
			push eax
			pop [esi].hThread
			call GetCurrModBase
			push eax
			pop [esi].hModule 
; Находим SetEvent
	                    dodata <db "SetEvent",0> 
	                    push ecx
	                    dodata <db "kernel32.dll",0> 
	                    push ecx 
	                    call EGetProcAddress 
;  Вызываем SetEvent, чтобы продолжить основную прогу... 
	                    push hEvent
	                    call eax
		.endif 
	.endif 
exit:
	ret
MsgProc endp 
Для удобства я объявил пару структур:
CREATE_THREAD_ARGS struct	; Параметры; смысл и название идентичны параметрам CT
	dwStackSize        dd 0
	lpStartAddress      dd 0
	dwCreationFlags    dd 0
CREATE_THREAD_ARGS ends
CRT_RES struct ; Результат, возвращаемый ECreateRemoteThread
	hModule	dd	0     ; база нашего модуля в чужом адресном пространстве
	ThreadID	dd	0     ; Индификатор нового потока
	hThread	dd	0     ; Хэндл нового потока
CRT_RES ends
Практика.
  Что я сделал в качестве примера использования EcreateRemoteThread()??? Банальный
MessageBoxA. Но так как сообщение заставляет прогу «застыть», то мы можем протестировать – не происходит ли досрочная выгрузка нашего модуля. Если происходит, то возврат из MBA будет указывать
в никуда, появиться Access Violation. Это ты сможешь пронаблюдать, если уберешь из MsgProc’a LoadLibraryA. Рассмотрим текст  главного модуля примера, который я написал. Тут все просто:
;-- Start of MakeIt.bat --
; @echo off
;
; if exist 1.obj del 1.obj
; if exist 1.exe del 1.exe
;
; c:\masm32\bin\ml /c /coff /nologo 1.asm
; c:\masm32\bin\Link /SUBSYSTEM:WINDOWS /FIXED:NO /MERGE:.rdata=.text /SECTION:.text,ERW 
; 1.obj
;
; dir 1.*
;
; pause
;-- End of MakeIt.bat --
    .386 
    .model flat, stdcall 
    option casemap :none 
      include c:\masm32\include\windows.inc         ;  Куча структур и еще много разной полезной хрени
      include c:\masm32\include\user32.inc             ;  Разные MessageBox()'ов и SetWindowsHookEx()'ов
      include c:\masm32\include\kernel32.inc          ;  ExitProcess, GetModuleHandle и т.д.
      include c:\masm32\macros\macros.asm           ;  Макросы 
      include ..\include\API_Emul.asm                     ;  Эмуляция апи функций+пару макросов by me 😉
      include ..\include\CRT_API.asm                      ;  CreateRemoteThread() by me 😉
      includelib c:\masm32\lib\user32.lib 
      includelib c:\masm32\lib\kernel32.lib
.code 
ThreadProc proc 
;  Находим MessageBox 
	dodata <db "MessageBoxA",0>
	push ecx
	dodata <db "user32.dll",0>
	push ecx
	call EGetProcAddress
;  Показываем MB
	push MB_OK
	dodata <db "GOTCHA!!!",0>
	push ecx 
	push ecx
	push 0
	call eax 
;  Завершаем поток
	dodata <db "ExitThread",0>
	push ecx
	dodata <db "kernel32.dll",0>
	push ecx
	call EGetProcAddress
	push 0
	call eax 
	ret 
ThreadProc endp 
start: 
main proc 
	invoke FindWindow, NULL, SADD("Калькулятор")
; Идентификатор окна в первом параметре
;  Второй параметр - размер стэка нового потока , делаем его дефолтным
(1Мб)
;  Третий – Точка входа потока в ГЛАВНОМ модуле.
;  Для примера возьмем его suspend’нутым
(4ый параметр)
	invoke ECreateRemoteThreadNt,  eax, 0,ThreadProc,CREATE_SUSPENDED 
;  Результат в виде структуры CRT_RES
	assume eax: PTR CRT_RES
	.if (eax>0) && ([eax].hThread!=NULL) 
;  В CRT_RES уже есть hThread, но он не имеет прав на приостановку/восстановление
		invoke OpenThread, THREAD_SUSPEND_RESUME, FALSE, [eax].ThreadID 
;  Запускаем поток
		invoke ResumeThread, eax 
	.endif 
	invoke ExitProcess, 0 
	ret 
main endp 
end start
Ну вот, тут все прокомментировано и должно быть понятно.
Если ты думаешь, что это все, что может этот подход к хукам, ты ошибаешься… Можно делать поистине удивительные вещи с этой фишкой! Мы с ZeroIce’ом пораскинули мозгами и пришли к выводу, что довольно легко можно сделать свой код ВООБЩЕ невидимым… То бишь ни в списке процессов, ни в списке потоков, ни в списке модулей, он светиться не будет! Как это сделать? Просто! Все по старой схеме, только вместо создания потока и увеличения счетчика модуля делаем VirtualAlloc и копируем в зарезервированную область нужный код! А потом передаем
туда управление. А если сделать его еще и полиморфиком ;). Только тут, конечно, есть недостаток. Поток, в который мы внедрились, приостановиться на время выполнения нашего внедренного кода, но ведь можно и сделать
так, чтобы эта фишка происходила по определенному событию… Например, реализовать перехват АПИ через изменение таблицы импорта, а наш код будет покоиться, пока не произойдет вызов перехватываемой АПИ ;). Реализацию этого фишкаря оставлю тебе… В то же время в папке 9х ты найдешь пример резервирования памяти в чужом процессе и копирования
туда кода, установление атрибута исполнения и т.д.
Но передача туда управления не произойдет! У нас другая тема, реализация перехвата АПИ – тоже избитая тема, ссылка в конце статьи+читай у Рихтера…
Инструменты
Для тестирования кода на платформе 9х использовался MS Windows ME+OllyDbg v1.09d
  Для тестирования кода на платформе NT использовался MS Windows XP Professional(build 2600 без sp)+SoftIce
  В качестве компилятора везде выступал masm32v8.0 by Hutch
Ссылки
1) Где купить Рихтера? Например http://www.books.ru/shop/books/8283
или http://www.books-shop.com/book89.html
или  http://www.mistral.ru/content/38168.shtml
(А ты че думал??? Электронный вариант? Черта с
два… Не для этого Рихтер работал, чтобы ты
на халяву его талмуд скатал)
2) masm32 by Hutch http://www.movsd.com/masmdl.htm
3) Описание таблицы экспорта http://www.wasm.ru/article.php?article=1002007
4) Эмуляция GetProcAddress’a http://www.wasm.ru/article.php?article=searchapi
5) Нахождение базы kernel’a http://sbvc.host.sk/articles/9.html
Greetzzzzz
Спасибо Four-F’у, который додумался, что все
это работает только из-за релоков. Если бы
не он, я бы, наверное, до сих пор делал бы /DEBUG
:).
Спасибо ZeroIce’у, который часто указывал
мне на баги и ошибки в моих примерах.