Модераторы: Poseidon, Snowy, bems, MetalFan
  

Поиск:

Добавить материал
 

Как получить данные из TStringGrid в "чужом" процессе
Rrader
Репутация: 70
Всего: 191

Профиль
Быстрая цитата Цитата
Теги:
Внимание! Настоятельно рекомендую ознакомиться с материалом Получение данных из TStringGrid (D3-DXE), если Вы озадачены получением данных из этой таблицы smile 


На этом форуме и на многих других не раз возникал вопрос о том, как получить данные из компонента TStringGrid, созданного в "чужом" процессе. API-функция GetWindowText не дает нужного результата, поскольку текстовые данные таблицы хранятся в памяти обособленно от каких-либо оконных API. Более того, все, что мы можем получить о таблице в "чужом" процессе, это лишь её дескриптор (Handle). Задача подобного рода является достаточно сложной, и стандартного её решения не существует. Хочу предложить Вам одно нестандартное, но эффективное решение.

Задача о получении данных из TStringGrid прямым инжектом

[align=justify]Особенности метода:

1) Используется прямое внедрение в процесс (без DLL) с запуском удаленного потока. Обычно данная техника обнаруживается антивирусами (практика показывает, что для людей, которым нужно получить данные из TStringGrid, это не является проблемой). Если мы откажемся от CreateRemoteThread, то перед нами встанет еще одна проблема - мы не сможем напрямую получить доступ к необходимым данным (которые могли бы передать через параметр CreateRemoteThread). Данная проблема имеет эффективное решение, о котором говорится во второй части статьи.

2) Для обеспечения универсальности в некоторых местах мы не можем использовать код Delphi, только ассемблер. Это связано с тем, что, во-первых, я стремился сделать код как можно более компактным (его можно портировать на WinAPI с минимальными модификациями), а во-вторых, что самое главное, сделать его работоспособным для разных версий Delphi. Код (работоспособность) зависим от версии Delphi, на котором скомпилировано целевое приложение.

Что это значит? Дело в том, что от версии к версии в Delphi вносятся модификации не только в компилятор и IDE, но и в VCL. Стандартные классы расширяются, появляются новые методы и свойства. Используя знания анатомии класса Delphi, можно получить доступ ко многим членам класса без ограничений, но все это возможно только через ассемблер.

Некоторые сведения об устройстве классов в Delphi

В ассемблерном представлении классы почти ничем не отличаются от записей (records). Отличия следующие - каждая такая запись вбирает в себя не только свои собственные поля, но и поля всех предков. Также добавляется поле с адресом таблицы VMT - оно всегда первое по счёту. Рассмотрим пример:

Код

type
  TMyClass = class(TObject)
  private
    FField1: Integer;
    FField2: Integer;
  end;
  { Наследник }
  TMyClassEx = class(TMyClass)
  public
    FField3: Integer;
  end;

Эквивалентная запись для класса TMyClassEx будет выглядеть следующим образом:
Код

  TMyClassEx = record
    VMT: Pointer;
    FField1: Integer;
    FField2: Integer;
    FField3: Integer;
    // В Delphi 2009 появилось поле FMonitor: Pointer; 
  end;

Для доступа к полям Вам будет необходим "указатель на класс". Загрузив его в регистр (скажем, EAX), можно организовать доступ к полям, учитывая порядок их объявления:
[EAX + 0] - первое поле (VMT)
[EAX + 4] - второе поле FField1
[EAX + 8] - третье поле FField2
[EAX + 12] - четвертое поле FField3

Здесь используются непосредственные значения при адресации (hard-coded values). При работе со встроенным ассемблером так делать не рекомендуется, но при работе с классами у нас нет другого выхода.

Как видите, секций private и public больше нет - они имеют только высокоуровневую реализацию. Это касается всех секций класса. 

Реализация метода получения данных

Давайте рассмотрим, как можно получить данные из удаленного процесса. У нас есть лишь только дескриптор целевой TStringGrid. Зная его, мы сможем определить PID процесса через функцию GetWindowThreadProcessId. Открыв процесс, мы сможем записать в него функцию (выделив память), и запустить ее на исполнение через создание удаленной нити (будем считать, что процесс разрешает нам выполнять все описанные действия). Нам также понадобятся дополнительные данные (см. ниже), которые можно передать через указатель в параметр функции. Следует учесть, что на момент создания нити данные для параметра уже должны быть подготовлены и размещены в чужом процессе (для этого используется функция VirtualAllocEx).

Что следует размещать в данных? Ну, во-первых, дескриптор TStringGrid, так как он у нас один известный и будет использоваться smile Далее - все адреса API-функций, которые будут использоваться в "чужом" процессе. Для многих API-функций адреса можно получить непосредственно в нашем процессе, потому что модули ntdll.dllkernel32.dll и user32.dll для user-mode приложений, в которых используется TStringGrid, загружаются по одинаковым адресам. Все строки, которые будут использоваться для вызовов API, также следует передавать удаленной функции (для удобства через массив символов, если это возможно). Последнее, что нужно передать - это данные, определяющие особенности работы функции. В нашем случае - это смещения 4-х свойств и одного метода StringGrid для получения данных в программах, написаных на разных версиях Delphi. Об этом см. ниже.

Как работает функция DataGrabber

Так называется удаленная функция, которая осуществляет всю работу по получению данных и их передаче. Получить данные - дело одно, а передать - совсем другое. Дело в том, что функция работает не в нашем процессе, но получает там строки, которые нужно передать в наш процесс. TStringGrid может быть очень большим, и для непрерывной передачи данных я выбрал такое средство IPC, как именованные каналы (Named Pipes).

Работает функция в несколько этапов:
  • Открывает уже созданный нами именованный канал;
  • Этап 1 (STAGE_1). Сначала посылаются свойства ColCount, RowCount, FixedCols, FixedRows для копирования внешнего вида "чужой" StringGrid. smile Как получить эти свойства, зная лишь дескриптор StringGrid? Нужно каким-то образом получить указатель на экземпляр класса "чужой" TStringGrid, а затем работать с ним на ассемблере. Выход есть, и он универсален. В VCL для любого потомка TWinControl вызывается следующая функция при создании:
    Код

    SetProp(FHandle, MakeIntAtom(WindowAtom), THandle(Self));

    Вот то, что касается WindowAtom:
    Код

    ...
      WindowAtomString := Format('Delphi%.8X', [GetCurrentProcessID]);
      WindowAtom := GlobalAddAtom(PChar(WindowAtomString));
    ...

    Получается, что мы можем получить Self, просто прочитав нужное нам свойство. ID "чужого" процесса получить очень легко. Это свойство в сформированном виде передается через данные.

    Здесь есть некоторые проблемы. Как я уже говорил, от Delphi к Delphi разработчики иногда меняют код VCL, и это, как Вы понимаете, влияет на то, какие смещения полей следует использовать в данном конкретном случае. Для примера я использовал смещения для версии Delphi 2009 (C++Builder 2009). Если Вам требуется другая версия - рассчитайте сами (можно посмотреть в дизассемблере или в окне CPU). Универсальности здесь особой ждать не следует - адреса могут "плавать" и меняться даже от режима компиляции. Это плата за быстроту метода. Универсальный метод лишен перечисленных выше недостатков, но работает намного медленнее прямого внедрения.
  • Этап 2 (STAGE_2). После посылки необходимых свойств мы посылаем текст в цикле, подобном этому:
    Код

    var
      I, J: Integer;
      S: String;
    ...
    for I := StringGrid.ColCount - 1 downto 0 do
      for J := StringGrid.RowCount - 1 downto 0 do
        S := StringGrid.Cells[I, J];
    ...
    // Отправка текста

    Здесь нас подстерегает очередная проблема - дело в том, что свойства классов - это не поля, и прямой доступ из ассемблера к ним невозможен. Решение состоит в ручном вызове "геттера" для данного свойства. Вот он:
    Код

    function TStringGrid.GetCells(ACol, ARow: Integer): string;
    var
      ssl: TStringSparseList;
    begin
      ssl := TStringSparseList(TSparseList(FData)[ARow]);
      if ssl = nil then Result := '' else Result := ssl[ACol];
    end;

    Допустим, мы знаем его адрес. По соглашению параметр ACol нужно передать в EDX, а параметр ARow - в ECX. EAX должен содержать указатель на экземпляр StringGrid - Self. Мы его уже получили ранее из оконного свойства. Результат будет помещен на стек, мы должны будем обязательно проинициализировать в nil место для результата. Это соглашение по получению результата от функций, возвращающих строки (помещаемых на стек).

    На самом деле мы не знаем адреса TStringGrid.GetCells - это private-метод. Но можно рассчитать этот адрес относительно адреса любого другого метода, если таковой известен. В примере я рассчитывал смещение относительно деструктора - его адрес "зашит" в VMT по смещению vmtDestroy (-4). От версии к версии смещение может также меняться. Следует учесть, что деструктор у TStringGrid виртуальный, поэтому подобный приём применим.
  • При каждой итерации этапа 2 происходит отправка полученного текста в канал с информацией о том, какой ячейке принадлежит текст (по координатам - счетчикам цикла).
  • Когда весь текст отправлен, мы отправляем пакет с информацией о том, что канал можно закрывать, и закрываем его дескриптор через CloseHandle. Принимающая сторона обновляет наш StringGrid.
Проект находится в аттаче. На форме MainForm лежит поле ввода EdHandle для ввода дескриптора "чужой" StringGrid, кнопка BGetData, которая получит данные, заполнив ими нашу StringGrid1 smile 
Вот часть кода главного модуля:
</div>
Код

type
  { Дефиниции }
  TGetProp = function (hWnd: HWND; lpString: PWideChar): THandle; stdcall;
  TCreateFile = function (lpFileName: PWideChar; dwDesiredAccess,
    dwShareMode: DWORD; lpSecurityAttributes: PSecurityAttributes;
    dwCreationDisposition, dwFlagsAndAttributes: DWORD;
    hTemplateFile: THandle): THandle; stdcall;
  TWriteFile = function (hFile: THandle; const Buffer;
    nNumberOfBytesToWrite: DWORD; var lpNumberOfBytesWritten: DWORD;
    lpOverlapped: POverlapped): BOOL; stdcall;
  TRtlZeroMemory = procedure (Destination: Pointer; Length: DWORD); stdcall;
  TCopyString = function (lpString1, lpString2: PWideChar;
    iMaxLength: Integer): PWideChar; stdcall;
  TCloseHandle = function (hObject: THandle): BOOL; stdcall;

  { Удаленные данные }
  PInjectData = ^TInjectData;
  TInjectData = packed record
    { Закрытие объекта }
    FCloseHandle: TCloseHandle;
    { Работа с памятью на API }
    FZeroMemory: TRtlZeroMemory;
    { Копирование строки }
    FCopyString: TCopyString;
    { Получение свойства окна }
    FGetProp: TGetProp;
    { Для подключения к каналу }
    FCreateFile: TCreateFile;
    { Для записи в канал }
    FWriteFile: TWriteFile;
    { Окно TStringGrid в "чужом" процессе }
    FGridWnd: HWND;
    { Нужное свойство окна }
    FProperty: array[0..14] of WideChar;
    { Имя канала - PIPE_NAME }
    FPipeName: array[0..23] of WideChar;
    { Смещения }
    FColOffset: Integer;
    FRowOffset: Integer;
    FFixedRowOffset: Integer;
    FFixedColOffset: Integer;
    FGetCellsOffset: Integer;
  end;

const
   { Максимальная длина строки }
   BUF_SIZE = 1024;

type
  { Для Pipe }
  PPipeDataStruct = ^TPipeDataStruct;
  TPipeDataStruct = packed record
    { Текущий этап }
    FStage: Byte;
    { Флаг конца передачи данных }
    FTerminate: Boolean;
    { Число фиксированных колонок }
    FFixedCols: Integer;
    { Число фиксированных столбцов }
    FFixedRows: Integer;
    { Число колонок }
    FColCount: Integer;
    { Число рядов }
    FRowCount: Integer;
    { Текущая колонка }
    FColPos: Integer;
    { Текущий ряд }
    FRowPos: Integer;
    { Данные }
    FString: array [1..BUF_SIZE] of WideChar;
  end;

  TPipeThread = class(TThread)
  private
    { Переданные данные }
    FColCount, FRowCount: Integer;
    FFixedCols, FFixedRows: Integer;
    { Ячейки }
    FCells: array of array of WideString;
    FPipe: THandle;
    Data: TPipeDataStruct;
  protected
    procedure Execute; override;
  public
    { Обновление TStringGrid - после приема всех данных }
    procedure Update;
  end; 
    
const
  InjectSize = SizeOf(TInjectData);
  PipeDataSize = SizeOf(TPipeDataStruct);  
  { Этапы передачи данных }
  STAGE_1 = $00000001; // Передача числа строк и столбцов
  STAGE_2 = $00000002; // Передача текстовых данных 
  { Имя канала в формате UNC }
  PIPE_NAME = '\\.\PIPE\StringGridPipe';

var
  MainForm: TMainForm;
  { Для определения конца инициализации }
  FEvent: THandle = 0;
  
implementation

{$R *.dfm}

{ Функция, которая будет передавать данные из "чужого" процесса }
{ На входе получает локально заполненные данные }
procedure DataGrabber(Data: PInjectData); stdcall;
var
  SendData: TPipeDataStruct;
  BytesWritten: DWORD;
  { Адрес TStringGrid.GetCells(EDX, ECX); }
  MethodAddress: Pointer;
  { GetCells возвращет результат на стек в эту переменную }
  S: PWideChar;
  { StringGrid Self }
  Prop: DWORD;
asm
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        PUSH    0                                   { hTemplateFile }
        PUSH    0                                   { dwFlagsAndAttributes }
        PUSH    OPEN_EXISTING                       { dwCreationDisposition }
        PUSH    0                                   { lpSecurityAttributes }
        MOV     EDX, FILE_SHARE_READ
        OR      EDX, FILE_SHARE_WRITE
        PUSH    EDX                                 { dwShareMode }
        PUSH    GENERIC_WRITE                       { dwDesiredAccess }
        MOV     EBX, [Data] 
        LEA     EDX, (TInjectData)[EBX].FPipeName
        PUSH    EDX                                 { lpFileName }
        CALL    (TInjectData)[EBX].FCreateFile      { CreateFile }
        CMP     EAX, INVALID_HANDLE_VALUE           { EAX - Result }
        JZ      @@Quit
        MOV     ESI, EAX                            { ESI <= Pipe Handle }
        PUSH    0                                   { lpOverlapped }
        MOV     BytesWritten, 0
        LEA     EDX, BytesWritten
        PUSH    EDX                                 { lpNumberOfBytesWritten }
        MOV     EDX, PipeDataSize
        PUSH    EDX                                 { nNumberOfBytesToWrite }
        { Подготовка данных - этап 1 }
        MOV     SendData.FTerminate, False
        MOV     SendData.FStage, STAGE_1
        { Получение экземпляра класса TStringGrid }
        LEA     EDX, (TInjectData)[EBX].FProperty
        PUSH    EDX                                 { lpString }
        PUSH    (TInjectData)[EBX].FGridWnd         { hWnd }
        CALL    (TInjectData)[EBX].FGetProp         { GetProp }
        MOV     ECX, EAX
        MOV     Prop, ECX
        { Получение необходимых свойств TStringGrid }
        MOV     EDX, (TInjectData)[EBX].FColOffset
        MOV     EAX, [EAX + EDX]
        MOV     SendData.FColCount, EAX
        MOV     EDX, (TInjectData)[EBX].FRowOffset
        MOV     EAX, [ECX + EDX]
        MOV     SendData.FRowCount, EAX
        MOV     EDX, (TInjectData)[EBX].FFixedColOffset
        MOV     EAX, [ECX + EDX]
        MOV     SendData.FFixedCols, EAX
        MOV     EDX, (TInjectData)[EBX].FFixedRowOffset
        MOV     EAX, [ECX + EDX]
        MOV     SendData.FFixedRows, EAX
        { Расчёт адреса GetCells (ECX = Self) }
        MOV     ECX, [ECX]                          { ECX <= VMT }
        MOV     ECX, [ECX - 4]
        ADD     ECX, (TInjectData)[EBX].FGetCellsOffset
        MOV     MethodAddress, ECX                  { MethodAddress = GetCells }
        LEA     EDX, SendData
        PUSH    EDX                                 { Buffer }
        PUSH    ESI                                 { hFile }
        CALL    (TInjectData)[EBX].FWriteFile       { WriteFile }
        { Пересылка текста - этап 2 }
        MOV     SendData.FStage, STAGE_2
        { Установка счетчиков EDI и EBX }
        MOV     EDI, SendData.FColCount
        DEC     EDI
        { Цикл 1 }
@@Cycle1:
        { Восстанавливаем второй счётчик }
        MOV     EBX, SendData.FRowCount
        DEC     EBX
        { Цикл 2 - получение и отправка данных }
@@Cycle2:
        { Очищаем поле FString }
        PUSH    BUF_SIZE                            { Length }
        MOV     ECX, [Data]
        LEA     EAX, SendData.FString               { Buffer }
        PUSH    EAX
        CALL    (TInjectData)[ECX].FZeroMemory      { RtlZeroMemory }
        { Заполняем позицию текущей ячейки } 
        MOV     SendData.FColPos, EDI
        MOV     SendData.FRowPos, EBX
        { Получаем текст }
        MOV     EDX, EDI                            { ACol }
        MOV     ECX, EBX                            { ARow }
        { Инициализация обязательна }
        MOV     S, 0                                { S := nil }
        LEA     EAX, S
        PUSH    EAX                                 { Push @S }
        MOV     EAX, Prop                           { EAX <= Self }
        CALL    MethodAddress                       { GetCells }
        { Копируем текст }
        MOV     EAX, [Data]
        PUSH    BUF_SIZE                            { iMaxSize }
        PUSH    S                                   { Source }  
        LEA     EDX, SendData.FString
        PUSH    EDX                                 { Destination }
        CALL    (TInjectData)[EAX].FCopyString      { lstrcpyn }
        { Отправляем текст }
        MOV     EAX, [Data]
        PUSH    0                                   { lpOverlapped }
        LEA     EDX, BytesWritten
        PUSH    EDX                                 { lpNumberOfBytesWritten }
        MOV     EDX, PipeDataSize
        PUSH    EDX                                 { nNumberOfBytesToWrite }
        LEA     EDX, SendData
        PUSH    EDX                                 { Buffer }
        PUSH    ESI                                 { hFile }
        CALL    (TInjectData)[EAX].FWriteFile       { WriteFile }  
        DEC     EBX
        JNS     @@Cycle2
        DEC     EDI
        JNS     @@Cycle1
        { Сообщаем о завершении работы }
        MOV     SendData.FTerminate, True
        PUSH    0                                   { lpOverlapped }
        MOV     BytesWritten, 0
        LEA     EDX, BytesWritten
        PUSH    EDX                                 { lpNumberOfBytesWritten }
        MOV     EDX, PipeDataSize
        PUSH    EDX                                 { nNumberOfBytesToWrite }
        LEA     EDX, SendData
        PUSH    EDX                                 { Buffer }
        PUSH    ESI                                 { hFile }
        MOV     EBX, [Data]
        CALL    (TInjectData)[EBX].FWriteFile       { WriteFile }  
        { Закрываем канал }
        PUSH    ESI                                 { hObject }
        CALL    (TInjectData)[EBX].FCloseHandle     { CloseHandle }
        POP     EBX
        POP     EDI
        POP     ESI
@@Quit: { Exit }
end;

{ Получаем данные, заполняя ими нашу таблицу через Pipe }
procedure GetRemoteStringGridData(Wnd: HWND);
var
  FPID: DWORD;
  hUser32, hKernel32, hNtDll, hProcess, hThread: THandle;
  LocalData: TInjectData;
  RemoteData, RemoteDataGrabberFunc: Pointer;
  dwPageSize: DWORD;
  SystemInfo: TSystemInfo;
begin
  GetSystemInfo(SystemInfo);
  dwPageSize := SystemInfo.dwPageSize;
  hNtDll := GetModuleHandle('ntdll.dll');
  hUser32 := GetModuleHandle('user32.dll');
  hKernel32 := GetModuleHandle('kernel32.dll');
  if (hNtDll <> 0) and (hUser32 <> 0) and (hKernel32 <> 0) then
  begin
    GetWindowThreadProcessId(Wnd, @FPID);
    if FPID = 0 then
      Exit;
    hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION
      or PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ,
      False, FPID);
    if hProcess <> 0 then
    try
      RemoteData := VirtualAllocEx(hProcess, nil, dwPageSize,
        MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      if Assigned(RemoteData) then
      try
        { Рассчитываем адрес, по которому запишем функцию }
        RemoteDataGrabberFunc := RemoteData;
        Inc(PByte(RemoteDataGrabberFunc), InjectSize);
        { Записываем функцию }
        if WriteProcessMemory(hProcess, RemoteDataGrabberFunc,
          @DataGrabber, dwPageSize - InjectSize, PDWORD(nil)^) then
        begin
          { Заполняем локальные данные}
          with LocalData do
          begin
            @FGetProp := GetProcAddress(hUser32, 'GetPropW');
            @FCreateFile := GetProcAddress(hKernel32, 'CreateFileW');
            @FWriteFile :=  GetProcAddress(hKernel32, 'WriteFile');
            @FZeroMemory := GetProcAddress(hNtDll, 'RtlZeroMemory');
            @FCopyString := GetProcAddress(hKernel32, 'lstrcpynW');
            @FCloseHandle := GetProcAddress(hKernel32, 'CloseHandle');
            { StringGrid Handle }
            FGridWnd := Wnd;
            StringToWideChar(Format('Delphi%.8X', [FPID]), FProperty,
              SizeOf(FProperty));
            lstrcpyW(FPipeName, PIPE_NAME);
            { Смещения Delphi/C++ Builder 2009 }
            FColOffset := $274;
            FRowOffset := $29C;
            FFixedRowOffset := $28C;
            FFixedColOffset := $288;
            FGetCellsOffset := $484;
          end;
          { Копируем данные в "чужой" процесс }
          if WriteProcessMemory(hProcess, RemoteData, @LocalData,
            SizeOf(TInjectData), PDWORD(nil)^) then
          begin
            { Создание события в занятом состоянии }
            FEvent := CreateEvent(nil, True, False, nil);
            if FEvent <> 0 then
            try
              { Создание канала }
              with TPipeThread.Create(False) do
              begin
                FreeOnTerminate := True;
                { Ждем конца инициализации }
                WaitForSingleObject(FEvent, INFINITE);
              end;
            finally
              CloseHandle(FEvent);
            end;
            { Запуск удаленного кода на исполнение }
            hThread := CreateRemoteThread(hProcess, nil, 0,
              RemoteDataGrabberFunc, RemoteData, 0, PDWORD(nil)^);
            if hThread <> 0 then
            try
              WaitForSingleObject(hThread, INFINITE);
            finally
              CloseHandle(hThread);
            end;
          end;      
        end;
      finally
        VirtualFreeEx(hProcess, RemoteData, 0, MEM_RELEASE);
      end;
    finally
      CloseHandle(hProcess);
    end;
  end;
end;

end.


Комментарии посетителей:


Дата 3.9.2008, 18:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Qu1nt **   Репутация: 18  Всего: 50 
Кто тебя научил так программировать?! Браво.
PM MAIL   Вверх

  Дата 3.9.2008, 18:56 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
Rrader ***   Репутация: 70  Всего: 191 
Задача о сабклассинге "чужих" окон прямым инжектом


Оригинальный метод был предложен программистом по имени Robert Custer.

Сабклассинг - это контроль сообщений окон, реализуемый посредством смены оконной процедуры.

Следующий пример показывает, как можно осуществить удаленный сабклассинг без внедрения DLL. Пример будет менять оконную процедуру у родителя окна любого SysListView32 (TListView) так, чтобы элементы списка с нечетными номерами отображались красным цветом, а с четными - зеленым.

Сначала стоит разобраться, как это сделать в своем приложении на чистом API и не очень smile Чтобы изменить цвет элемента, нужно обрабатывать сообщение WM_NOTIFY, которое будет посылаться родителю SysListView32. 

Получив это сообщение, мы должны проанализировать параметр lParam, приведя его к типу TNMHdr (запись, содержащая данные сообщения). Если параметр code этой записи будет равен NM_CUSTOMDRAW, значит сейчас следует отрисовать элемент. Чтобы изменить цвет элемента, мы должны отправить значение CDRF_NOTIFYITEMDRAW как результат в ответ на WM_NOTIFY в том случае, когда член dwDrawStage записи PNMCustomDraw(PNMHdr(lParam))^ равен CDDS_PREPAINT. Затем мы снова получим WM_NOTIFY, и когда dwDrawStage будет равен CDDS_ITEMPREPAINT, то через член clrText записи PNMLVCustomDraw(PNMHdr(lParam))^ мы уже сможем изменить цвет на нужный. Индекс элемента передается в члене dwItemSpec записи PNMCustomDraw(PNMHdr(lParam))^.

Доступ к данным из оконной процедуры
Данные нам понадобятся, чтобы вызывать оригинальную оконную процедуру smile Способ доступа к ним заключается в следующем: когда мы записывали данные и новую оконную процедуру, мы записали их рядом. Т.е. сначала идут данные, а за ними сразу NewProc. В ней самой нам необходимо узнать свой адрес, а затем, отняв размер данных из этого адреса, мы получим адрес данных! smile Как получить этот адрес? Мы знаем, что адрес текущей инструкции находится в регистре EIP. Но ассемблер в Delphi не дает возможности его считывать. Его можно прочитать неявно, зная, как работает, скажем, команда call. При вызове данная команда заносит на стек адрес возврата, чтобы вернуться на следующую за call инструкцию, когда будет выполнена команда ret. Мы можем прочитать адрес возврата, вытолкнув его из стека командой pop в регистр (в примере это ECX). После этого у нас в регистре ECX будет находиться адрес текущей инструкции. Чтобы получить @NewProc, нам нужно из ECX вычесть определенное значение - размер команд между точкой входа в функцию и нашей командой pop. Эти команды - код входа (по созданию фрейма стека) и инициализации, генерируемый Delphi. Смещение можно посчитать на основе ассемблерного листинга функции. Далее мы вычитаем из ECX еще и размер данных - и получаем адрес данных. 

Привожу код главного модуля. Процедуры внедрения те же. Проект находится в аттаче. В примере меняется окно ListView у стандартного приложения "Сведения о системе" (через сабклассинг родителя). Будет затронут и TreeView, так как тот также использует схожую технику отрисовки. Пример проверен на работоспособность в Windows XP, Windows Vista, Windows 7.

Код

unit UMain;

{ Notice: If this code works, it was written by rrader.
  Else, I don't know who wrote it

  Web: http://www.wmsdk.com }

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, CommCtrl, StdCtrls, XPMan, ShellAPI, ComCtrls, ShlObj,
  ActiveX;

type
  { Дефиниции }
  TSetWindowLong = function (hWnd: HWND; nIndex: Integer;
    dwNewLong: Longint): Longint; stdcall;
  TCallWindowProc = function (lpPrevWndFunc: TFNWndProc; hWnd: HWND;
    Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

  { Удаленные данные }
  PInjectData = ^TInjectData;
  TInjectData = packed record
    fnSetWindowlong: TSetWindowLong;
    fnCallWindowProc: TCallWindowProc;
    hWnd: HWND;
    fnNewProc, fnOldProc: TFNWndProc;
  end; 

  TMainForm = class(TForm)
    BSubClass: TButton;
    BCancel: TButton;
    BShowWnd: TButton;
    ListView1: TListView;
    procedure BCancelClick(Sender: TObject);
    procedure BSubClassClick(Sender: TObject);
    procedure BShowWndClick(Sender: TObject);
  private
    { Private declarations }
    procedure OnWMNotify(var Msg: TMessage); message WM_NOTIFY;
  public
    { Public declarations }
    function InjectCode(Wnd: HWND): DWORD;
    function EjectCode(): DWORD;  
  end;

  function InjectFunc(Data: PInjectData): DWORD; stdcall;
  function EjectFunc(Data: PInjectData): DWORD; stdcall;

var
  MainForm: TMainForm;
  { PID процесса Explorer }
  FPID: DWORD;
  pDataRemote: Pointer;

const
  StructSize = SizeOf(TInjectData);

implementation

{$R *.dfm}

{ Удаленная оконная процедура }
function NewProc(hWnd: HWND; uMsg: UINT;
  wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
label
  CallOrig, Quit;
var
  pData: PInjectData;
begin
  asm
          { Сохранить EIP на стек }
          CALL    @@Dummy
  @@Dummy:
          { ECX <= EIP }
          POP     ECX
          { Смещение 0Ch можно рассчитать на основе ассемблерного листинга }
          { ECX <= @NewProc }
          SUB     ECX,   0Ch
          { pData <= @NewProc}
          MOV     pData, ECX
         { InjectData в "чужом" процессе находится
            непосредственно перед NewProc }
          SUB     pData, StructSize
  end;
  { Сабклассинг }
  case uMsg of
    WM_NOTIFY:
        asm
               { ECX <= NMHdr }
               MOV     ECX, lParam
               MOV     EDX, ECX
               { if .code = NM_CUSTOMDRAW then @@CustomDraw }
               CMP     (TNMHdr)[ECX].code, NM_CUSTOMDRAW
               { Отрисовка }
               JE      @@CustomDraw
               { Вызов оригинальной WndProc }
               JMP     CallOrig
        @@CustomDraw:
               { .dwDrawStage }
               MOV     ECX, (TNMCustomDraw)[ECX].dwDrawStage
               CMP     ECX, CDDS_PREPAINT
               JE      @@PrePaint
               CMP     ECX, CDDS_ITEMPREPAINT
               JE      @@ItemPrePaint
               JMP     CallOrig
        @@PrePaint:
               { Result }
               MOV     EAX, CDRF_NOTIFYITEMDRAW  
               POP     EBX
               POP     EBP
               RET     
        @@ItemPrePaint:
               { .dwItemSpec }
               MOV     ECX, (TNMCustomDraw)[EDX].dwItemSpec
               TEST    ECX, 1
               JZ      @@Even
               JMP     @@Odd
        @@Even:
               { .clrText }
               MOV     (TNMLVCustomDraw)[EDX].clrText, clGreen
               JMP     Quit
        @@Odd:
               { .clrText }
               MOV     (TNMLVCustomDraw)[EDX].clrText, clRed
               JMP     Quit
        end;
  end;
  CallOrig:
    { Оригинальная оконная процедура }
    Result := pData^.fnCallWindowProc(pData^.fnOldProc,
      hWnd, uMsg, wParam, lParam);
  Quit:
end;

function EjectFunc(Data: PInjectData): DWORD;
begin
  { Восстанавливаем оригинальную оконную процедуру }
  Result := DWORD(Data^.fnSetWindowLong(Data^.hWnd, GWL_WNDPROC,
    Longint(Data^.fnOldProc)) <> 0);
end;

function InjectFunc(Data: PInjectData): DWORD;
begin
  { Сабклассинг }
  Data^.fnOldProc := Pointer(Data^.fnSetWindowLong(Data^.hWnd,
    GWL_WNDPROC, Longint(Data^.fnNewProc)));
  Result := DWORD((Data^.fnOldProc <> nil));
end;

procedure TMainForm.BCancelClick(Sender: TObject);
begin
  EjectCode;
  InvalidateRect(0, nil, False);
end;

function TMainForm.EjectCode: DWORD;
var
  hProcess, hThread: THandle;
  pCodeRemote: Pointer;
  dwPageSize: DWORD;
  SystemInfo: TSystemInfo;
begin
  Result := 0;
  GetSystemInfo(SystemInfo);
  dwPageSize := SystemInfo.dwPageSize;
  if not Assigned(pDataRemote) then
    Exit;
  hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION or
    PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ,
    False, FPID);
  if hProcess <> 0 then
  try
    pCodeRemote := VirtualAllocEx(hProcess, nil, dwPageSize,
      MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if Assigned(pCodeRemote) then
    try
      if WriteProcessMemory(hProcess, pCodeRemote, @EjectFunc,
        dwPageSize, PDWORD(nil)^) then
      begin
        hThread := CreateRemoteThread(hProcess, nil, 0, pCodeRemote,
          pDataRemote, 0, PDWORD(nil)^);
        if hThread <> 0 then
        try
          WaitForSingleObject(hThread, INFINITE);
          GetExitCodeThread(hThread, Result);
        finally
          CloseHandle(hThread);
        end;
        if Assigned(pDataRemote) then
          VirtualFreeEx(hProcess, pDataRemote, 0, MEM_RELEASE);
      end;    
    finally
      VirtualFreeEx(hProcess, pCodeRemote, 0, MEM_RELEASE);
    end;
  finally
    CloseHandle(hProcess);
    pDataRemote := nil;
  end;
end;

function TMainForm.InjectCode(Wnd: HWND): DWORD;
var
  SystemInfo: TSystemInfo;
  hUser32, hProcess, hThread: THandle;
  LocalData: TInjectData;
  dwPageSize: DWORD;
  pNewProcRemote: Pointer;
  pCodeRemote: Pointer;
begin
  Result := 0;
  GetSystemInfo(SystemInfo);
  dwPageSize := SystemInfo.dwPageSize;
  hUser32 := GetModuleHandle('user32.dll');
  if hUser32 <> 0 then
  begin
    GetWindowThreadProcessId(Wnd, @FPID);
    if FPID = 0 then
      Exit;
    hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION
      or PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ,
      False, FPID);
    if hProcess <> 0 then
    try
      pDataRemote := VirtualAllocEx(hProcess, nil, dwPageSize,
        MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      if Assigned(pDataRemote) then
      try
        { Расчет адреса }
        pNewProcRemote := pDataRemote;
        Inc(PByte(pNewProcRemote), StructSize);
        { Записываем новую оконную процедуру в процесс }
        if WriteProcessMemory(hProcess, pNewProcRemote,
          @NewProc, dwPageSize - StructSize, PDWORD(nil)^) then
        begin  
          { Заполняем данные }
          with LocalData do
          begin
            @fnSetWindowLong := GetProcAddress(hUser32, 'SetWindowLongW');
            @fnCallWindowProc := GetProcAddress(hUser32, 'CallWindowProcW');
            hWnd := Wnd;
            fnNewProc := pNewProcRemote;
            fnOldProc := nil;
          end;
          { Записываем данные в "чужой" процесс }
          if WriteProcessMemory(hProcess, pDataRemote, @LocalData,
            SizeOf(TInjectData), PDWORD(nil)^) then
          begin
            pCodeRemote := VirtualAllocEx(hProcess, nil,
              dwPageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            if Assigned(pCodeRemote) then
            try
              { Записываем функцию, делающую инжект, в "чужой" процесс }
              if WriteProcessMemory(hProcess, pCodeRemote, @InjectFunc,
                dwPageSize, PDWORD(nil)^) then
              begin
                { Запускаем удаленную функцию InjectFunc }
                hThread := CreateRemoteThread(hProcess, nil, 0, pCodeRemote,
                  pDataRemote, 0, PDWORD(nil)^);
                if hThread <> 0 then
                try
                  WaitForSingleObject(hThread, INFINITE);
                  GetExitCodeThread(hThread, Result);
                finally
                  CloseHandle(hThread);
                end;
              end;
            finally
              VirtualFreeEx(hProcess, pCodeRemote, 0, MEM_RELEASE);
            end;
          end;  
        end;
      finally
        if Result = 0 then
          VirtualFreeEx(hProcess, pDataRemote, 0, MEM_RELEASE);
      end;
    finally
      CloseHandle(hProcess);
    end;
  end;
end;

{$DEFINE PUREPASCAL}
procedure TMainForm.OnWMNotify(var Msg: TMessage);
{$IFDEF PUREPASCAL}
begin
  { Работаем с нашим TListView }
  with Msg do
    if (PNMHdr(lParam)^.code = NM_CUSTOMDRAW) then
    with PNMCustomDraw(PNMHdr(lParam))^ do
    case dwDrawStage of
      CDDS_PREPAINT: Result := CDRF_NOTIFYITEMDRAW;
      CDDS_ITEMPREPAINT:
        begin
          with PNMLVCustomDraw(PNMHdr(lParam))^ do
          { Нечетный номер }
          if (PNMCustomDraw(PNMHdr(lParam))^.dwItemSpec and 1 <> 0) then
            clrText := clRed
          { Четный номер }  
          else clrText := clGreen;
        end;
    end
    else inherited;
end;
{$ELSE}
{ Ассемблерная реализация }
label
  CallOrig, Quit;
begin
  case Msg.Msg of
    WM_NOTIFY:
        asm
               MOV     EDX, Msg
               { ECX <= NMHdr }
               MOV     ECX, (TMessage)[EDX].lParam
               MOV     EDX, ECX
               { NM_CUSTOMDRAW }
               CMP     (TNMHdr)[ECX].code, NM_CUSTOMDRAW
               { Отрисовка }
               JZ      @@CustomDraw
               JMP     CallOrig
        @@CustomDraw:
               { .dwDrawStage }
               MOV     ECX, (TNMCustomDraw)[ECX].dwDrawStage
               CMP     ECX, CDDS_PREPAINT
               JZ      @@PrePaint
               CMP     ECX, CDDS_ITEMPREPAINT
               JZ      @@ItemPrePaint
               JMP     CallOrig
        @@PrePaint:
               { .Result }
               MOV     EDX, Msg
               MOV     (TMessage)[EDX].Result, CDRF_NOTIFYITEMDRAW 
               { Exit }
               JMP     Quit
        @@ItemPrePaint:
               { .dwItemSpec }
               MOV     ECX, (TNMCustomDraw)[EDX].dwItemSpec
               { Проверка на четность }
               TEST    ECX, 1
               { Четный номер }
               JZ      @@Even
               { Нечетный номер }
               JMP     @@Odd
        @@Even:
               { .clrText }
               MOV     (TNMLVCustomDraw)[EDX].clrText, clGreen
               JMP     Quit
        @@Odd:
               { .clrText }
               MOV     (TNMLVCustomDraw)[EDX].clrText, clRed
               JMP     Quit
        end;
  end;
  CallOrig: inherited;
  Quit: { NOP }
end;
{$ENDIF}

procedure TMainForm.BShowWndClick(Sender: TObject);

  { Путь к Program Common Files }
  function GetCommonFilesPath: String;
  const
    CSIDL_PROGRAM_FILES_COMMON = $002B;
  var
    ID: PItemIDList;
    Malloc: IMalloc;
  begin 
    if Succeeded(CoGetMalloc(1, Malloc)) then
      if Succeeded(SHGetSpecialFolderLocation(0,
        CSIDL_PROGRAM_FILES_COMMON, ID)) then
      try
        SetLength(Result, MAX_PATH);
        if SHGetPathFromIDList(ID, PChar(Result)) then
          Result := PChar(Result);
      finally
        Malloc.Free(ID);
      end; 
  end;

var
  Path: String;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation; 
begin
  FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  Path := GetCommonFilesPath;
  Path := Path + '\Microsoft Shared\MSInfo\msinfo32.exe';
  with StartupInfo do
    cb := SizeOf(TStartupInfo);
  if CreateProcess(PChar(Path), nil, nil, nil, False, NORMAL_PRIORITY_CLASS,
    nil, nil, StartupInfo, ProcessInfo) then
  with ProcessInfo do
  begin
    CloseHandle(hThread);
    CloseHandle(hProcess);
  end;
end;

function EnumChildProc(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  CName: array[0..Pred(MAX_PATH)] of Char;
  CNLen: Integer;
begin
  Result := True;
  CNLen := GetClassName(hWnd, CName, SizeOf(CName));
  if CNLen > 0 then
    if Copy(CName, 1, CNLen) = 'SysListView32' then
    begin
      Result := False;
      MainForm.InjectCode(GetParent(hWnd));
      InvalidateRect(0, nil, False);
    end;
end;

procedure TMainForm.BSubClassClick(Sender: TObject);
begin
  EnumChildWindows(FindWindow(nil, 'Сведения о системе'), @EnumChildProc, 0);
end;

end.

Надеюсь, данный материал поможет Вам в программировании!

Присоединённый файл ( Кол-во скачиваний: 252 )
Присоединённый файл  Attach.zip 11,40 Kb
PM MAIL   Вверх

Дата 3.9.2008, 19:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
THandle Group Icon   Репутация: 65  Всего: 372 
Rrader, наконец-то!!! Молодец!!! Как ты вообще все это понимаешь я не представляю. 


Цитата(Qu1nt @  3.9.2008,  19:54 Найти цитируемый пост)
Кто тебя научил так программировать?! Браво. 


Присоединяюсь.
PM WWW   Вверх

Дата 7.9.2008, 19:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
psychedeler   Репутация: нет  Всего: нет 
День добрый! Пытаюсь вникнуть в суть, т.к. озадачился проблемой "чужого" StringGrid'a...
Делаю на С++, поэтому с переходом от всего выше сказанного к С++ позникают проблемы.

1) PInjectData = ^TInjectData;
    TInjectData = packed record
Что это означает?
packed record, по-моему, сильно смахивает на С++ "struct", а ^ - это указатель (символ "*")
И по мне кажется, что фрагмент

Код

{ Удаленные данные }
  PInjectData = ^TInjectData;
  TInjectData = packed record
    { Закрытие объекта }
    FCloseHandle: TCloseHandle;
    { Работа с памятью на API }
    FZeroMemory: TRtlZeroMemory;
    { Копирование строки }
    FCopyString: TCopyString;
    { Получение свойства окна }
    FGetProp: TGetProp;
    { Для подключения к каналу }
    FCreateFile: TCreateFile;
    { Для записи в канал }
    FWriteFile: TWriteFile;
    { Окно TStringGrid в "чужом" процессе }
    FGridWnd: HWND;
    { Нужное свойство окна }
    FProperty: array[0..14] of AnsiChar;
    { Имя канала - PIPE_NAME }
    FPipeName: array[0..23] of AnsiChar;
    { Смещения }
    FColOffset: Integer;
    FRowOffset: Integer;
    FFixedRowOffset: Integer;
    FFixedColOffset: Integer;
    FGetCellsOffset: Integer;
  end;


на С++ выглядит как
Код

//
  struct TInjectData
  {
//
    TCloseHandle(FCloseHandle);
//
    TRtlZeroMemory(FZeroMemory);
//
    TCopyString(FCopyString);
//
    TGetProp(FGetProp);
//
    TCreateFile(FCreateFile);
//
    TWriteFile(FWriteFile);
//
    HWND FGridWnd;
//
    char FProperty[15];
//
    char FPipeName[24];
//    
    int FColOffset;
    int FRowOffset;
    int FFixedRowOffset;
    int FFixedColOffset;
    int FGetCellsOffset;
 };

TInjectData *PInjectData;


2)     LEA     EDX, (TInjectData)[EBX].FPipeName
И это что такое? Могу я получить какое-нибудь пояснение?

Перевод с Delphi на С++ дает ошибку синтаксиса при обработке 2-го случая.


Это сообщение отредактировал(а) Rrader - 8.5.2009, 12:41
PM MAIL   Вверх

Дата 8.9.2008, 09:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
ama_kid ***   Репутация: 15  Всего: 95 
Цитата(psychedeler @  7.9.2008,  20:59 Найти цитируемый пост)
на С++ выглядит как
на С++ это выглядит совсем не так, а примерно следующим образом:
Код
//
  struct _InjectData
  {
    TCloseHandle FCloseHandle;
    TRtlZeroMemory FZeroMemory;
    TCopyString FCopyString;
    TGetProp FGetProp;
    TCreateFile FCreateFile;
    TWriteFile FWriteFile;
    ...
 } TInjectData, *PInjectData;
Естесственно, перед этим надо определить типы TCloseHandle, TRtlZeroMemory и т.д...
Цитата(psychedeler @  7.9.2008,  20:59 Найти цитируемый пост)
И это что такое? Могу я получить какое-нибудь пояснение?
LEA - Load Effective Address, загрузка эффективного адреса строки в регистр EDX, если тебе это поможет. В данном случае это делается для того, чтобы передать эту строку параметром через стек в нижеследующий вызов 
Код
CALL    (TInjectData)[EBX].FCreateFile

Цитата(psychedeler @  7.9.2008,  20:59 Найти цитируемый пост)
Перевод с Delphi на С++ дает ошибку синтаксиса при обработке 2-го случая.
Привел бы ошибку хоть, которую он тебе выдает... Но есть подозрение, что ругается на приведение к типу (TInjectData), потому что в С++ это делается по другому, типо такого:
Код
LEA     EDX, PInjectData([EBX])->FPipeName
Ну это навскидку, возможно не совсем правильно, но главное - по другом, факт...

По сабжу: Rrader, молодец, интересная статья со стандартно нестандартным подходом smile
PM MAIL   Вверх

  Дата 8.9.2008, 14:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Rrader ***   Репутация: 70  Всего: 191 
Цитата(ama_kid @  8.9.2008,  16:47 Найти цитируемый пост)
Перевод с Delphi на С++ дает ошибку синтаксиса при обработке 2-го случая.

Код

CALL    [EBX].(TInjectData)FCreateFile

Код

// Дефиниции
typedef HANDLE (WINAPI *TGetProp)(HWND, LPCTSTR);
typedef HANDLE (WINAPI *TCreateFile)(LPCTSTR, DWORD, DWORD,
    LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);
typedef BOOL (WINAPI *TWriteFile)(HANDLE, LPCVOID, DWORD,
    LPDWORD, LPOVERLAPPED);
typedef void (WINAPI *TRtlZeroMemory)(PVOID, DWORD);
typedef BOOL (WINAPI *TCloseHandle)(HANDLE);
typedef LPTSTR (WINAPI *TCopyString)(LPTSTR, LPCTSTR, int);

// Удаленные данные
typedef struct _InjectData
{
    TCloseHandle FCloseHandle;
    TRtlZeroMemory FZeroMemory;
    TCopyString FCopyString;
    TGetProp FGetProp;
    TCreateFile FCreateFile;
    TWriteFile FWriteFile;
    HWND FGridWnd;
    char FProperty[15];
    char FPipeName[24];
    // Смещения
    int FColOffset;
    int FRowOffset;
    int FFixedRowOffset;
    int FFixedColOffset;
    int FGetCellsOffset;
} TInjectData, *PInjectData;

//  Максимальная длина строки
const int BUF_SIZE = 1024;
const int STAGE_1 = 1;
const int STAGE_2 = 2;

// Для Pipe
typedef struct _PipeDataStruct
{
    // Текущий этап
    unsigned char FStage;
    // Флаг конца передачи данных
    bool FTerminate;
    // Число фиксированных колонок
    int FFixedCols;
    // Число фиксированных столбцов
    int FFixedRows;
    // Число колонок
    int FColCount;
    // Число рядов }
    int FRowCount;
    // Текущая колонка
    int FColPos;
    // Текущий ряд
    int FRowPos;
    // Данные
    char FString[BUF_SIZE];
} TPipeDataStruct, *PPipeDataStruct;

const int PipeDataSize = sizeof(TPipeDataStruct);

PM MAIL   Вверх

Дата 11.6.2009, 10:40 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата
froloff   Репутация: нет  Всего: нет 
Браво Rrader, давно не видел столь изящно написанного кода.

Добавил поддержку C Builder 6.0. 

Код

{ CBuilder 6 offsets }
if MainForm.RbC6.Checked then
begin
  FRowOffset := $24C;
  FColOffset := $21C;
  FFixedRowOffset := $23C;
  FFixedColOffset := $238;
  FGetCellsOffset := $47C;
end;


И сохранение в файл (это думаю каждый сможет сделать на свой вкус).

Это сообщение отредактировал(а) Rrader - 19.6.2009, 17:38
PM MAIL   Вверх

  Дата 11.6.2009, 10:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Rrader ***   Репутация: 70  Всего: 191 
froloff, спасибо smile 

На самом деле у меня в принципе готов код, который получает эти данные без внедрения и без зависимости от компилятора Delphi (C++Builder). Т.е. эта статья - примитив, метод громоздок (но иногда оправдан для конкретных задач, потому что универсальный метод сложнее реализовать).

PM MAIL   Вверх

  Дата 27.6.2009, 17:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Rrader ***   Репутация: 70  Всего: 191 
PM MAIL   Вверх

Дата 10.6.2010, 13:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
hexware   Репутация: нет  Всего: нет 
ни у кого копии статьи не сохранилось? а то сайт wmsdk.com умер (
PM MAIL   Вверх

Дата 20.1.2011, 23:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
hhhhhhhhhhhh   Репутация: нет  Всего: нет 
Офигенный пример о сабклассинге чужих окон.
А нельзя ли куски асемблера переписать на WinApi?
Хотел подправить пример под свои нужды, но блин обломился... Не понятно в этом месте...
PM MAIL   Вверх

  Дата 21.1.2011, 11:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Rrader ***   Репутация: 70  Всего: 191 
hhhhhhhhhhhh, можно, правда, придется использовать библиотеку DLL, если охота полностью отказаться от asm-вставок и зашитых вручную значений.

http://wmsdk.com/2010/02/01/vcl-components...ering-part-one/ - тут используется сабклассинг окна в "чужом" процессе, причем без asm. Там много кода, не относящегося к сабклассингу, надо посмотреть и взять лишь нужное smile Показано, как работать со строками, как обрабатывать сообщения. Надо сказать, что Delphi почти всегда генерирует работоспособный код для использования в "чужом" процессе. Разумеется, есть потенциально опасные конструкции, например, блок case. Автоматические типы, над которыми покровительствует Delphi, также в группе риска.

P.S. Немного поправил код
PM MAIL   Вверх

Дата 2.2.2017, 12:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
_ls_   Репутация: нет  Всего: нет 
Подскажите, пожалуйста, смещения для дельфи 7. Вставил смещения от  C Builder 6.0. Число строк/столбцов определят верно, но вместо текста знаки вопросов в основном.
PM MAIL   Вверх

  
 
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1364 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.