奇淫怪巧之在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
      case Integer of
        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作為參數進行傳遞,那么函數的調用實現模式可以如下

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
       case Integer  of
         0: (value: int64);
         1: (vdouble: Double);
         2:
         (
           High: DWORD;
           low: DWORD
         )
     end;
var
  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<AnsiChar>;
       asm
          push v
        end;
     end;
    tkWChar:
     begin
      v.vc := Params[i].AsType<Char>;
      asm
          push v
       end;
     end;
    tkFloat:
       begin
        v.vf := Params[i].AsType<Single>;
        asm
          push v
         end;
       end;
    tkInt64:
       begin
        p64.value := Params[i].AsInt64;
        asm
          lea ecx,p64
          push [ecx][ 4]  //先壓低字節,再壓高字節
          push [ecx][ 0]
         end;
       end;
    tkRecord,tkClass:
       begin
        tmp := Integer(Params[i].GetReferenceToRawData);
        asm
          push tmp
         end;
       end;
    tkString,tkUString:
       begin
        st := Params[i].AsString;
        tmp := Integer(PChar(st));
        asm
          push tmp
         end;
       end;
     end;
   end;
  tmp := Integer(codeptr);
  asm
    call tmp
    mov  result,eax
   end;

end;

 

使用方法

h := LoadLibrary( ' user32.dll ');
   if h <>  0  then
    fh := GetProcAddress(h, ' MessageBoxW '); 
  if fh <>  nil  then
   begin
    InvokeRtti(fh,[TValue.From(Handle), ' asf ', ' 234 ', 64])

  end;

 

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

function Invoke(codeptr: Pointer;Params:  array  of Variant): 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
       case Integer  of
         0: (value: int64);
         1: (vdouble: Double);
         2:
         (
           High: DWORD;
           low: DWORD
         )
     end;
var
  p64: tpint64dbl;
  v: tva;
  tmp: Integer;
  st: string;
  ast: AnsiString;
  vtype: TVarType;
begin
   for i := High(Params)  downto  0  do
   begin
    vtype := VarType(Params[i]);
     case vtype  of
    varUString:
       begin
        st := Params[i];
        tmp := Integer(PChar(st));
        asm
          push tmp
         end;
       end;
    varString:
       begin
        st := Params[i];
        ast := AnsiString(st);
        tmp := Integer(PAnsiChar(ast));
        asm
          push tmp
         end;
       end;
    varInteger,varSmallint,varWord,varByte:
       begin
        v.vi := Params[i];
        asm
          push v
         end;
       end;
    varLongWord:
       begin
        v.vd := Params[i];
        asm
          push v
         end;
       end;
    varSingle:
       begin
        v.vf := Params[i];
        asm
          push v
         end;
       end;
    varDouble:
       begin
        p64.vdouble := Params[i];
        asm
          lea ecx,p64
          push [ecx][ 4]  //先壓低字節,再壓高字節
          push [ecx][ 0]
         end;
       end;
    varInt64:
       begin
        p64.value := Params[i];
        asm
          lea ecx,p64
          push [ecx][ 4]  //先壓低字節,再壓高字節
          push [ecx][ 0]
         end;
       end;
     end;
   end;
  tmp := Integer(codeptr);
  asm
    call tmp
    mov  result,eax
   end;

end

 

用法類似如下:

procedure Showmsg(msg: string;tmp: Double); stdcall;
begin
  ShowMessage(msg+floattostr(tmp));
end;

var
  tmp: Single;
begin
  tmp :=  13234.24;
  Invoke(@Showmsg,[ ' asfsf ',tmp])

end;

 

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM