• 关于跨进程使用回调函数的研究:以跨进程获取Richedit中RTF流为例(在Delphi 初始化每一个TWinControl 对象时,将会在窗体 的属性(PropData)中加入一些标志,DLL的HInstance的值与HOST 进程的HInstance并不一致)


    建议先参考我上次写的博文跨进程获取Richedit中Text:原创 获得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),定位函数地址。 这样将看到如图:
     
      
    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;
     
     
     
    http://blog.csdn.net/zswang/article/details/2645555
     
    ------------------------------------------------------------------------------------------------------------------------
    根据自己的猜测糊弄了一番,终于取到了正确的目标控件了。问题还是出在 ControlAtomString 上,基地址需要用目标控件所在的模块(即dll)实例句柄,即其中的HInstance要更改。

    下面一段是某个文章的摘抄:
    ------------------------------------------------------------
    分析RTL 源码发现,在Delphi 初始化每一个TWinControl 对象时,将会在窗体
    的属性(PropData)中加入一些标志,其中的一个标志是用于存放该对象的内存地址
    的。而FindControl 就是通过查看该属性来获取对象在内存中的起始地址。——即内
    存实例地址。
    这个“标志”,其固定形式为:
    ControlAtomString := Format('ControlOfs%.8X%.8X',
    [HInstance, GetCurrentThreadID]);
    由于在DLL 中,HInstance 的值与HOST 进程的HInstance 并不一致,所以,在
    DLL 中的ControlAtomString 也就与HOST 进程不一致。那么,通过B 标志去查A
    标志的属性,自然什么也得不到了。
    通过 Win32API GetWindowLong(),可以获得一个窗体所在的(真实的)实例句柄。
    这样,可以在DLL 中重新构造针对于任何一个窗体句柄的ControlAtomString。
    估计摘自这里:http://www.voidcn.com/blog/panpanxj/article/p-2753044.html
     
    http://bbs.csdn.net/topics/320268480
  • 相关阅读:
    第九周周记
    第七周周记
    第三次作业第一题
    第五周周记
    《世界是数字的》读后感想
    第十周周记
    迷茫
    测试作业
    价值观作业
    作业二 感想
  • 原文地址:https://www.cnblogs.com/findumars/p/6347997.html
Copyright © 2020-2023  润新知