加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

关于跨进程使用回调函数的研究。

(2008-07-14 17:56:34)
标签:

richedit

rtf

跨进程

其他进程

获取

it

分类: 代码集锦

  拿到这个问题,我习惯性地会从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),定位函数地址。 这样将看到如图:

 http://p.blog.csdn.net/images/p_blog_csdn_net/zswang/CPU%E6%B1%87%E7%BC%96.JPG

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;

0

阅读 收藏 喜欢 打印举报/Report
前一篇:IE的BUG
后一篇:蝉蜕
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有