前一陣子,研究了一段時間的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<AnsiChar>;
asm
push v
end;
tkWChar:
v.vc := Params[i].AsType<Char>;
asm
tkFloat:
v.vf := Params[i].AsType<Single>;
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 <> 0 then
fh := GetProcAddress(h,'MessageBoxW');
if fh <> 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 ,如需轉載請自行聯系原作者