天天看點

關于跨程序使用回調函數的研究:以跨程序擷取Richedit中RTF流為例。建議先參考我上次寫的博文跨程序擷取Richedit中Text: 獲得QQ聊天輸入框中的内容

建議先參考我上次寫的博文跨程序擷取Richedit中Text:
關于跨程式使用回調函數的研究:以跨程式擷取Richedit中RTF流為例。建議先參考我上次寫的博文跨程式擷取Richedit中Text: 獲得QQ聊天輸入框中的内容
 獲得QQ聊天輸入框中的内容

    拿到這個問題,我習慣性地會從VCL核心開始分析。找到TRichEdit聲明的單元,分析TRichEdit儲存為RTF流的代碼。(分析VCL核心代碼友善了解Windows标準API的封裝和使用)     打開聲明TRichEdit的ComCtrls.pas單元。搜尋"TRichEditStrings"(儲存流使用TRichEdit.Lines.SaveToStream方法,TRichEditStrings為TRichEdit.Line的類型)    TRichEditStrings = class(TStrings)

  private

    RichEdit: TCustomRichEdit;

    FPlainText: Boolean;

    FConverter: TConversion;

    procedure EnableChange(const Value: Boolean);

  protected

    function Get(Index: Integer): string; override;

    function GetCount: Integer; override;

    procedure Put(Index: Integer; const S: string); override;

    procedure SetUpdateState(Updating: Boolean); override;

    procedure SetTextStr(const Value: string); override;

  public

    destructor Destroy; override;

    procedure Clear; override;

    procedure AddStrings(Strings: TStrings); override;

    procedure Delete(Index: Integer); override;

    procedure Insert(Index: Integer; const S: string); override;

    procedure LoadFromFile(const FileName: string); override;

    procedure LoadFromStream(Stream: TStream); override;

    procedure SaveToFile(const FileName: string); override;

    procedure SaveToStream(Stream: TStream); override;

    property PlainText: Boolean read FPlainText write FPlainText;

  end; 尋找到SaveToStream的方法  procedure TRichEditStrings.SaveToStream(Stream: TStream); var    EditStream: TEditStream; 

   TextType: Longint;    StreamInfo: TRichEditStreamInfo;    Converter: TConversion; begin    if FConverter <> nil then Converter := FConverter    else Converter := RichEdit.DefaultConverter.Create;    StreamInfo.Stream := Stream;    StreamInfo.Converter := Converter;    try      with EditStream do      begin        dwCookie := LongInt(Pointer(@StreamInfo));        pfnCallBack := @StreamSave;        dwError := 0;      end;      if PlainText then TextType := SF_TEXT      else TextType := SF_RTF;      SendMessage(RichEdit.Handle, EM_STREAMOUT, TextType, Longint(@EditStream));     if EditStream.dwError <> 0 then        raise EOutOfResources.Create(sRichEditSaveFail);    finally      if FConverter = nil then Converter.Free;    end; end; 

看關鍵的一句:“ SendMessage(RichEdit.Handle, EM_STREAMOUT, TextType, Longint(@EditStream));” 這下明白了,擷取RTF關鍵的是向Richedit發送EM_STREAMOUT消息。   關于EM_STREAMOUT消息想了解更多可以查閱MSDN:   EM_STREAMOUT      wParam = (WPARAM) (UINT)  uFormat ;      lParam = (LPARAM) (EDITSTREAM FAR *)  lpStream ; 程序間的記憶體位址是相對的。 A程序$00450000記憶體位址值為34,那麼B程序$00450000記憶體位址就不一定是34了。  在發送EM_STREAMOUT消息時,lParam參數表示的位址就是相對于目标程序的。  跨程序通路記憶體主要用到如下API函數:  GetWindowThreadProcessId -- 根據窗體句柄獲得其所在的線程、程序ID OpenProcess -- 打開程序并傳回通路句柄 VirtualAllocEx -- 配置設定程序虛拟記憶體空間,傳回所配置設定的記憶體位址。 VirtualFreeEx -- 釋放程序虛拟記憶體空間 ReadProcessMemory、WriteProcessMemory -- 讀寫程序記憶體資料。 和以往不一樣,這個消息用到了回調函數“ pfnCallBack := @StreamSave;” 函數也是存放在記憶體中的資料(一些機器指令),通路函數同樣會碰到程序間不能直接通路記憶體的問題。 也就是說:需要将函數資料寫入到目标程序中,才能被正常調用。  【如何獲得函數的資料?】

function MyFunction(A, B: Integer): Integer;

begin

  Result := A + B;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

  Edit1.Text := IntToStr(Integer(@MyFunction));

end; 函數位址容易得到。調試如上代碼,點選按鈕獲得函數位址,打開CPU檢視器(Ctrl+Alt+C),定位函數位址。 這樣将看到如圖: 

關于跨程式使用回調函數的研究:以跨程式擷取Richedit中RTF流為例。建議先參考我上次寫的博文跨程式擷取Richedit中Text: 獲得QQ聊天輸入框中的内容

 03C2 --------add eax,edx

7105 --------jon +$05

E81f43FBFF --call @IntOver

C3 --------- ret 前面就是十六進制資料,後面就是該資料表示的機器指令。 這些就是函數資料,将它寫入到目标程序就可以調用了!!!  兩個程序載入相同的DLL那麼DLL的函數位址則是相同的,也就是說API函數SendMessage在A、B兩個程序的位址一緻。有了這點,利用系統API函數SendMessage發送WM_COPYDATA消息就可以互動資料了。  當然,如果指令裡有相對位址的通路也得克隆才成,比如上面的" E81f43FBFF --call @IntOver "就用到了相對位址。囧  可以将編譯條件中“溢出、範圍、IO"檢查都關掉,減少相對位址的通路。 跨程序之前,先在本程序實驗通過再說:  【第一個實驗:正常調用回調函數】  function MySendMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

begin

  Result := SendMessage(hWnd, Msg, wParam, lParam);

end;

type

  TMySendMessage = function (hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;

var vMySendMessage: TMySendMessage = MySendMessage;

function EditStreamCallBack(dwCookie: Longint; pbBuff: PByte;

  cb: Longint; var pcb: Longint): Longint; stdcall;

var

  vCopyDataStruct: TCopyDataStruct;

begin

  pcb := cb;

  vCopyDataStruct.dwData := 0;

  vCopyDataStruct.cbData := cb;

  vCopyDataStruct.lpData := pbBuff;

  vMySendMessage(dwCookie, WM_COPYDATA, 0, Integer(@vCopyDataStruct));

  Result := ERROR_SUCCESS;

end;

procedure TForm1.Button3Click(Sender: TObject);

var

  vEditStream: TEditStream;

begin

  vEditStream.dwCookie := Handle;

  vEditStream.dwError := 0;

  vEditStream.pfnCallback := EditStreamCallBack;

  FMemoryStream := TMemoryStream.Create;

  SendMessage(RichEdit1.Handle, EM_STREAMOUT, SF_RTF, Integer(@vEditStream));

  FMemoryStream.Position := 0;

  Memo1.Lines.LoadFromStream(FMemoryStream);

  FMemoryStream.Free;

end;  “ EditStreamCallBack“就是要複制到目标程序的函數資料。 調試通過後,獲得其十六進制資料。  

{ 55          } PUSH EBP  { 8BEC        } MOV EBP,ESP

{ 83C4F4      } ADD ESP,$F4

{ 8B4510      } MOV EAX,DWORD PTR [EBP+$10]

{ 8B5514      } MOV EDX,DWORD PTR [EBP+$14]

{ 8902        } MOV DWORD PTR [EDX],EAX

{ 33D2        } XOR EDX,EDX

{ 8955F4      } MOV DWORD PTR [EBP-$0C],EDX

{ 8945F8      } MOV DWORD PTR [EBP-$08],EAX

{ 8B450C      } MOV EAX,DWORD PTR [EBP+$0C]

{ 8945FC      } MOV DWORD PTR [EBP-$04],EAX

{ 8D45F4      } LEA EAX,DWORD PTR [EBP-$0C]

{ 50          } PUSH EAX

{ 6A00        } PUSH $00

{ 6A4A        } PUSH $4A

{ 8B4508      } MOV EAX,DWORD PTR [EBP+$08]

{ 50          } PUSH EAX

{ FF15B88C4500} CALL DWORD PTR [$00458CB8]

{ 33C0        } XOR EAX,EAX

{ 8BE5        } MOV ESP,EBP

{ 5D          } POP EBP

{ C21000      } RET $0010 其中 { FF15B88C4500} CALL DWORD PTR [$00458CB8]裡的 [$00458CB8] 裡是相對位址,不同的調試環境會不一樣。 我們這裡 [$00458CB8]裡存放的就是系統API函數SendMessage的位址。 【第二個實驗:拼裝函數資料】  const

  EditStreamCallBackBytes =

#$55 + //                     PUSH EBP

#$8B#$EC + //                 MOV EBP,ESP

#$83#$C4#$F4 + //             ADD ESP,$F4

#$8B#$45#$10 + //             MOV EAX,DWORD PTR [EBP+$10]

#$8B#$55#$14 + //             MOV EDX,DWORD PTR [EBP+$14]

#$89#$02 + //                 MOV DWORD PTR [EDX],EAX

#$33#$D2 + //                 XOR EDX,EDX

#$89#$55#$F4 + //             MOV DWORD PTR [EBP-$0C],EDX

#$89#$45#$F8 + //             MOV DWORD PTR [EBP-$08],EAX

#$8B#$45#$0C + //             MOV EAX,DWORD PTR [EBP+$0C]

#$89#$45#$FC + //             MOV DWORD PTR [EBP-$04],EAX

#$8D#$45#$F4 + //             LEA EAX,DWORD PTR [EBP-$0C]

#$50 + //                     PUSH EAX

#$6A#$00 + //                 PUSH $00

#$6A#$4A + //                 PUSH $4A

#$8B#$45#$08 + //             MOV EAX,DWORD PTR [EBP+$08]

#$50 + //                     PUSH EAX

#$FF#$15#$00#$00#$00#$00 + // CALL DWORD PTR [H] -- String Index:43

#$33#$C0 + //                 XOR EAX,EAX

#$8B#$E5 + //                 MOV ESP,EBP

#$5D + //                     POP EBP

#$C2#$10#$00 + //             RET $0010

#$00#$00#$00#$00 + //         Api Address -- String Index:55

#$00#$00#$00#$00 + //         _editstream : dwCookie -- String Index:59

#$00#$00#$00#$00 + //         _editstream : dwError

#$00#$00#$00#$00; //          _editstream : pfnCallback

type

  TVclApi = packed record //JMP DWORD PTR [$HHHHHHHH]

    rJmp: Word; // FF 25

    rAddress: PInteger; // API實際位址

  end;

  PVclApi = ^TVclApi;

procedure TForm1.Button2Click(Sender: TObject);

type

  PEditStream = ^TEditStream;

var

  vEditStreamCallBack: string;

begin

  vEditStreamCallBack := EditStreamCallBackBytes;

  PInteger(@vEditStreamCallBack[43])^ := Integer(@vEditStreamCallBack[55]);

  PInteger(@vEditStreamCallBack[55])^ := PVclApi(@SendMessage)^.rAddress^;

  PEditStream(@vEditStreamCallBack[59])^.dwCookie := Handle;

  PEditStream(@vEditStreamCallBack[59])^.pfnCallback := @vEditStreamCallBack[1];

  FMemoryStream := TMemoryStream.Create;

  SendMessage(RichEdit1.Handle, EM_STREAMOUT, SF_RTF, Integer(@vEditStreamCallBack[59]));

  FMemoryStream.Position := 0;

  Memo1.Lines.LoadFromStream(FMemoryStream);

  FMemoryStream.Free;

end; VCL中調用API時使用JMP指令。 ” PVclApi(@SendMessage)^.rAddress^”就是獲得SendMessage實際位址。  好了,本程序的實驗做完後,我們就要拿另一個程序開刀了。 封裝一下,最終代碼如下:

uses RichEdit;

{$WARN SYMBOL_DEPRECATED OFF}

type
  TRichEditStreamReader = class
  private
    FStream: TStream;
    FHandle: THandle;
  protected
    procedure WndProc(var Message: TMessage); virtual;
  public
    constructor Create(AStream: TStream);
    destructor Destroy; override;
    property Handle: THandle read FHandle;
  end;

{ TRichEditStreamReader }

constructor TRichEditStreamReader.Create(AStream: TStream);
begin
  FStream := AStream;
  FHandle := AllocateHWnd(WndProc);
end;

destructor TRichEditStreamReader.Destroy;
begin
  DeallocateHWnd(FHandle);
  inherited;
end;

procedure TRichEditStreamReader.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_COPYDATA:
      begin
        if not Assigned(FStream) then Exit;
        FStream.Write(PCopyDataStruct(Message.LParam)^.lpData^,
          PCopyDataStruct(Message.LParam)^.cbData);
      end;
  end;
end;

function Process_ReadRichEditStream(
  AHandle: THandle; AStream: TStream; AFormat: Longword): Boolean;
type
  TVclApi = packed record //JMP DWORD PTR [$HHHHHHHH]
    rJmp: Word; // FF 25
    rAddress: PInteger; // API實際位址
  end;
  PVclApi = ^TVclApi;
const
  EditStreamCallBackBytes =
#$55 + //                     PUSH EBP
#$8B#$EC + //                 MOV EBP,ESP
#$83#$C4#$F4 + //             ADD ESP,$F4
#$8B#$45#$10 + //             MOV EAX,DWORD PTR [EBP+$10]
#$8B#$55#$14 + //             MOV EDX,DWORD PTR [EBP+$14]
#$89#$02 + //                 MOV DWORD PTR [EDX],EAX
#$33#$D2 + //                 XOR EDX,EDX
#$89#$55#$F4 + //             MOV DWORD PTR [EBP-$0C],EDX
#$89#$45#$F8 + //             MOV DWORD PTR [EBP-$08],EAX
#$8B#$45#$0C + //             MOV EAX,DWORD PTR [EBP+$0C]
#$89#$45#$FC + //             MOV DWORD PTR [EBP-$04],EAX
#$8D#$45#$F4 + //             LEA EAX,DWORD PTR [EBP-$0C]
#$50 + //                     PUSH EAX
#$6A#$00 + //                 PUSH $00
#$6A#$4A + //                 PUSH $4A
#$8B#$45#$08 + //             MOV EAX,DWORD PTR [EBP+$08]
#$50 + //                     PUSH EAX
#$FF#$15#$00#$00#$00#$00 + // CALL DWORD PTR [H] -- String Index:43
#$33#$C0 + //                 XOR EAX,EAX
#$8B#$E5 + //                 MOV ESP,EBP
#$5D + //                     POP EBP
#$C2#$10#$00 + //             RET $0010
#$00#$00#$00#$00 + //         Api Address -- String Index:55
#$00#$00#$00#$00 + //         _editstream : dwCookie -- String Index:59
#$00#$00#$00#$00 + //         _editstream : dwError
#$00#$00#$00#$00; //          _editstream : pfnCallback
type
  PEditStream = ^TEditStream;
var
  vEditStreamCallBack: string;
  vProcessId: DWORD;
  vProcess: THandle;
  vPointer: Pointer;
  vNumberOfBytesRead: Cardinal;
  vRichEditStreamReader: TRichEditStreamReader;
begin
  Result := False;
  if not Assigned(AStream) then Exit;
  if not IsWindow(AHandle) then Exit;
  GetWindowThreadProcessId(AHandle, @vProcessId);
  vProcess := OpenProcess(PROCESS_VM_OPERATION or PROCESS_VM_READ or
    PROCESS_VM_WRITE, False, vProcessId);
  try
    vPointer := VirtualAllocEx(vProcess, nil, 4096, MEM_RESERVE or MEM_COMMIT,
      PAGE_READWRITE);
    vRichEditStreamReader := TRichEditStreamReader.Create(AStream);
    try
      vEditStreamCallBack := EditStreamCallBackBytes;
      PInteger(@vEditStreamCallBack[43])^ := Integer(vPointer) + 55 - 1;
      PInteger(@vEditStreamCallBack[55])^ := PVclApi(@SendMessage)^.rAddress^;
      PEditStream(@vEditStreamCallBack[59])^.dwCookie := vRichEditStreamReader.Handle;
      PEditStream(@vEditStreamCallBack[59])^.pfnCallback := vPointer;
      WriteProcessMemory(vProcess, vPointer, @vEditStreamCallBack[1],
        Length(vEditStreamCallBack), vNumberOfBytesRead);
      SendMessage(AHandle, EM_STREAMOUT, AFormat, Integer(Integer(vPointer) + 59 - 1));
    finally
      vRichEditStreamReader.Free;
      VirtualFreeEx(vProcess, vPointer, 0, MEM_RELEASE);
    end;
  finally
    CloseHandle(vProcess);
  end;
end; { Process_ReadRichEditStream }

procedure TForm1.Button1Click(Sender: TObject);
var
  vHandle: THandle;
  vMemoryStream: TMemoryStream;
begin
  vHandle := FindWindow('WordPadClass', nil);
  if vHandle = 0 then Exit;
  vHandle := FindWindowEx(vHandle, 0, 'RICHEDIT50W', nil);
  if vHandle = 0 then Exit;
  vMemoryStream := TMemoryStream.Create;
  try
    Process_ReadRichEditStream(vHandle, vMemoryStream, SF_RTF);
    vMemoryStream.Position := 0;
    RichEdit1.PlainText := False;
    RichEdit1.Lines.LoadFromStream(vMemoryStream);
  finally
    vMemoryStream.Free;
  end;
end;      

繼續閱讀