天天看點

奇淫怪巧之在Delphi中調用不申明函數

   前一陣子,研究了一段時間的Win32Asm,研究到後來發現Win32的ASM實際上還是和C版的介紹的一樣。甚至還封裝了一個簡版的類似VCL庫結構架構的32ASM結構庫,不過搞着搞着就沒興趣了,也沒繼續往下深入,唉!發現年齡越來越大,人也越來越懶。

  休息了好長一陣子,在亂七八糟的東西亂弄一堆之後,總算發現了一個能有點用處的東西,于是就欣欣然跑來記錄一下日志部落格以為備份。

  我們都知道在Delphi,VC等這類靜态檢測形的語言,如果要使用一個函數,必須要先申明一下此函數結構,然後調用的時候,編譯器才會根據申明的函數結構進行編譯産生資料以做調用。比如MessageBox這類函數,都是在Windows中申明過了,調用的時候可以根據聲明來編輯代碼。對于使用Delphi這類使用靜态預先編譯檢查的來說,這種方法非常适用簡單,但是如果假設,俺們需要自己設計一個腳本語言,然後在這個腳本語言中,我們需要可以調用DLL中的函數,于是我們也給設計一個類似于Delphi的這種預先聲明的模式,然後腳本中調用,甚至,還可以不用預先申明,而隻用根據參數以及函數名稱來調用這個dll中的函數,那麼此時我們在腳本中寫的東西,我們在Delphi中就并沒有預先聲明的這個函數結構了。按照正常來調用自然不可行 ,如果說對應每個DLL中的函數給聲明一個函數出來外部,這個更是不可能,因為DLL是不可控的,我們并不知道使用者用腳本需要調用什麼DLL,以及調用什麼DLL中的函數。那麼此時,我們如何來調用這個DLL中的函數,讓腳本中的實作可用呢。我們先來分析一下,首先腳本調用肯定會要知道DLL名稱,然後會輸入函數名稱,然後就是函數參數了,變相來說,就是我們有DLL名可以獲得DllHandle,有 DLLHandle和函數名稱我們可以獲得函數位址CodeAddr。

就是說比如我們設計一個腳本,在腳本中可能輸入如下:

dll=DllObject('user32.dll');

dll.MessageBoxW(0,'asf','afd',64); 

那麼我們在Delphi中能獲得的就是MessageBoxW這個函數的函數位址以及 傳遞進入的參數,但是我們并不會聲明這個函數。我們現在的目的就是要在Delphi中把這個腳本給解析出來并正确調用MessageBoxW。也就如題說的通過未聲明函數的位址調用函數。

實際上任何語言中調用某一個函數,模式都是差不多,無非是傳參和調用,而傳參又有好多模式,比如Delphi的就有Register,Stdcall,Safecall,cdecl等,C也有這些調用模式,Delphi的Register模式對應C中的fastcall。正是這些傳參模式的不同而導緻了參數的入棧 方式不一樣,Register是用優先通過寄存器,然後才入棧,stdcall和cdecl等都是直接入棧,并且入棧順序都是參數從右向左入棧,唯一的差別是stdcall是不用自己管理棧函數在調用完成之後會自動清理堆棧空間,而cdecl需要調用者我們自己來清理堆棧空間。SafeCall是stdcall模式中加上了對于Com的異常處理等。我們一般Dll中的傳參模式都是stdcall和cdecl,是以,這裡就隻針對這兩個來進行處理。

那麼現在的任務就是将參數壓入堆棧,然後call一下函數位址就OK了。于是重要的任何就是參數壓棧 ,call位址這個是最簡單的。

現在就需要來分析一下參數壓棧,我們都知道在32位系統中,以4位元組為機關進行傳輸的,是以說基本上傳遞的參數都是4位元組模式,那麼我們byte,char,wchar,boolean等類型都需要變成4位元組來做傳輸,int64,double等占據8位元組,就需要變成2個4位元組進行壓棧。是以此時我們就可以設計兩個資料結構用來處理轉換這些類型

tva=record

      case Integer of

      0: (vi: Integer);

      1: (vb: Boolean);

      2: (vc: Char);

      3: (vac: AnsiChar);

      4: (vf: Single);

      5: (vd: DWORD);

    end;

    tpint64dbl = record

        0: (value: int64);

        1: (vdouble: Double);

        2:

         (

           High: DWORD;

           low: DWORD

         )

    end; 

 tva結構就專門用來将那些低于4位元組的參數變成4位元組然後進行Push,而 tpint64dbl就是将8位元組的Int64和double變成2個4位元組進行壓棧, tpint64dbl在壓棧時,先壓low低位元組,然後壓入High高4位元組。于是這些基本參數就可以一一壓入堆棧。然後就是一些特殊類型的字段,比如說object,string,以及Record等複雜類型的資料的壓棧模式,這個俺們隻要懂一點Win32彙編的就可以知道,這些參數的壓棧實際上都是偏移位址入棧,是以,取得其位址然後壓入堆棧就行了。

那麼處理模式可以如下:

如果參數是String,那麼就直接 

        st := Params[i];

        tmp := Integer(PChar(st));

        asm

          push tmp

        end;

如果是Object,那麼在 Delphi中直接就是位址

然後傳遞參數的模式,我們就可以用for 尾部 downto 0按照規則進行壓棧

如果是高版本的Delphi我們可以直接用array of TValue作為參數進行傳遞,那麼函數的調用實作模式可以如下

<a></a>

function InvokeRtti(codeptr: Pointer;Params: array of TValue): Integer;overload;

var

  i: Integer;

  type

    tva=record

      case Integer of

      0: (vi: Integer);

      1: (vb: Boolean);

      2: (vc: Char);

      3: (vac: AnsiChar);

      4: (vf: Single);

      5: (vd: DWORD);

    end;

    tpint64dbl = record

        0: (value: int64);

        1: (vdouble: Double);

        2:

         (

           High: DWORD;

           low: DWORD

         )

  p64: tpint64dbl;

  v: tva;

  tmp: Integer;

  st: string;

begin

  for i := High(Params) downto 0 do

  begin

    case Params[i].TypeInfo.Kind of

    tkInteger:

      begin

        tmp := Params[i].AsInteger;

        asm

          push tmp

        end;

      end;

    tkChar:

    begin

       v.vac := params[i].AsType&lt;AnsiChar&gt;;

       asm

          push v

       end;

    tkWChar:

      v.vc := Params[i].AsType&lt;Char&gt;;

      asm

    tkFloat:

        v.vf := Params[i].AsType&lt;Single&gt;;

    tkInt64:

        p64.value := Params[i].AsInt64;

          lea ecx,p64

          push [ecx][4]  //先壓低位元組,再壓高位元組

          push [ecx][0]

    tkRecord,tkClass:

        tmp := Integer(Params[i].GetReferenceToRawData);

    tkString,tkUString:

        st := Params[i].AsString;

        tmp := Integer(PChar(st));

  end;

  tmp := Integer(codeptr);

  asm

    call tmp

    mov  result,eax

end;

使用方法

h := LoadLibrary('user32.dll');

  if h &lt;&gt; 0 then

    fh := GetProcAddress(h,'MessageBoxW'); 

 if fh &lt;&gt; nil then

    InvokeRtti(fh,[TValue.From(Handle),'asf','234',64])

如果是低版本的,可以由Variant入手來實作如下

function Invoke(codeptr: Pointer;Params: array of Variant): Integer;overload;

  ast: AnsiString;

  vtype: TVarType;

    vtype := VarType(Params[i]);

    case vtype of

    varUString:

        st := Params[i];

    varString:

        ast := AnsiString(st);

        tmp := Integer(PAnsiChar(ast));

    varInteger,varSmallint,varWord,varByte:

        v.vi := Params[i];

    varLongWord:

        v.vd := Params[i];

    varSingle:

        v.vf := Params[i];

    varDouble:

        p64.vdouble := Params[i];

    varInt64:

        p64.value := Params[i];

end; 

用法類似如下:

procedure Showmsg(msg: string;tmp: Double); stdcall;

  ShowMessage(msg+floattostr(tmp));

  tmp: Single;

  tmp := 13234.24;

  Invoke(@Showmsg,['asfsf',tmp])

至此基本上的功能就完成了,不過此種方法有一個不好的問題,那就是對于調用函數的傳回結果,無法預測,傳回的都是integer類型,而無法确定傳回的結果是位址還是确實就是integer還是說是其他類型。另外,此方法并非全面的一些實作,如果要使用的全面請反彙編參考Delphi對應的函數調用的參數傳遞過程以對應進行修正。 

本文轉自 不得閑 部落格園部落格,原文連結: http://www.cnblogs.com/DxSoft/p/3469228.html  ,如需轉載請自行聯系原作者

繼續閱讀