關於Delphi/BCB程式中GetWindowTextA/GetDlgItemTextA斷點為何失效的簡單分析 (7千字)

看雪資料發表於2001-12-28

VCL中標準的單行編輯控制元件TEdit和多行編輯控制元件TMemo在類樹中的派生關係如下:

TObject -> TPersistent -> TComponent -> TControl -> TWinControl -> TCustomEdit -> TEdit

TObject -> TPersistent -> TComponent -> TControl -> TWinControl -> TCustomEdit -> TCustomMemo -> TMemo

以TEdit為例,來看一下它是如何獲取控制元件中的字串的,把這個過程(也就是VCL內部處理訊息的過程)搞清楚了,就能夠知道該設什麼樣的斷點來監視這個獲得字串的過程,從而找到我們輸入的註冊碼在記憶體中的存放位置,進而可以透過BPM/BPR斷點來跟蹤對註冊碼的處理過程。

TEdit用來存放字串的屬性是Text,該屬性繼承自TControl。讀取該屬性的函式是私有函式GetText( )。
(以下程式碼來自VCL的原始碼stdctrls.pas、control.pas、system.pas、sysutils.pas)

//----------------------------------------------------------------------------------------------------
TWndMethod = procedure(var Message: TMessage) of object;

TControl = class(TComponent)
    private
        FWindowProc: TWndMethod;
        FText:      PChar;
        function    GetText: TCaption;
        procedure    SetText(const Value: TCaption);
        ......
    protected
        property  Text: TCaption read GetText write SetText;
        procedure WndProc(var Message: TMessage); virtual;
        ......
    public
        function  GetTextBuf(Buffer: PChar; BufSize: Integer): Integer;
        function  GetTextLen: Integer;
        procedure SetTextBuf(Buffer: PChar);
        function  Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
        property  WindowProc: TWndMethod read FWindowProc write FWindowProc;
        ......
end;       

//----------------------------------------------------------------------------------------------------

GetText( )函式及相關函式的定義如下。可見,TControl最終是以WM_GETTEXTLENGTH和WM_GETTEXT訊息為引數直接調WndProc( )的。

//----------------------------------------------------------------------------------------------------
function TControl.GetText: TCaption;
var
  Len: Integer;
begin
  Len := GetTextLen;
  SetString(Result, PChar(nil), Len);
  if Len <> 0 then GetTextBuf(Pointer(Result), Len + 1);
end;

function TControl.GetTextLen: Integer;
begin
  Result := Perform(WM_GETTEXTLENGTH, 0, 0);
end;

function TControl.GetTextBuf(Buffer: PChar; BufSize: Integer): Integer;
begin
  Result := Perform(WM_GETTEXT, BufSize, Longint(Buffer));
end;

procedure TControl.SetTextBuf(Buffer: PChar);
begin
  Perform(WM_SETTEXT, 0, Longint(Buffer));
  Perform(CM_TEXTCHANGED, 0, 0);
end;

procedure TControl.SetText(const Value: TCaption);
begin
  if GetText <> Value then SetTextBuf(PChar(Value));
end;
                 
function TControl.Perform(Msg: Cardinal; WParam, LParam: Longint): Longint;
var
  Message: TMessage;
begin
  Message.Msg := Msg;
  Message.WParam := WParam;
  Message.LParam := LParam;
  Message.Result := 0;
  if Self <> nil then WindowProc(Message);
  Result := Message.Result;
end;

constructor TControl.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FWindowProc := WndProc;
  ......
end;
//----------------------------------------------------------------------------------------------------

WndProc( )是個virtual函式。TEdit和TCustomEdit未過載這個函式,而TWinControl過載了這個函式,但它並未處理WM_GETTEXT訊息,而是直接把該訊息傳遞給了父類的函式TControl.WndProc( )。

//----------------------------------------------------------------------------------------------------
procedure TWinControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
  KeyState: TKeyboardState;
  WheelMsg: TCMMouseWheel;
begin
  ......
  inherited WndProc(Message);
end;

//----------------------------------------------------------------------------------------------------

TControl.WndProc( )直接用Dispatch( )來分發該訊息。

//----------------------------------------------------------------------------------------------------
procedure TControl.WndProc(var Message: TMessage);
var
  Form: TCustomForm;
begin
  ......
  Dispatch(Message);
end;

//----------------------------------------------------------------------------------------------------------

由於WM_GETTEXT在TEdit中沒有對應的專門的處理函式,所以Dispatch( )最終會呼叫TEdit.DefaultHandler( ),這個方法是從TCustomEdit繼承來的,如此層層向上往父類分發,直到TControl.DefaultHandler( )對WM_GETTEXT訊息進行了處理,它只是簡單地把TControl的私有變數FText複製到軟體提供的緩衝區中,所用到的函式只有StrLen( )、StrLCopy( )。

//----------------------------------------------------------------------------------------------------------
procedure TControl.DefaultHandler(var Message);
var
  P: PChar;
begin
  with TMessage(Message) do
    case Msg of
      WM_GETTEXT:
        begin
          if FText <> nil then P := FText else P := '';
          Result := StrLen(StrLCopy(PChar(LParam), P, WParam - 1));
        end;
      WM_GETTEXTLENGTH:
        if FText = nil then Result := 0 else Result := StrLen(FText);
      WM_SETTEXT:
        begin
          P := StrNew(PChar(LParam));
          StrDispose(FText);
          FText := P;
          SendDockNotification(Msg, WParam, LParam);
        end;
    end;
end;
//----------------------------------------------------------------------------------------------------------

看一下sysutils.pas中的StrLen( )、StrLCopy( )函式的實現,是用匯編寫的,沒有呼叫任何API函式。這就說明從進入TControl.GetText( )開始,一直到WM_GETTEXT成功返回為止,沒有呼叫過任何Win32 API函式。所以常用的GetDlgItemTextA、GetWindowTextA斷點不生效是當然的。

//----------------------------------------------------------------------------------------------------------
function StrLen(const Str: PChar): Cardinal; assembler;
asm
        MOV    EDX,EDI
        MOV    EDI,EAX
        MOV    ECX,0FFFFFFFFH
        XOR    AL,AL
        REPNE  SCASB
        MOV    EAX,0FFFFFFFEH
        SUB    EAX,ECX
        MOV    EDI,EDX
end;

function StrLCopy(Dest: PChar; const Source: PChar; MaxLen: Cardinal): PChar; assembler;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV    ESI,EAX
        MOV    EDI,EDX
        MOV    EBX,ECX
        XOR    AL,AL
        TEST    ECX,ECX
        JZ      @@1
        REPNE  SCASB
        JNE    @@1
        INC    ECX
@@1:    SUB    EBX,ECX
        MOV    EDI,ESI
        MOV    ESI,EDX
        MOV    EDX,EDI
        MOV    ECX,EBX
        SHR    ECX,2
        REP    MOVSD
        MOV    ECX,EBX
        AND    ECX,3
        REP    MOVSB
        STOSB
        MOV    EAX,EDX
        POP    EBX
        POP    ESI
        POP    EDI
end;
//-------------------------------------------------------------------------------------------------------

那麼如何才能在VCL將使用者輸入的字串複製到軟體的緩衝區中時使SoftICE彈出來呢?一種辦法是先在軟體的記憶體中搜尋StrLCopy( )這個函式的機器碼(即其Signature),找到之後在其入口處設個斷點即可。實際上,你也可以在TControl.DefaultHandler( )、TControl.GetText( )等函式的入口處設斷點,只要VCL在獲取TEdit.Text的過程中呼叫了該函式,當然呼叫的次數越少越好。

另外一種方法就是跟蹤FText何時被賦值,實際上就在上面的WM_SETTEXT處。

blowfish

相關文章