Войти

Показать полную графическую версию : Delphi | Работа с памятью чужого процесса, перехват API


Savant
21-07-2005, 20:15
Вопрос в PM пришел:
Привет!
Как не трудно догадаться,данная функция возвращает False,и я не уверен,всё ли правильно я делаю.
Итак:

var
eax : Pointer;
OldProtect : DWORD;
begin
VirtualProtectEx(processInfo.hProcess, eax, 2, PAGE_EXECUTE_READWRITE, @OldProtect);
end;

Указатель eax получен от GetProcAddress и имеет значение 7C5901D5 (адрес загрузки функции LoadLibraryA).
В общем,в отладчике можно наблюдать следующие ошибки :
C0000018 - STATUS_CONFLICTING_ADDRESSES
ERROR_INVALID_ADDRESS (GetLastError)
Что делать ?!

Вторым парметром должен идти адрес в памяти, зарезервированной функциями VirtualAlloc(Ex) c параметром MEM_RESERVE, а не указатель, полученный "от GetProcAddress и имеющий значение 7C5901D5 (адрес загрузки функции LoadLibraryA)".

lpAddress
[in] Pointer to the base address of the region of pages whose access protection attributes are to be changed.
All pages in the specified region must be within the same reserved region allocated when calling the VirtualAlloc or VirtualAllocEx function using MEM_RESERVE. The pages cannot span adjacent reserved regions that were allocated by separate calls to VirtualAlloc or VirtualAllocEx using MEM_RESERVE.

DillerInc
21-07-2005, 21:10
Savant
Ну,тогда я сдаюсь :( .
Дело в том,что мне необходимо изменить защитные атрибуты определённой страницы чужого процесса,к примеру: поменять на PAGE_EXECUTE_READWRITE ,но теперь я совсем запутался...

Savant
21-07-2005, 22:18
DillerInc
Не могу не спросить - каковы причины?
Может есть другое решение?

Кстати, нужные адреса можно и через VirtualQueryEx() получить. Ща попробуем.... =)

DillerInc
21-07-2005, 23:08
Savant
Не могу не спросить - каковы причины?
...т.е. всей моей затеи что-ли ?
Мне необходимо поймать на крючок вызов функции LoadLibraryA из библиотеки Kernel32.dll в чужом процессе, иными словами,иметь возможность поместить в нужное место в нужное время два байта ( EB FE ), если я вообще правильно понял всю эту катавасию.
нужные адреса можно и через VirtualQueryEx() получить
...типа так:

var
memInfo : _MEMORY_BASIC_INFORMATION
address : Pointer;
begin
VirtualQueryEx(processInfo.hProcess, address, memInfo, SizeOf(memInfo));
end;

...только что мы поставим в качестве переменной address - ведь там тоже "Pointer to the base address..." ?

Savant
21-07-2005, 23:20
Когда мне надо посмотреть или проверить как работает та или иная функция, то обычно я пишу небольшую программку, которая эту самую функцию активно использует
Итак, представленная ниже программа (если я все правильно сделал) должна:
1) найти все запущенные процессы
2) в области виртуальной памяти каждого процесса найти используемые им части (регионы/блоки) и вывести по ним информацию
Если выводить в файл, то он получается размером в 2,37 мб :)))

program vpe;

{$APPTYPE CONSOLE}

uses
Windows, PsAPI;

var
hProcess: Cardinal;
Buf: Memory_Basic_Information;
tmp: array[0..200] of Cardinal; // буыер под 201 процесс =))
i, k, j: Cardinal;
// si: System_Info;

label
l;

function Int2Hex( Value : DWord; Digits : Integer ): ShortString;
{ this function is copyrighted by KSer}
asm
MOV [ECX], DL
XADD EDX, ECX
@@loop1:
PUSH EAX
db $24, $0F // and al,$0F
AAM
//AAD
DB $D5, $11
db $04, $30 // add al,$30
MOV [EDX], AL
POP EAX
SHR EAX, 4
DEC EDX
LOOP @@loop1
end;

function GetAProtect(const i: Cardinal): String;
var
temp: Cardinal;
const
PAGE_WRITECOMBINE = $400;
begin
Result := '';

if i = 0 then begin
Result := 'unknown (0)'; Exit; end;

temp := i and $000000FF;
case temp of
PAGE_NOACCESS: Result := 'PAGE_NOACCESS';
PAGE_READONLY: Result := 'PAGE_READONLY';
PAGE_READWRITE: Result := 'PAGE_READWRITE';
PAGE_WRITECOPY: Result := 'PAGE_WRITECOPY';
PAGE_EXECUTE: Result := 'PAGE_EXECUTE';
PAGE_EXECUTE_READ: Result := 'PAGE_EXECUTE_READ';
PAGE_EXECUTE_READWRITE: Result := 'PAGE_EXECUTE_READWRITE';
PAGE_EXECUTE_WRITECOPY: Result := 'PAGE_EXECUTE_WRITECOPY';
end;

if i and PAGE_GUARD <> 0 then
Result := Result + ' && PAGE_GUARD';
if i and PAGE_NOCACHE <> 0 then
Result := Result + ' && PAGE_NOCACHE';
if i and PAGE_WRITECOMBINE <> 0 then
Result := Result + ' && PAGE_WRITECOMBINE';

if Result = '' then Result := '!!! Error';
end;

function GetState(const i: Cardinal): String;
begin
case i of
MEM_COMMIT: Result := 'MEM_COMMIT';
MEM_FREE: Result := 'MEM_FREE';
MEM_RESERVE: Result := 'MEM_RESERVE';
else Result := '!!! Error';
end;
end;

function GetType(const i: Integer): String;
begin
case i of
0: Result := 'unknown (0)';
MEM_IMAGE: Result := 'MEM_IMAGE';
MEM_MAPPED: Result := 'MEM_MAPPED';
MEM_PRIVATE: Result := 'MEM_PRIVATE';
else Result := '!!! Error';
end;
end;

begin
// Через GetSystemInfo можно узнать размер страницы в памяти и так как
// Вам нужно добраться до определенной страницы, то ее адрес вычисляется
// просто: Base_Address + si.dwPageSize * PageNumber_In_Allocation_Region
// GetSystemInfo(si);
if not EnumProcesses(@tmp, sizeof(tmp), i) then Exit;
for k := 0 to (i div 4) - 1 do begin
WriteLn(#10#13'* * * * * // PID = ', tmp[k]);
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, True, tmp[k]);
j := 0;
l: if VirtualQueryEx(hProcess, Ptr(j), Buf, sizeof(Buf)) > 0 then begin
WriteLn('Base Address: 0x' + Int2Hex(Cardinal(Buf.BaseAddress), 8) + ' (', Cardinal(Buf.BaseAddress), ')');
WriteLn('Allocation Base: 0x' + Int2Hex(Cardinal(Buf.AllocationBase), 8) + ' (', Cardinal(Buf.AllocationBase), ')');
WriteLn('Allocation Protect: ', GetAProtect(Buf.AllocationProtect));
WriteLn('Region Size: 0x' + Int2Hex(Buf.RegionSize, 8) + ' (', Buf.RegionSize, ')');
WriteLn('State: ', GetState(Buf.State));
WriteLn('Protect: ', Buf.Protect);
WriteLn('Type: ', GetType(Buf.Type_9));
if (Cardinal(Buf.BaseAddress) + Buf.RegionSize < $FFFFFFFF) and (Buf.RegionSize > 0) then begin
WriteLn;
j := Cardinal(Buf.BaseAddress) + Buf.RegionSize;
goto l; // loop
end;
end;
end;

ReadLn;
end.


Эх, млин чуть-чуть не успел, вот и пост DillerInc'a появился... :(

Мне необходимо поймать на крючок вызов функции LoadLibrary
Это уже хуки. Если не ошибаюсь, то подробности в гугле по Hooking Windows API .

EB FE
хех. цикл. бесконечный. очень любимый всякими патчами/кряками ("замораживание" процесса). н-да...

Savant
21-07-2005, 23:24
Вот на Делфи - http://forum.sources.ru/index.php?showtopic=43286&hl=hook

Добавлено:
В этой статье (http://wasm.ru/article.php?article=tbc14) (ближе к концу) тоже есть полезности (на асме).

Перехват APi функций в WinNT+ (http://www.rsdn.ru/article/baseserv/IntercetionAPI.xml)

Добавлено:
Статьи о перехвате вызовов API, используя Delphi:
Перехват API функций в Windows NT (часть 1). Основы перехвата. (http://www.wasm.ru/article.php?article=apihook_1)
Перехват API функций в Windows NT (часть 2). Методы внедрения кода. (http://www.wasm.ru/article.php?article=apihook_2)
Перехват API функций в Windows NT (часть 3). Нулевое кольцо. (http://www.wasm.ru/article.php?article=apihook_3)

DillerInc
22-07-2005, 00:14
Savant
Спасибо за ссылки - будем разбираться.
Если что, буду тогда сюда постить,если ты не против.
очень любимый всякими патчами/кряками
...а кто ж его не любит - экспериментируем,экспериментируем... ;) .

DillerInc
23-07-2005, 21:31
Вообще,как то странно это...к примеру:

var
EntryPoint : LPDWORD;
Temp_Protect : Cardinal;
begin
VirtualProtectEx(pInfo.hProcess, PPointer(EntryPoint), 2, PAGE_EXECUTE_READWRITE, Temp_Protect);
end;

...работает на ура,а этот вариант:

var
ProcAddress : LPDWORD;
Temp_Protect : Cardinal;
begin
ProcAddress := LPDWORD(GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryA'));
VirtualProtectEx(pInfo.hProcess, PPointer(ProcAddress), 2, PAGE_EXECUTE_READWRITE, Temp_Protect);
end;

...продолжает упорно возвращать STATUS_CONFLICTING_ADDRESSES, хотя если взять последнюю предоставленную ссылку,то там описывается как раз аналогичный вариант с внедрением своего кода в начало системной функции (только там сразу применяется функция ReadProcessMemory,что кстати странно, т.к. кто гарантирует,что у нас есть право записи в эту страницу памяти ).
Вопрос: почему первый вариант работает,а второй нет ?
Как можно поправить ситуацию ?

DillerInc
25-07-2005, 00:12
Нет,ну это просто проказа какая-то :angry: :
беру любое смещение из адресного простанства чужого процесса,вставляю его как второй параметр в функцию VirtualProtectEx - всё работает без каких-либо проблем!
Как только использую адрес начала системной функции - всё идёт крахом, из-за чего рушится и вся остальная конструкция.
Может быть проблема заключается именно в попытке обращения к импортируемой библиотеке,но в указанных примерах (по ссылкам) предлагали же непосредственное внедрение своих команд в начало системной функции :blink: ...

DillerInc
26-07-2005, 22:32
Ну,неужели ни у кого нету никаких соображений по этому поводу,никаких советов ?
Может причина "недосягаемости" адресного пространтсва импортируемых библиотек заключается где-то в атрибутах,свойствах открытия файла или создания процесса из последнего...
:help:

Savant
27-07-2005, 11:58
hasherfrog
Темы разбил...

DillerInc
Ну,неужели ни у кого нету никаких соображений по этому поводу,никаких советов ?
Конкретно я могу посоветовать прочитать еще три статьи, добавленные чуть выше. Там на Delphi, но довольно муторно расписано (по крайней мере я первую статью съел только со второго прочтения, вторая статья получше ушла, до третьей еще не дошел).
беру любое смещение из адресного простанства чужого процесса,вставляю его как второй параметр в функцию VirtualProtectEx - всё работает без каких-либо проблем!
Этого не может быть. Могу представить программу, которая это подтвердит.
... применяется функция ReadProcessMemory,что кстати странно, т.к. кто гарантирует,что у нас есть право записи в эту страницу памяти?
права задаются в OpenProcess()

Кстати говоря, а Вы не из под 9x/Me случайно работаете?! Там область применения VirtualProtectEx() ограничена 2 Гб, а область загрузки системных DLL как раз идет дальше (т.н. shared virtual address space).

p.s.: получать адрес из числа (Integer->Pointer) в Delphi лучше функцией ptr(), например ptr($4FFF).

DillerInc
27-07-2005, 15:53
Savant
Кстати говоря, а Вы не из под 9x/Me случайно работаете?!
...нет,Win2k SP4 .
Этого не может быть
...да,походу я действительно погорячился.Я имел в виду смещения в секции кода - та же точка входа позволяет проводить данную экзекуцию,пробовал ещё над несколькими смещениями - без проблем :huh: .
права задаются в OpenProcess()
...а если чужой процесс создан следующим образом:

var
sInfo : TStartupInfo;
pInfo : TProcessInformation;
begin
sInfo.cb := SizeOf(sInfo.cb); // Обрубок структуры класса TStartupInfo

CreateProcess(OpenFileName.lpstrfile, NIL, NIL, NIL, False, CREATE_SUSPENDED, NIL, NIL, sInfo, pInfo);

...возможна ли тут какая-то заковырка ?

DillerInc
01-08-2005, 23:56
В общем,почитав всякого и наковырявшись с кодом "по полной программе",я к сожалению так конкретно и не понял почему возникает вышеописанная ошибка - ну,да бог с ним...
Во большинстве материалов,которые я читал,преподавалась следующая техника т.н. "сплайсинга" системных функций:
в адресном пространстве чужого процесса создаётся удалённый поток с помощью CreateRemoteThread,в который потом подгружается библиотека,ответственная за перехват системных функций (размещение своих команд в начале таких функций,перенаправление управления на свой код-перехватчик)...
Причём такой вариант трактуется именно как наиболее предпочтительный и эффективный.

Но теперь я здесь по иному вопросу.
Функция GetModuleHandle возвращает дескриптор указанного модуля,который,как я понял,является ни чем иным как базовым адрестом загрузки этого самого модуля.Проблема заключается походу в следующем:

The GetModuleHandle function retrieves a module handle for the specified module if the file has been mapped into the address space of the calling process.
...если опять-таки правильно понял,функция способна возвратить хэндлы только тех модулей,которые являются загруженными в адресное простанство вызывающего процесса.
А как можно получить такой дескриптор какого-нибудь модуля в чужом процессе ?

DillerInc
04-08-2005, 00:19
Я почти достиг того,чего я хотел,а именно:

var
sInfo : TStartupInfo;
pInfo : TProcessInformation;
dEvent : _DEBUG_EVENT;
Base : Cardinal;
dll_Name : array [0..MAX_PATH - 1] of Char;
begin
if CreateProcess(OpenFileName.lpstrFile, NIL, NIL, NIL, False,
DEBUG_ONLY_THIS_PROCESS, NIL, NIL, sInfo, pInfo) then
begin
repeat // начинаем бесконечный цикл ожидания отладочного события
WaitForDebugEvent(dEvent, INFINITE);
if dEvent.dwDebugEventCode = LOAD_DLL_DEBUG_EVENT then // если отладочное событие - загрузка библиотеки,то...
begin
PPointer(Base) := dEvent.LoadDll.lpBaseOfDll; // ...получаем базовый адрес загруженной библиотеки
if GetModuleFileNameEx(pInfo.hProcess, Base,
@dll_Name, SizeOf(dll_Name)) <> 0 then // Пытаемся получить имя библиотеки по её модулю...
begin
Break; // Если предыдущая функция удалась,то выходим из бесконечного цикла
end else MessageBox(Handle, 'Function failed', NIL, MB_OK);
DoExit; // Типа убиваем всё наповал :]
end;
ContinueDebugEvent(dEvent.dwProcessId, dEvent.dwThreadId, DBG_CONTINUE);
until False;

...недочёт в том,что функция GetModuleFileNameEx постоянно проваливается,возвращая нуль.
Соответственно,кто-нибудь может сказать в чём могут быть причины ?

Можно,конечно,пытаться использовать поле dEvent.LoadDll.lpImageName структуры _DEBUG_EVENT, которое,как я понял,должно содержать указатель на имя загруженной библиотеки,но там какие-то трудности,о которых как-то размыто пишут на MSDN...и сколько я не пробывал использовать эту возможность - ничего путного не выходило.Хотя это был бы наверное самый идеальный вариант получения имени загруженной библиотеки.

DillerInc
09-08-2005, 01:30
хм...неужели я задаю такие сложные вопросы,что никто не хочет на них отвечать... :huh:
Иными словами,либо я чего-то не допонимаю,либо библиотека psapi.dll, в которой объявлены такие функции как GetModuleFileNameEx, GetModuleBaseName, криво написана...
Тем не менее мне удалось создать работающий код(точнее взять его основу на MSDN),который будет динамически получать имя загружаемой в чужом процессе библиотеки,хотя,по-моему,всё это скорее напоминает какие-то танцы с бубном.

function GetFileNameFromHandle(hFile : Cardinal) : Boolean;
{ Параметр hFile мы получаем из dEvent.LoadDll.hFile (см. выше) - кстати,одно из значений,которое в отличии от остальных значений реально возвращается отладочным событием LOAD_DLL_DEBUG_EVENT }
const
wantedLibName = 'XXX.dll';
var
hFileMap : Cardinal;
pMem : Pointer;
FilePath : array [0..MAX_PATH - 1] of Char;
sFilePath : String;
sFileName : String;
begin
Result := False;

try
hFileMap := CreateFileMapping(hFile, NIL, PAGE_READONLY, 0, 0, NIL);
if hFileMap <> 0 then
begin
pMem := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);
if pMem <> NIL then
begin
if GetMappedFileName(GetCurrentProcess(), pMem,
@FilePath, SizeOf(FilePath)) <> 0 then // тут мы наконец-то получаем полный путь к нашему файлу
begin
sFilePath := StrPas(FilePath);
sFileName := ExtractFileName(sFilePath); // здесь мы убираем путь,оставляя только само имя файла...
if AnsiSameText(wantedLibName, sFileName) then // и сравниваем его с именем искомого файла
begin
Result := True;
end;
end else MessageBox(Handle, 'GetMappedFileName failed', NIL, MB_OK);
end else MessageBox(Handle, 'pMem = NIL', NIL, MB_OK);
end else MessageBox(Handle, 'hFileMap = 0', NIL, MB_OK);

finally
UnMapViewOfFile(pMem);
CloseHandle(hFileMap);
end; // try...finally

end; // GetFileNameFromHandle

...вот так вот :) .

DillerInc
21-04-2006, 16:53
В общем,подумал,что может кому-то будет интересно,каким образом можно всё-таки поймать вызов какой-нибудь API-функции,заменив первые команды последней банальным EB FE,то есть попросту зациклив эту функцию.
Случай относится к тому варианту,когда вы запускаете подопытный процесс в режиме CREATE_SUSPENDED.
Причина,из-за которой у меня в своё время не получалось поймать функцию,заключалась в следующем.
При подобной технике необходимо,чтобы процесс был разморожен и как бы начал физически выполняться,если так можно выразиться.
Достигается это с помощью предварительного зацикливания процесса на точке входа,а именно:

var
OpenDlgBox : TOpenFileName;
sInfo : TStartupInfo;
pInfo : TProcessInformation;
EntryPoint : Cardinal;
RestoreEP : WORD;

// Функция для зацикливания адреса,которая возвращает два оригинальных байта,заменённых нашим опкодом
function EndlessLoopRVA(RVA : DWORD) : WORD;
const
Code : WORD = $FEEB; // наш магический опкод
error : LPCTSTR = 'Произошла ошибка во время зацикливания RVA';
var
OriginalData : WORD;
Temp_Protect : Cardinal;
nBytesRead, nBytesWritten : Cardinal;
begin
try
VirtualProtectEx(pInfo.hProcess, PPointer(RVA), 2,
PAGE_EXECUTE_READWRITE, Temp_Protect);
ReadProcessMemory(pInfo.hProcess, PPointer(RVA),
@OriginalData, 2, nBytesRead);
WriteProcessMemory(pInfo.hProcess, PPointer(RVA), @Code,
SizeOf(WORD), nBytesWritten);
VirtualProtectEx(pInfo.hProcess, PPointer(RVA), 2,
Temp_Protect, Temp_Protect);

Result := OriginalData;

except MessageBox(Handle, error, NIL, MB_OK);
end; // try...except

end; // EndlessLoopRVA

// Процедура,которая восстанавливает оригинальные байты
procedure RestoreData(RVAs : DWORD; RestoreCode : WORD);
var
Trash : WORD; // это просто для размещения мусора
Temp_Protect : Cardinal;
nBytesRead, nBytesWritten : Cardinal;
begin
try
VirtualProtectEx(pInfo.hProcess, PPointer(RVAs), 2,
PAGE_EXECUTE_READWRITE, Temp_Protect);
ReadProcessMemory(pInfo.hProcess, PPointer(RVAs), @Trash,
2, nBytesRead);
WriteProcessMemory(pInfo.hProcess, PPointer(RVAs),
@RestoreCode, SizeOf(WORD), nBytesWritten);
VirtualProtectEx(pInfo.hProcess, PPointer(RVAs), 2,
Temp_Protect, Temp_Protect);

except MessageBox(Handle, 'Произошла ошибка при восстановлении кода',
NIL, MB_OK);
end; // try...except

end; // RestoreData

begin

if CreateProcess(OpenDlgBox.lpstrFile, nil, nil, nil, False, CREATE_SUSPENDED,
nil, nil, sInfo, pInfo) then
begin
RestoreEP := EndlessLoopRVA(EntryPoint); // Помещаем наш опкод на точку входа
ResumeThread(pInfo.hThread); // Размораживаем процесс
Sleep(300); // Даём ему немножко времени очухаться
SuspendThread(pInfo.hThread); // Снова замораживаем его
RestoreData(EntryPoint, RestoreEP); // И восстанавливаем оригинальные байты
end;
end;
После того как процесс,только набрав обороты,упрётся в наш опкод на точке входа мы будем в состоянии работать с API-функциями,отображёнными в адресное пространство подопытного процесса.

Под конец стоит заметить,что зацикливание библиотечных функций не будет работать на 9х-ой линейке Windows,т.к. если я не ошибаюсь,библиотеки там отображаются в некую общую область памяти,а не в каждый процесс отдельно.

QWERYTY
08-04-2012, 16:08
Вижу тема давнишняя, но наверняка сюда заходят.
Была аналогичная проблема с получением хендла длл в чужом процессе.
Честно говоря был удивлен не найдя простой функции для этого.
Пришлось делать через тулхелп32.
В юзес подключаем Tlhelp32 и перечисляем модули в конкретном процессе(или если не ошибаюсь во всех когда вместо PId передаётся 0):
{Code}
// HLibrary := GetModuleHandle(PWideChar('ИмяМодуля.Dll')); Вот так разумеется не проканывает
// HLibrary разумеется глобальная переменная

procedure TForm1.Button2Click(Sender: TObject);
var
SnapModule: CARDINAL;
ModuleEntry: ModuleEntry32;
NextModule: BOOL;
begin
HLibrary := 0;
SnapModule := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PId); // Не забываем предварительно получить пид
ModuleEntry.dwSize := SizeOf(ModuleEntry);
NextModule := Module32First(SnapModule, ModuleEntry);
while NextModule do
begin
if ModuleEntry.szModule = 'ИмяМодуля.Dll' then
begin
HLibrary := ModuleEntry.hModule;
Exit;
end;
NextModule := Module32Next(SnapModule, ModuleEntry);
end;
CloseHandle(SnapModule);
end;
{/Code}

Ну там можно поиграть структурой ModuleEntry под свои потребности. Там много интересных полей.

QWERYTY
11-04-2012, 13:06
DillerInc
"var
ProcAddress : LPDWORD;
Temp_Protect : Cardinal;
begin
ProcAddress := LPDWORD(GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryA'));"

Зачем ProcAddress какого то непонятного типа?
var
ProcAddress: POINTER;
begin
ProcAddress := GetProcAddress(GetModuleHandle('Kernel32.dll'), 'LoadLibraryA');"
Функция VirtualProtectEx судя из описания в моей студии требует lpAddress (System.Pointer), так надо понимать lp перед переменной означает что нам нужен LONG POINTER, т.е. указатель на функцию, а не указатель на параметр DWORD. Зачем там все эти выкрутасы?

Поймать то вызов легко. Создаётся удалённый поток в процессе "жертве", и поток загружает нашу длл в адресное пространство. А в длл разумеется код который выполняет перехват(затирает первые 5 байт перехватываемой функции и записывает адрес нашей функции), и в зависимости от задачи можно восстановить искомую функцию, вызвать её, и опять переписать 5 байт до следующего вызова.

А вот самому вызвать свою функцию из своей же длл(закинутой в сторонний процесс) не понятно пока как.
Я понимаю что моя длл теперь в виртуальном пространстве стороннего процесса.
Непонятно почему .modBaseAddr(как я понимаю адрес загрузки) совпадает с хендлом модуля.
Допустим можно вычислить адрес функции, но ведь это виртуальный адрес в том процессе, и как вызвать функцию по виртуальному адресу?




© OSzone.net 2001-2012