前一陣子,研究了一段時間的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。
就是說比如我們設計一個腳本,在腳本中可能輸入如下:
那么我們在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字節進行壓棧。所以此時我們就可以設計兩個數據結構用來處理轉換這些類型
end;
tva結構就專門用來將那些低於4字節的參數變成4字節然后進行Push,而 tpint64dbl就是將8字節的Int64和double變成2個4字節進行壓棧, tpint64dbl在壓棧時,先壓low低字節,然后壓入High高4字節。於是這些基本參數就可以一一壓入堆棧。然后就是一些特殊類型的字段,比如說object,string,以及Record等復雜類型的數據的壓棧模式,這個俺們只要懂一點Win32匯編的就可以知道,這些參數的壓棧實際上都是偏移地址入棧,所以,取得其地址然后壓入堆棧就行了。
那么處理模式可以如下:
如果參數是String,那么就直接
st := Params[i];
如果是Object,那么在 Delphi中直接就是地址
然后傳遞參數的模式,我們就可以用for 尾部 downto 0按照規則進行壓棧
如果是高版本的Delphi我們可以直接用array of TValue作為參數進行傳遞,那么函數的調用實現模式可以如下
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;
使用方法
if h <> 0 then
fh := GetProcAddress(h, ' MessageBoxW ');
if fh <> nil then
begin
InvokeRtti(fh,[TValue.From(Handle), ' asf ', ' 234 ', 64])
end;
如果是低版本的,可以由Variant入手來實現如下
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;
用法類似如下:
begin
ShowMessage(msg+floattostr(tmp));
end;
var
tmp: Single;
begin
tmp := 13234.24;
Invoke(@Showmsg,[ ' asfsf ',tmp])
end;
至此基本上的功能就完成了,不過此種方法有一個不好的問題,那就是對於調用函數的返回結果,無法預測,返回的都是integer類型,而無法確定返回的結果是地址還是確實就是integer還是說是其他類型。另外,此方法並非全面的一些實現,如果要使用的全面請反匯編參考Delphi對應的函數調用的參數傳遞過程以對應進行修正。