![]() |
Wyjście Spis treści Poprzedni Następny
Autor:
©Iczelion |
©2008 mgr Jerzy Wałaszek I LO w Tarnowie
|
Na tej lekcji kontynuujemy temat funkcji uruchomieniowych biblioteki Win32. Nauczymy się sposobów modyfikowania testowanego procesu.
Załaduj {ten przykład}
Z poprzedniej lekcji wiemy, jak załadować testowany proces i jak obsługiwać występujące w nim zdarzenie uruchomieniowe. Aby być użytecznym, nasz program musi umieć modyfikować testowany proces. W bibliotece Win32 istnieje kilka funkcji specjalnie przeznaczonych do tego celu.
ReadProcessMemory PROTO hProcess: DWORD,
lpBaseAddress: DWORD,
lpBuffer: DWORD,
nSize: DWORD,
lpNumberOfBytesRead: DWORD
Następne dwie funkcje API wymagają nieco wyjaśnień. W wielozadaniowym systemie operacyjnym, takim jak system Windows, może istnieć kilka programów pracujących jednocześnie. System Windows daje każdemu wątkowi pewien przedział czasu. Gdy przedział ten kończy się, system Windows zamraża bieżący wątek i przełącza wykonanie na następny, który posiada najwyższy priorytet. Tuż przed przełączeniem zachowywany jest stan rejestrów bieżącego wątku, aby gdy nadejdzie jego czas, mógł być z powrotem reaktywowany. System Windows potrafi odtworzyć ostatnie "środowisko" wątku. Zapamiętane wartości rejestrów są wspólnie nazywane kontekstem.
Wróćmy do tematu. Gdy pojawia się zdarzenie uruchomieniowe, system Windows zawiesza testowany proces. Kontekst procesu zostaje zapamiętany. Ponieważ testowany proces jest zawieszony, możemy mieć pewność, iż wartości kontekstu pozostaną niezmienione. Wartości te możemy pobrać przy pomocy funkcji GetThreadContext, a zmienić za pomocą SetThreadContext.
Te dwie funkcje API są bardzo potężne. Przy ich pomocy masz pod palcami moc w stylu VxD nad testowanym procesem: możesz zmienić zapamiętane wartości rejestrów, a tuż przed wznowieniem wątku wartości w kontekście zostaną z powrotem umieszczone w rejestrach. Każda zmiana w kontekście odzwierciedli się w testowanym procesie. Pomyśl o tym: możesz nawet zmienić zawartość rejestru licznika rozkazów eip i przekierować wykonanie procesu do dowolnego adresu! W normalnych warunkach nie mógłbyś tego dokonać.
GetThreadContext PROTO hThread:DWORD, lpContext:DWORD
Funkcja SetThreadContext posiada identyczne parametry. Zobaczmy, jak wygląda struktura CONTEXT:
MAXIMUM_SUPPORTED_EXTENSION EQU 512
SIZE_OF_80387_REGISTERS EQU 80
FLOATING_SAVE_AREA STRUCT
ControlWord DWORD ?
StatusWord DWORD ?
TagWord DWORD ?
ErrorOffset DWORD ?
ErrorSelector DWORD ?
DataOffset DWORD ?
DataSelector DWORD ?
RegisterArea BYTE SIZE_OF_80387_REGISTERS DUP(?)
Cr0NpxState DWORD ?
FLOATING_SAVE_AREA ENDS
CONTEXT STRUCT
ContextFlags DWORD ?
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_DEBUG_REGISTERS
iDr0 DWORD ?
iDr1 DWORD ?
iDr2 DWORD ?
iDr3 DWORD ?
iDr6 DWORD ?
iDr7 DWORD ?
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_FLOATING_POINT
FloatSave FLOATING_SAVE_AREA <>
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_SEGMENTS
regGs DWORD ?
regFs DWORD ?
regEs DWORD ?
regDs DWORD ?
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_INTEGER
regEdi DWORD ?
regEsi DWORD ?
regEbx DWORD ?
regEdx DWORD ?
regEcx DWORD ?
regEax DWORD ?
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_CONTROL
regEbp DWORD ?
regEip DWORD ?
regCs DWORD ?
regFlag DWORD ?
regEsp DWORD ?
regSs DWORD ?
; Ta sekcja jest zwracana, gdy ContextFlags
; zawiera wartość CONTEXT_EXTENDED_REGISTERS
ExtendedRegisters DWORD MAXIMUM_SUPPORTED_EXTENSION DUP(?)
CONTEXT ENDSJak możesz zaobserwować, pola tych struktur są kopiami prawdziwych rejestrów mikroprocesora. Zanim będziesz mógł skorzystać z tej struktury, musisz określić w polu ContextFlags, którą grupę rejestrów chcesz odczytać lub zapisać. Na przykład, jeśli chcesz odczytać / zapisać wszystkie rejestry, to musisz określić w ContextFlags wartość CONTEXT_FULL. Jeśli jedynie chcesz zapisać / odczytać pola regEbp, regEip, regCs, regFlag, regEsp i regSs, to musisz określić w ContextFlags wartość CONTEXT_CONTROL.
Używając strukturę CONTEXT powinieneś pamiętać o jednej rzeczy: musi ona być wyrównana do granicy podwójnego słowa, w przeciwnym wypadku otrzymasz dziwne wyniki w systemach NT/2000/XP. Powyżej deklaracji tej struktury należy zatem umieścić dyrektywę asemblera ALIGN DWORD (wyrównanie do granicy podwójnego słowa), np. w sposób następujący:
ALIGN DWORD MyContext CONTEXT <>
Pierwszy przykład demonstruje zastosowanie funkcji DebugActiveProcess. Najpierw potrzebujesz uruchomić program docelowy o nazwie win.exe, który wchodzi w pętlę nieskończoną tuz przed pokazaniem swojego okna na ekranie. Następnie uruchamiasz ten przykład, który przyłączy się do win.exe i zmodyfikuje jego kod, tak aby program ten wyszedł z pętli nieskończonej i pokazał swoje własne okno.
Plik DEBUG2.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\comdlg32.inc
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
INCLUDELIB \masm32\lib\user32.lib
.DATA
AppName DB "Przykład uruchamiania nr 2", 0
ClassName DB "SimpleWinClass", 0
SearchFail DB "Nie można odnaleźć docelowego procesu", 0
TargetPatched DB "Program naprawiony!", 0
buffer DW 9090h
.DATA?
DBEvent DEBUG_EVENT <>
ProcessId DD ?
ThreadId DD ?
ALIGN DWORD
context CONTEXT <>
.CODE
start:
INVOKE FindWindow, ADDR ClassName, NULL
.IF eax!=NULL
INVOKE GetWindowThreadProcessId, eax, ADDR ProcessId
mov ThreadId, eax
INVOKE DebugActiveProcess, ProcessId
.WHILE TRUE
INVOKE WaitForDebugEvent, ADDR DBEvent, INFINITE
.BREAK .IF DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
.IF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
mov context.ContextFlags, CONTEXT_CONTROL
INVOKE GetThreadContext, DBEvent.u.CreateProcessInfo.hThread, ADDR context
INVOKE WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess,
context.regEip, ADDR buffer, 2, NULL
INVOKE MessageBox, 0, ADDR TargetPatched, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_CONTINUE
.CONTINUE
.ENDIF
.ENDIF
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_EXCEPTION_NOT_HANDLED
.ENDW
.ELSE
INVOKE MessageBox, 0, ADDR SearchFail, ADDR AppName, MB_OK + MB_ICONERROR
.ENDIF
INVOKE ExitProcess, 0
END start
To będzie nasz testowany proces. W rzeczywistości jest to lekko zmodyfikowany program z lekcji o prostym oknie, w którym została wstawiona pętla nieskończona tuż przed wejściem do pętli wiadomości.
Plik WIN.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\user32.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib
WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD
.DATA
ClassName DB "SimpleWinClass", 0
AppName DB "Okno z pętlą nieskończoną", 0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
INVOKE GetModuleHandle, NULL
mov hInstance, eax
INVOKE GetCommandLine
mov CommandLine, eax
INVOKE WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
INVOKE ExitProcess, eax
WinMain PROC hInst: HINSTANCE,
hPrevInst: HINSTANCE,
CmdLine: LPSTR,
CmdShow: DWORD
LOCAL wc : WNDCLASSEX
LOCAL msg : MSG
LOCAL hwnd : HWND
mov wc.cbSize, SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW OR CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra, NULL
mov wc.cbWndExtra, NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground, COLOR_WINDOW+1
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
INVOKE RegisterClassEx, ADDR wc
INVOKE CreateWindowEx, NULL, ADDR ClassName, ADDR AppName,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
hInst, NULL
mov hwnd, eax
; Ten rozkaz powoduje powstanie pętli nieskończonej
jmp $
; Tutaj rozpoczyna się dalsza część programu
INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
INVOKE UpdateWindow, hwnd
.WHILE TRUE
INVOKE GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax)
INVOKE TranslateMessage, ADDR msg
INVOKE DispatchMessage, ADDR msg
.ENDW
mov eax, msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
INVOKE PostQuitMessage, NULL
.ELSE
INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc ENDP
END start
INVOKE FindWindow, ADDR ClassName, NULL
Nasz program wymaga dołączenia go do testowanego procesu przy pomocy funkcji DebugActiveProcess, która potrzebuje numeru ID procesu. Numer identyfikacyjny ID procesu możemy otrzymać wywołując funkcję GetWindowThreadProcessId, która z kolei potrzebuje uchwytu okna jako parametru. Zatem najpierw należy uzyskać uchwyt okna.
Przy pomocy FindWindow możemy określić nazwę klasy okna, które jest nam potrzebne. Zwrócony zostaje uchwyt do okna utworzonego przez tę klasę. Jeśli otrzymamy wartość NULL, to żadne okno nie powstało na podstawie tej klasy.
.IF eax!=NULL
INVOKE GetWindowThreadProcessId, eax, ADDR ProcessId
mov ThreadId, eax
INVOKE DebugActiveProcess, ProcessId
Po otrzymaniu numeru identyfikacyjnego ID procesu możemy wywołać funkcję DebugActiveProcess. Następnie wchodzimy do pętli oczekującej na zdarzenia uruchomieniowe.
.IF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
mov context.ContextFlags, CONTEXT_CONTROL
INVOKE GetThreadContext, DBEvent.u.CreateProcessInfo.hThread,
ADDR context
Gdy otrzymamy powiadomienie CREATE_PROCESS_DEBUG_INFO, to oznacza ono zawieszenie działania procesu testowanego, co umożliwi nam przeprowadzenie w nim operacji chirurgicznej. W tym przykładzie zastąpimy instrukcję skoku (jmp $ - kod 0EBh 0FEh) tworzącego pętlę nieskończoną dwiema instrukcjami NOP (kod 90h).
Najpierw musimy otrzymać adres tej instrukcji w obrębie procesu. Ponieważ testowany proces do czasu przyłączenia naszego programu już znajduje się w pętli nieskończonej, rejestr eip będzie zawsze wskazywał na tę instrukcję. Zatem wystarczy pobrać zawartość rejestru eip. Do osiągnięcia tego celu wykorzystujemy funkcję GetThreadContext. Ustawiamy pole ContextFlags na wartość CONTEXT_CONTROL, aby przekazać funkcji GetThreadContext, iż chcemy wypełnić strukturę CONTEXT rejestrami sterującymi.
INVOKE WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess,
context.regEip, ADDR buffer, 2, NULL
Teraz gdy posiadamy zawartość rejestru eip, możemy wywołać WriteProcessMemory w celu zastąpienia instrukcji "jmp $" dwiema instrukcjami NOP, co pomoże naszemu procesowi testowanemu wyjść trwale z pętli nieskończonej. Po tej operacji wyświetlamy użytkownikowi krótką wiadomość, a następnie wywołujemy ContinueDebugEvent w celu wznowienia działania procesu testowanego. Ponieważ instrukcja "jmp $" została zastąpiona instrukcjami NOP, proces ten zacznie wykonywać dalsze instrukcje, co spowoduje wyświetlenie okna i wejście do pętli wiadomości. Jako dowód na ekranie pojawi się jego okno.
Drugi przykład wykorzystuje nieco inne podejście do przerwania pętli nieskończonej w procesie testowanym.
Plik DEBUG3.ASM
.386
.MODEL FLAT, STDCALL
OPTION CASEMAP:NONE
INCLUDE \masm32\include\windows.inc
INCLUDE \masm32\include\kernel32.inc
INCLUDE \masm32\include\comdlg32.inc
INCLUDE \masm32\include\user32.inc
INCLUDELIB \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\comdlg32.lib
INCLUDELIB \masm32\lib\user32.lib
.DATA
AppName DB "Przykład uruchamiania nr 3", 0
ClassName DB "SimpleWinClass", 0
SearchFail DB "Nie można odnaleźć docelowego procesu", 0
LoopSkipped DB "Przeskoczone dwa bajty!", 0
.DATA?
DBEvent DEBUG_EVENT <>
ProcessId DD ?
ThreadId DD ?
ALIGN DWORD
context CONTEXT <>
.CODE
start:
INVOKE FindWindow, ADDR ClassName, NULL
.IF eax!=NULL
INVOKE GetWindowThreadProcessId, eax, ADDR ProcessId
mov ThreadId, eax
INVOKE DebugActiveProcess, ProcessId
.WHILE TRUE
INVOKE WaitForDebugEvent, ADDR DBEvent, INFINITE
.BREAK .IF DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
.IF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
mov context.ContextFlags, CONTEXT_CONTROL
INVOKE GetThreadContext, DBEvent.u.CreateProcessInfo.hThread, ADDR context
add context.regEip, 2
INVOKE SetThreadContext, DBEvent.u.CreateProcessInfo.hThread, ADDR context
INVOKE MessageBox, 0, ADDR LoopSkipped, ADDR AppName,
MB_OK + MB_ICONINFORMATION
.ELSEIF DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.IF DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_CONTINUE
.CONTINUE
.ENDIF
.ENDIF
INVOKE ContinueDebugEvent, DBEvent.dwProcessId,
DBEvent.dwThreadId,
DBG_EXCEPTION_NOT_HANDLED
.ENDW
.ELSE
INVOKE MessageBox, 0, ADDR SearchFail, ADDR AppName, MB_OK + MB_ICONERROR
.ENDIF
INVOKE ExitProcess, 0
END start
.IF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
mov context.ContextFlags, CONTEXT_CONTROL
INVOKE GetThreadContext, DBEvent.u.CreateProcessInfo.hThread,
ADDR context
add context.regEip, 2
INVOKE SetThreadContext, DBEvent.u.CreateProcessInfo.hThread,
ADDR context
Program ten również wywołuje funkcję GetThreadContext do pobrania bieżącej zawartości rejestru eip, lecz zamiast zastępowania instrukcji jmp $, zwiększa on wartość pola regEip o 2, aby "przeskoczyć ponad" tą instrukcją. W wyniku tej modyfikacji po wznowieniu procesu testowanego rozpocznie on wykonywanie poleceń za instrukcją skoku.
Teraz możesz zobaczyć potęgę funkcji Get/SetThreadContext. Można również modyfikować inne rejestry w strukturze, co będzie od razu odzwierciedlone w testowanym procesie po jego wznowieniu. Wolno ci nawet wstawiać instrukcje int 3h, aby wywoływać zatrzymania kontrolne procesu testowanego.
|
Autorem
kursu jest
Iczelion. Kurs
programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną
zgodą autora. |
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe