首页
社区
课程
招聘
[旧帖] [求助]关于堆栈平衡问题? 0.00雪花
发表于: 2009-1-24 18:01 12179

[旧帖] [求助]关于堆栈平衡问题? 0.00雪花

2009-1-24 18:01
12179
如果要nop掉的是一个call指令,如何保持堆栈平衡呢?
只要按ENTER“跟随”进入被call的函数,看一下函数的返回指令,如果是retn,就直接用Nop替换;如果是retn XXX,就把call替换成为add esp, XXX;
就这样。具体请查阅指令手册关于retn指令的说明。


我可能要气疯大家了,我也没脾气了,大家要生气的自便~!很菜的问题! 达文西,对不住您!

我用OD1加载一个OD2,F9运行起OD2,然后查看句柄,输入bp SetWindowTextA [esp+4]==003C0152下断,断在下面的位置:

004779DB  |.  E8 4CF20200   call 复件_吾?004A6C2C
004779E0  |.  83C4 10       add esp,10
[COLOR="Red"]004779E3  |.  8D85 E0FEFFFF lea eax,[local.72]
[COLOR="red"]004779E9  |.  50            push eax                                 ; /Text
004779EA  |.  8B15 7C3B4D00 mov edx,dword ptr ds:[4D3B7C]            ; |
004779F0  |.  52            push edx                                 ; |hWnd => 003C0152 ('吾爱破解 - [LCG]',class='1212121')
[COLOR="red"]004779F1  |.  E8 C87B0300   call <jmp.&USER32.SetWindowTextA>        ; \SetWindowTextA
004779F6  |.  C705 88574D00>mov dword ptr ds:[4D5788],1
00477A00  |.  33C9          xor ecx,ecx


这里可以直接修改004779E9  push eax;eax指向“吾爱破解”地址003C0152
比如修改成004779E9  push 003C0152
(没错误吧!?)

还有直接修改 关键句:call <jmp.&USER32.SetWindowTextA> ,把这个call给NOP掉;
我看了一下004779E3  lea eax,[local.72]这句,汇编中显示的是lea eax,dword ptr ss:[ebp-120],则004779E9  push eax就应该是push    dword ptr ss:[ebp-120]吧(应该没错误吧!?)按照堆栈平衡方法,是不是要修改call <jmp.&USER32.SetWindowTextA> 为sun esp,120?或者是别的,我觉得不对劲,因为有:
push eax
push edx
就要有
pop edx
pop eax
这样才保持堆栈平衡!(我在发表愚见了)

另外,理解
只要按Enter“跟随”进入被call的函数,看一下函数的返回指令
这句,我是觉得他说的call是过程函数,而不是api函数的调用call。我先在call <jmp.&USER32.SetWindowTextA>按Enter“跟随”,进入下面一段代码:
[COLOR="Red"]004AF5BE   $- FF25 6CD95000 jmp dword ptr ds:[<&USER32.SetWindowText>;  user32.SetWindowTextA
004AF5C4   $- FF25 70D95000 jmp dword ptr ds:[<&USER32.ShowCaret>]   ;  user32.ShowCaret
004AF5CA   $- FF25 74D95000 jmp dword ptr ds:[<&USER32.ShowScrollBar>;  user32.ShowScrollBar
004AF5D0   $- FF25 78D95000 jmp dword ptr ds:[<&USER32.ShowWindow>]  ;  user32.ShowWindow

然后在jmp dword ptr ds:[<&USER32.SetWindowText>处按Enter“跟随”,到下面一段代码:
77D17EEB >  8B4C24 04       mov ecx,dword ptr ss:[esp+4]
77D17EEF    56              push esi
77D17EF0    E8 2FBBFFFF     call user32.77D13A24
77D17EF5    8BF0            mov esi,eax
77D17EF7    85F6            test esi,esi
77D17EF9    74 23           je short user32.77D17F1E
77D17EFB    56              push esi
77D17EFC    E8 E8D9FFFF     call user32.77D158E9
77D17F01    85C0            test eax,eax
77D17F03    6A 01           push 1
77D17F05    FF7424 10       push dword ptr ss:[esp+10]
77D17F09    6A 00           push 0
77D17F0B    6A 0C           push 0C
77D17F0D    56              push esi
77D17F0E    74 12           je short user32.77D17F22
77D17F10    E8 86D4FFFF     call user32.77D1539B
77D17F15    33C9            xor ecx,ecx
77D17F17    85C0            test eax,eax
77D17F19    0F9DC1          setge cl
77D17F1C    8BC1            mov eax,ecx
77D17F1E    5E              pop esi
[COLOR="red"]77D17F1F    C2 0800         retn 8

可以看到最后的是retn 8,所以我直接修改call <jmp.&USER32.SetWindowTextA>为add esp,8。

我上面的思路很乱来,中心问题就是一个,怎么进入被call的函数USER32.SetWindowTextA,并找到函数USER32.SetWindowTextA的返回指令。

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (5)
雪    币: 293
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
有必要这么麻烦吗?一般的call都是这个样子的:
push 123
push 456
push 789
call 12345678
比如说你想nop掉call 12345678,先断在call这一行,然后看堆栈地址,比如说是0012FA00,然后按F8单步执行call,再看堆栈地址,比如说它变成了0012FA0C,这是你就知道了参数占了0012FA0C-0012FA00=0C个字节,直接把call 12345678这句修改成add esp,0C就行了。
2009-1-24 18:45
0
雪    币: 193
活跃值: (857)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
哈哈哈哈哈哈~! 简单问题复杂化了我~!
2009-1-24 18:57
0
雪    币: 193
活跃值: (857)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
sub esp,1 在堆栈中留出局部变量的空间 


看看下面的文章对你的汇编语言一定有帮助! 


汇编中参数的传递和堆栈修正 

在 Win32汇编中,我们经常要和 Api 打交道,另外也会常常使用自己编制的类似于 Api 的带参数的子程序,本文要讲述的是在子程序调用的过程中进行参数传递的概念和分析。一般在程序中,参数的传递是通过堆栈进行的,也就是说,调用者把要传递给子程序(或者被调用者)的参数压入堆栈,子程序在堆栈取出相应的值再使用,比如说,如果你要调用 SubRouting(Var1,Var2,Var3),编译后的最终代码可能是 

push Var3 
push Var2 
push Var1 
call SubRouting 
add esp,12 

也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到调用前的状态。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生不正确的结果,这就是我在前面使用“可能”这两个字的原因:各种语言中调用子程序的约定是不同的,它们的不同点见下表: 

C SysCall StdCall Basic Fortran Pascal 
参数从左到右 是 是 是 
参数从右到左 是 是 是 
调用者清除堆栈 是 
允许使用:VARARG 是 是 是 

VARARG 表示参数的个数可以是不确定的,有一个例子就是 C 中的 printf 语句,在上表中,StdCall 的定义有个要说明的地方,就是如果 StdCall 使用 :VARARG 时,是由调用者清除堆栈的,而在没有:VARARG时是由被调用者清除堆栈的。 
在 Win32 汇编中,约定使用 StdCall 方式,所以我们要在程序开始的时候使用 .model stdcall 语句。也就是说,在 API 或子程序中,最右边的参数先入堆栈,然后子程序在返回的时候负责校正堆栈,举例说明,如果我们要调用 MessageBox 这个 API,因为它的定义是 MessageBox(hWnd,lpText,lpCaption,UType) 所以在程序中要这样使用: 

push MB_OK 
push offset szCaption 
push offset szText 
push hWnd 
call MessageBox 
... 

我们不必在 API 返回的时候加上一句 add sp,4*4 来修正堆栈,因为这已经由 MessageBox 这个子程序做了。在 Windows API 中,唯一一个特殊的 API 是 wsprintf,这个 API 是 C 约定的,它的定义是 wsprintf(lpOut,lpFormat,Var1,Var2...),所以在使用时就要: 

push 1111 
push 2222 
push 3333 
push offset szFormat 
push offset szOut 
call wsprintf 
add esp,4*5 

下面要讲的是子程序如何存取参数,因为缺省对堆栈操作的寄存器有 ESP 和 EBP,而 ESP是堆栈指针,无法暂借使用,所以一般使用 EBP 来存取堆栈,假定在一个调用中有两个参数,而且在 push 第一个参数前的堆栈指针 ESP 为 X,那么压入两个参数后的 ESP 为 X-8,程序开始执行 call 指令,call 指令把返回地址压入堆栈,这时候 ESP 为 X-C,这时已经在子程序中了,我们可以开始使用 EBP 来存取参数了,但为了在返回时恢复 EBP 的值,我们还是再需要一句 push ebp 来先保存 EBP 的值,这时 ESP 为 X-10,再执行一句 mov ebp,esp,根据右图可以看出,实际上这时候 [ebp + 8] 就是参数1,[ebp + c]就是参数2。另外,局部变量也是定义在堆栈中的,它们的位置一般放在 push ebp 保存的 EBP 数值的后面,局部变量1、2对应的地址分别是 [ebp-4]、[ebp-8],下面是一个典型的子程序,可以完成第一个参数减去第二个参数,它的定义是: 

MyProc proto Var1,Var2 ;有两个参数 
local lVar1,lVar2 ;有两个局部变量 

注意,这里的两个 local 变量实际上没有被用到,只是为了演示用,具体实现的代码是: 

MyProc proc 

push ebp 
mov ebp,esp 

sub esp,8 

mov eax,dword ptr [ebp + 8] 
sub eax,dword ptr [ebp + c] 

add esp,8 

pop ebp 
ret 8 

MyProc endp 

现在对这个子程序分析一下,push ebp/mov ebp,esp 是例行的保存和设置 EBP 的代码,sub esp,8 在堆栈中留出两个局部变量的空间,mov /add 语句完成相加,add esp,8 修正两个局部变量使用的堆栈,ret 8 修正两个参数使用的堆栈,相当于 ret / add esp,8 两句代码的效果。可以看出,这是一个标准的 Stdcall 约定的子程序,使用时最后一个参数先入堆栈,返回时由子程序进行堆栈修正。当然,这个子程序为了演示执行过程,使用了手工保存 ebp 并设置局部变量的方法,实际上,386 处理器有两条专用的指令是完成这个功能用的,那就是 Enter 和 Leave,Enter 语句的作用就是 push ebp/mov ebp,esp/sub esp,xxx,这个 xxx 就是 Enter 的,Leave 则完成 add esp,xxx/pop ebp 的功能,所以上面的程序可以改成: 

MyPorc proc 
enter 8,0 

mov eax,dword ptr [ebp + 8] 
sub eax,dword ptr [ebp + c] 

leave 
ret 8 
MyProc endp 


好了,说到这儿,参数传递的原理也应该将清楚了,还要最后说的是,在使用 Masm32 编 Win32 汇编程序的时候,我们并不需要记住 [ebp + xx] 等麻烦的地址,或自己计算局部变量需要预留的堆栈空间,还有在 ret 时计算要加上的数值,Masm32 的宏指令都已经把这些做好了,如在 Masm32 中,上面的程序只要写成为: 

MyProc proc Var1,Var2 
local lVar1,lVar2 

mov eax,Var1 
sub eax,Var2 
ret 

MyProc endp 

编译器会自动的在 mov eax,Var1 前面插上一句 Enter 语句,它的参数会根据 local 定义的局部变量的多少自动指定,在 ret 前会自动加上一句 Leave,同样,编译器会根据参数的多少把 ret 替换成 ret xxx,把 mov eax,Var1 换成 mov eax,dword ptr [ebp + 8] 等等。 

最后是使用 Masm32 的 invoke 宏指令,在前面可以看到,调用带参数的子程序时,我们需要用 push 把参数压入堆栈,如果不小心把参数个数搞错了,就会使堆栈不平衡,从而使程序从堆栈中取出错误的返回地址引起不可预料的后果,所以有必要有一条语句来完成自动检验的任务,invoke 就是这样的语句,实际上,它是自动 push 所有参数,检测参数个数、类型是否正确,并使用 call 来调用的一个宏指令,对于上面的 push/push/call MyProc 的指令,可以用一条指令完成就是: 

invoke MyProc,Var1,Var2 

当然,当程序编译好以后你去看机器码会发现它被正确地换成了同样的 push/push/call 指令。但是,在使用 invoke 之前,为了让它进行正确的参数检验,你需要对函数进行申明,就象在 C 中一样,申明的语句是: 

MyProc proto :DWORD,:DWORD 

语句中 proto 是关键字,表示申明,:DWORD 表示参数的类型是 double word 类型的,有几个就表示有几个参数,在 Win32 中参数都是 double word 型的,申明语句要写在 invoke 之前,所以我们一般把它包括在 include 文件中,好了,综合一下,在 Masm32 中使用一个带参数的子程序或者 Api ,我们只需用: 

... 
MyProc proto :dword,:dword 
... 
.data 
x dd ? 
y dd ? 
dwResult dd ? 
... 
mov x,1 
mov y,2 
invoke MyProc x,y 
mov dwResult,eax 
2009-1-24 19:28
0
雪    币: 193
活跃值: (857)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
如果要nop掉的是一个call指令,如何保持堆栈平衡呢?
只要按ENTER“跟随”进入被call的函数,看一下函数的返回指令,如果是retn,就直接用Nop替换;如果是retn XXX,就把call替换成为add esp, XXX;
就这样。具体请查阅指令手册关于retn指令的说明。 


这种方法不知道怎么做,不知道我上面说的歪方法正确不?
2009-1-24 19:53
0
雪    币: 578
活跃值: (808)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
学习了,收藏
2022-9-18 19:31
0
游客
登录 | 注册 方可回帖
返回
//