[转帖]Calls 怎样使用堆栈

来源:百度文库 编辑:神马文学网 时间:2024/04/27 21:29:23
加密狗复制|加密锁复制|软件狗|dongle|复制加密狗|加密狗破解|加密锁破解|密码狗破解|软件破解|usb硬件| >> 加密锁|keypro|dog|dongle|加密狗复制 >> 交流区 >> 破解交流区 >> [转帖]Calls 怎样使用堆栈
[转帖]Calls 怎样使用堆栈 saint12,2004-05-13 14:37:27

Calls 怎样使用堆栈(一)

Calls 怎样使用堆栈
by Ignatz of stoicForce
(如有错误敬请指正----译者) 

前言 

这是一篇关于call和程序怎样利用堆栈将变量传送给call的文章,非常有用,尤其对新手。我觉得熟练的cracker不会从中学到多少东西,但是如果不知道堆栈的工作机理、call是干什么的、参数是如何传给call的,那么这篇文章会对你大有帮助。
我还想声明一点,每个人应当为自己的行为负责,利用这里提供的知识作任何非法的事,我将不承担责任。下面让我们舒适愉快的享受吧。
所需工具
你所需要的是:1、大脑中的闲置空间 2、饮料
长岛冰茶就可以了:40毫升郎姆酒,40毫升特魁乐,40毫升威士忌,40毫升伏特加,些许柠檬汁和可乐,真是十全十美。 

为什么要call? 

我相信你已经问过自己什么是call?它有什么好处?比方说,你写了一段程序,需要很频繁的在屏幕显示文字,每次的文字不一样,但每次将文字调到屏幕上的代码是一样的,所以在屏幕上显示的每一个信息,你都要为它一遍一遍地将全部代码写下来。不过,有一种更省力的办法,就是只将代码写一遍,然后给这段代码起个名字,比如"write_to_screen",以后你需要做的就只是调用它,而不必一遍一遍的写了。不过等一下,还有另一个问题:有时信息的确不同,所以必须有个变量如"msg",它包含着我们想显示的信息,当然了,我们得把变量传给此函数。在c++这样的高级语言中应当是这样:
write_to_screen(msg);
但在汇编语言中是什么样子呢?会是这样:
push msg
call write_to_screen
这个例子解释了本教程要说明的两话题:什么是call,为什么需要将变量传送给call。现在你已经了call的好处以及它的作用。或许你急于了解那个push,下面我们要接触的东西就是堆栈了。 

堆栈 

是储存数值的结构,这些数值都是4个字节(=1 DWORD),工作方式是后进先出。就是说最后放进堆栈的数值最先出栈,与堆栈有关的命令只有两个,push和pop,push将数值放入堆栈,而pop将数值从堆栈中取出。举个例子:
push a  Stack= {a}
push b  Stack= {a,b}
push c  Stack= {a,b,c}
pop eax  Stack= {a,b} eax = c
pop ebx  Stack= {a} eax = c; ebx = b
push eax    Stack= {a,c} eax = c; ebx = b
push 5  Stack= {a,c,5} eax = c; ebx = b 

不过push和pop还能做其他事情,要明白这一点我们应首先了解一下一种寄存器ESP(堆栈指针,这个寄存器总是指向堆栈起始处,就是说后来者把门,如果一个数值入栈,esp值就减4,如果一个数值出栈,esp值就加4,例如:
ESP = 7E0000; EAX = 01020304
: ...
:7DFFF0 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
:7E0000 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
这是初始状态,堆栈装着78563421(原文如此--译者) 

现在将EAX入栈,情况如下:
ESP = ESP - 4 = 7DFFF0                      ESP 指向这里
: ...                                       |
:7DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 04 03 02 01
:7E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
如你所见,堆栈指针调整了,数值被写在正确的位置。 

pop ebx
你可以再次将数值弹出
ESP = ESP + 4 = 7E0000  EBX = 01020304
:7DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 04 03 02 01
:7E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
        |
        ESP 现在这向这里
        注意被弹出的数值不变 

当然你也可以将ESP值增加,也可以将ESP值减少
让我们看看如果ESP加1会发生什么
ESP = ESP + 1 = 7E0001
:7DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 04 03 02 01
:7E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
           |
           ESP 现在指向这里. 

现在我们将一个数值弹出
pop EBX
ESP = ESP + 4 = 7E0005
:7DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 04 03 02 01
:7E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
                       |
                       ESP 现在指向这里.
EBP为00785634 

通过加1,我们看到堆栈失序,因此对ESP只应当加是4的整数倍的值。
现在你知道了堆栈的工作原理和作用,让我们到call的内部看看。 

call的内部 

正如你看到的,堆栈是存储数值的理想场所,我们再看看前面的小例子。运用刚才学到的知识,我们能知道发生了什么,甚至发现了一个严重的错误。
push msg              ; 这句指令不会起作用,因为我们只能将4个字节推入堆栈!
call write_to_screen  ; 这个 call 会将数据从堆栈读出 

因此我们必须传送msg的地址。我们用 lea eax , msg 命令可以轻松的做到这一点。现在AX有了msg的地址,然后我们用 push eax 将地址入栈,你可以准确的看到正在发生的事情:
data: EAX = 00000000
:00450000 31 32 33 34 35 36 37 38-39 30 00 00 00 00 00 00   1234567890...... 

stack: ESP = 007E0000
:007DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 04 03 02 01
:007E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
push前的状态 

:00412l00 lea eax, msg  ; 除了EAX被置为00450000,什么也没变
:00412104 push eax   ; 数据没变但是堆栈变了
              ; stack: ESP = 007DFFFC                         ............ 地址
              ; :007DFFF0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 45 00
              ; :007E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
:00412105 call write_to_screen
              ; put the serial to the screen.
     ; short break ---
:00412110 next instruction ... 

现在call会做什么呢?会作一件非常重要的事情。它会把下一条指令的地址自动的放入堆栈,为什么这样做?因为它只是个调用而不是个跳转,在它做完了该做的事情,执行了调用的子程序之后,程序应当继续运行。这就是call的全部秘密。这样它把返回的地址存储到堆栈,计算机知道下面做什么。让我们进入这个call看看堆栈的样子:
stack: ESP = 007DFFF8             ........... 返回的地址入栈
:007DFFF0 00 00 00 00 00 00 00 00-10 21 41 00 00 00 45 00
:007E0000 12 34 56 78 00 00 00 00-00 00 00 00 00 00 00 00
然后call会继续 

所以每当进入一个call,你都会得到位于栈顶的返回的地址。现在只剩一件事了:call是如何从堆栈读取数据的?

saint12,2004-05-13 14:38:01

Calls 怎样使用堆栈(二)

从堆栈读取数据 

这部分最有趣。有许多方法可以做到这点。第一种可能的方法是将返回的数值弹出并将它存到某处,然后将所有其他数据弹出,再将返回地址入栈,当然这很繁琐,特别是在有许多数据和一个复杂的call的情况下,这将迫使你再次将数值存入堆栈,因为寄存器不能保存所有的数据,这种方法或许只能应付带很少参数的小call。
那么其他方法是什么呢?很简单,将EBP(基指针,用于地址变量)存入堆栈,然后用 mov EBP ,ESP 命令将堆栈地址存入EBP。现在你可以用类似 mov eax , [EBP + 20] 这样的命令对所有变量进行EBP相对寻址操作,通过这种方法你对堆栈有了更清楚的了解。你仍可以在call中正常的使用堆栈,只要确保返回值位于栈顶,且离开call时再次调用EBP。
看一个简单的例子,我用Win32Dasm从MP3 CD Maker 获取了下面的代码:
beginning of the call
:004301A1 push ebp                    ; 储存EBP
:004301A2 mov ebp, esp                ; 现在EBP指向栈顶, 你不必关心push和pop, 因为你对参数进行EBP相对寻址
:004301A4 mov eax, dword ptr [ebp+08] ; 等价于 pop eax, pop eax 不过丢掉了返回地址 ;(
:004301A7 push esi                    ; 存储下一个值 

. call 内容并不重要.
. 离开 call
:004301FF pop esi                     ; 再次存贮esi
:00430200 pop ebp                     ;再次存储初始基指针
:00430201 ret                         ; 现在返回地址位于栈顶 

提示:你可能注意到象 mov eax , [ebp+10]之类的东西,现在你可以命名[ebp+10]变量,因为在这个call内它总是指向同一个值。比如,在带名字和序列号的保护Call中,可以说[ebp+10]是名字,[ebp+14]是序列号,这大大增加了程序的易读性。
有了这些知识,让我们看看用Win32DASM获取的代码的零碎知识。 

零星知识 

I) 

第一个例子是call GetWindowText.要了解它,先让我们看看MSDN是怎么表述的:
int GetWindowText(
HWND hWnd, // 窗口或文本控制句柄
LPTSTR lpString, // 文本缓冲地址
int nMaxCount // 要复制的最大字符数
);
如果成功,返回值是被复制字符串的长度,不包括末端的null字符。(失败,返回值为0)
返回值存在EAX中,那么,那些参数(hWnd, lpString, nMaxCount)怎么样呢?我们看看这些代码吧:
:00445273 push 00000020 ; nMaxXount
:00445275 push eax      ; 字符串地址
:00445276 push edi      ; hWnd 

* Reference To: USER32.GetWindowTextA, Ord:015Eh
          |
:00445277 Call dword ptr [0044C430]
看看数值是怎样入栈的?所以啦,当程序取得了序列号,你又想知道它存在哪,你只需在第二个push上下断,读取eax中的地址--这就行了。 

II) 

第二个例子是MP3CD Maker 中一个将字母变大写的函数,看看call是否可能与保护有联系;通过搞清楚什么在call前面入栈,得知参数是什么;另外,通过寻找哪个参数被call所改变来查看call的结果及它是否有返回值。
:00421539 lea  eax, dword ptr [ebp-74] ; eax 保存名字地址
:0042153C push eax                     ; 地址入栈并传给call
:0042153D call 0043BB82                ; 仅一个参数
:00421542 add esp, 00000004            ; 堆栈进行调整 <-------
                                                              |
call的内容:                                                   |
:0042BF40 push ebp     ; 使用EBP相对模式                      |
:0042BF41 mov ebp, esp ; 现在你应该知道吧                     |
:0042BF43 push edi                                            |
:0042BF44 push esi     ; edi和esi入栈                         |
-命名变量:                                                    |
:0043BB96 mov eax, dword ptr [ebp+08] ; 你可以命名ebp+08为regName,因为它存有注册名的值,以将它改为大写
-离开call                                                     |
:0042C09C pop edi      ; 然而只有edi被弹出                    |
:0042C09D leave        ; 所以这里看来有错误                   |
:0042C09E ret          ; 这里要对----------------------------- 

现在咱们甚至可以将这个call叫作"void RegNameToUpCase(char* Name)"
现在你应当读懂更多的汇编指令了,因为call之间许多命令只不过是为下一个call要用到的参数作准备。 

结束语 

自己找一些call及它用到的堆栈,自己去试试,不要见时间浪费在找注册码上,在getwindowtexta前面的参数上设断,能将一个call的参数搞明白也会使你能为call“命名”,也使反汇编的代码更加易读。
阿拉欢迎有关这篇文章的评论和批评,写mail给我Ignate或访问stoicForce. 

敬启
Ignate---让破解使人更享受。 

译者的话 

这简直是一个误会!
假期刚完,回来后狐朋狗友们免不了相互请吃,我酒量不行,每次都有些醉意(这里的人特能劝酒),尽管大家都保护。实在太高兴了。
前些天,半杯张裕白兰地(这酒后劲特大)被他们灌下后,我已经飘飘然,快成仙了。回家在网上看到了这篇文章,感觉不错,带着酒意翻译起来(断断续续的可能有两天,饭局挺频的),本来没打算贴出去,因为我对有些地方吃不准。当时很困,没完成就睡了。醒来以后已经是晚上,酒精的威力还没完全退,上了一会网就下了,又译了一会儿。晚上再次醒来看伊拉克战争,没甚么新进展,上网吧。一看有个叫auger的家伙已经把文章的前半段贴出去了,至于为什么没全贴出去我就不知道了(:-<)。更要命的是几个哥们还挺捧场,fly甚至还加了精华,哎呀,我那个高兴啊,受不了了.........!!!!!!!!!!。
这事想起来挺滑稽。
话说回来,这篇文章可能有些地方不一定完全准确,还请各位指正,以免误人,谢谢了。
无影者,2004-07-23 02:33:42

不错
luyiping5488,2004-08-25 19:44:28

是个好东西,你辛苦啦,

 97 1 8: 此主题共有4帖 此页4帖 每页15帖 

[查看完整模式]

Page created in 0.0000 seconds width 2 queries.