[原创] 试探编译后class里面的method和普通的procedure调用区别...(0分)

  • 主题发起人 主题发起人 Rocklee
  • 开始时间 开始时间
R

Rocklee

Unregistered / Unconfirmed
GUEST, unregistred user!
首先这是偶的第一篇原创,也是首次混合basm编程,请各位要转载的话,不要把tiger的名字去掉。
第一次写东东,写得不好,不要见怪。

这里讲的method是指class里面的procedure或function,也就是说 procedure of object。那到底method和普通的procedure有什么不一样?

method在pascal语法的定义为 procedure ... of object;或function ... of object;和一般的procedure/function区别在于一个带有self,一个则没有。

在一个关于hook method 的研究课题中发现。拦截普通的procedure或function,能返回到原来的地址,并且参数传递也OK。
但后来将此方法转到method的hook上时,发现出问题了,调试后发现被hook后返回原来的地址执行代码,self的值不见了!而且里面一些属性的值也不见了!

为了弄清原理,于是就展开了一场asm跟踪。

首先,我们理清一下,一般procedure调用的参数传递如何在asm中体现吧,以下为标准的delphi procedure测试(Delphi与C不一样,默认是用寄存器进行参数传递,目的为了高效),诸如stdcall,asm等其他方式的procedure这里不论论。

写了一个简单的例子:
procedure test (a:integer;b:integer);
var l_a,l_b:integer;
begin
l_a:=a;
l_b:=b;
form1.Perform(l_a,l_b,0);//D把断点落在这里,看看如何调用method。
end;

procedure test1 (a:integer;b:integer;c:integer);
var l_a,l_b,l_c,l_d:integer;
begin
l_a:=a;
l_b:=b;
l_c:=c;
l_d:=form1.Perform(l_a,l_b,l_c);
end;
procedure test2 (a:integer;b:integer;c:integer;d:integer;e:integer);
var l_a,l_b,l_c,l_d:integer;
begin
l_a:=a;
l_b:=b;
l_c:=c+d+e;
l_d:=form1.Perform(l_a,l_b,l_c);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
test(1,2)
//A,小于3个参数的调用
test1(1,2,3)
//B,等于3个参数的调用
test2(1,2,3,4,5)
//C,大于3个参数的调用
end;

把断点落在A处,等中断暂停时按Ctrl-Alt-C呼出CPU窗口,翻译出的asm为:
mov edx,$2
mov eax,$1
call test


也就是把第一个参数赋入eax,第二个参数赋入edx,再call test。得出结论:Delphi的普通procedure参数传递方式是:第一个参数赋给eax,第二个参数赋给edx。
把断点落在B处,等中断暂停时按Ctrl-Alt-C呼出CPU窗口,翻译出的asm为:
mov ecx,$3
mov edx,$2
mov eax,$1
call test1
得出结论:这种情况下,参数传递方式是:第一个参数赋给eax,第二个参数赋给edx,第三个参数赋给ecx。

把断点落在C处,等中断暂停时按Ctrl-Alt-C呼出CPU窗口,翻译出的asm为:
push $04
push $05
mov ecx,$3
mov edx,$2
mov eax,$1
call test2
从这里可以看出,当参数大于3时,后面的参数得先入栈,前三个参数的赋值跟B处调用相同。

以上是一般procedure的调用参数传递过程,从反汇编可以看出,eax被用上了。


再看看,form1.perform的调用如何进行。把断点放在D处,反汇编得出:
inc eax //很明显,第一个参数赋给eax
dec eax
inc edx //第二个参数赋给edx
dec edx
push $0 //执行到这里时,eax依然为1,也就是第一个参数的值
mov ecx,edx //将第二个参数赋给ecx
mov edx,[Form1] //将Form1实例的地址传给edx
xchg eax,edx //执行这部之前,eax依然为1,也就是第一个参数的值,但之后,跟edx交换了,变成eax为Form1的地址,edx为第一个参数。
call TControl.Perform
也就是说进入call perform之前,eax已经指向Form1!,而其他的参数则逐个往后推一个寄存器,也就是说,原本往eax里的第一个参数现在放edx,第二个参数则存放于ecx中...

为了验证是否eax是不是实例(也就是class的可见对象:Self),我在Formcreate中加入了:
Caption:=inttostr(integer(self));显示为14100056,跟踪到Tcontrol.perform里的if Self <> nil then...这句中断,反汇编后,eax的值为D72658,即14100056,
而且if self <>nil then 这句被反汇编为
test eax,eax
jz +$0b
再次证明,self的值是靠eax传递的!


那么class内部的method1 call method2又如何传递参数呢?
我们再看看:
在public节下建立一procedure:
procedure proc1( a:integer;b:integer);

procedure TForm1.proc1( a:integer;b:integer);
var l_a,l_b:integer;
begin
l_a:=a+1-1;
l_b:=b+1-1;
Perform(l_a,l_b,0);
end;

然后在button1click中加入 proc1(1,2);并把断点停在这里反汇编:
mov ecx,$2
mov edx,$1
mov eax,ebx //看,在class内部进行内部method的呼叫时,ebx总是保存着self的值,随时赋给eax
call TForm1.proc1
再把断点落在 proc1中的 Perform(l_a,l_b,0);可以发现,此时的eax和ebx均指向form1。
可以得到一个结论,在class里调用同一class的method,也需要向eax赋值,而给它赋值的是ebx,一个随时候命的忠实臣子。


言归正传,现在看看TControl.perForm里的代码及其反汇编:

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;
反汇编,Message各元素的赋值:
mov [ebp-$10],edx //Message.Msg=Msg
mov [ebp-$0c],ecx //Message.WParm=WParm
mov edx,[ebp+$08] //Message.LParm=LParm,第三个参数放于[ebp+$8]中!
mov [ebp-$08],edx
xor edx,edx //Message.Result=0
mov [ebp-$04],edx
.....


FormCreate中的代码:

procedure TForm1.FormCreate(Sender: TObject);
var l1,l2:integer;
l0:Cardinal;
begin
Caption:=inttostr(integer(self));
HookPerForm;
l0:=WM_ACTIVATE;
l1:=1;
l2:=2;
if button1.Perform(l0,l1,l2)=0 then //看看这里的汇编
//captioN:='1';
end;

反汇编以上的if ...这句:
push edi
mov ecx,esi
mov edx,[ebp-$04]
mov eax,[ebx+$2f0] //将self赋给eax!
call TControl.Perform

进入Perform后,第一句已经被成功修改为Jmp tcc.perform1,也就是新的地址,此时EAX的值不变,指向self。
function tcc.perform1(Msg: Cardinal
WParam, LParam: Longint): Longint;
var var
lpw: PWChar;
l_lp: Longint;
l_Msg:Cardinal;
l_wParm:Longint;
l_lParm:Longint;
begin
l_Msg:=Msg;
l_wParm:=WParam;
l_lParm:=LParam;
result:=performaddr2(l_Msg,l_wParm,l_lParm);
end;
反汇编:
mov eax,edx //l_Msg=Msg ==>这里出问题了,首当其冲,eax被冲掉!
mov edx,ecx //l_wParm=wParm
mov ecx,[ebp+$08] //l_lParm=LParm
push ecx
mov ecx,edx
mov edx,eax
mov eax,[performaddr2+$4]

途中碰了无数次钉子,灰心了,从冰箱里拿了个橙子吃,一般吃一般看老妖写的ring0层调试,像雾里看花,不过橙子味道很好......



N分钟后,再来......
再N次失败后,终于成功了!!!

function tcc.perform1(Msg: Cardinal
WParam, LParam: Longint): Longint;
var
lpw: PWChar;
l_lp: Longint;
l_Msg: Cardinal;
l_wParm: Longint;
l_lParm: Longint;
l_object: TControl;
begin
{object procedure call seq:
eax -- self
edx -- first parameter
ecx -- second parameter
}
asm
mov l_object ,eax;
end;
l_Msg := Msg;
l_wParm := WParam;
l_lParm := LParam;
asm
//mov eax,l_lParm;
push l_lParm;
mov ecx,l_wParm;
mov edx,l_Msg;
mov eax,l_object
//必须用l_object重置eax,因为上面几条asm已经把eax变得面目全非了!
call dword ptr [performaddr2];
mov ebx,eax;
end;
end;

这是针对perform的hookapi 的修改,今晚是个值得纪念的日子,因为困绕N个月的问题终于得到解决,而且这是个没有前人尝试过的方法。
今天是清明节,时间不允许,不能及时扫墓,希望先人原谅..............
 
tiger即是偶。
贴子原起于http://www.delphibbs.com/delphibbs/dispq.asp?lid=3356511
自己找到答案,这种做法好像在大富翁里偶是第一个,所以贴出和大家分享。[:D]
 
后退
顶部