一、引出堆栈
-
如果我们要临时存储一些数据,而且数量很少的话,可以将数据放到寄存器中
-
但是当数据数量很大时,我们需要存到内存中,那么现在我们想要一种内存,可以记录存了多少数据,并且能够非常快的找到数据,于是我们可以这样设计内存。用两个寄存器分别存储栈底和栈顶的内存地址,存数据的时候,TOP值减4,释放数据时TOP值加4,如果想要读取中间的某个数值时可以通过TOP或者BASE加上偏移量的方式去读取
-
windows操作系统中的堆栈都是从高地址往低地址存入数据,所以从栈底到栈顶的地址依次从大到小
-
像这种内存的读写方式我们称为堆栈
-
堆栈的优点:临时存储大量的数据,便于查找
二、堆栈操作原理
下述过程就是汇编语言提供push和pop的变形,即用其他指令来达到和push和pop一样的功能,这样做对程序来说相对更加安全,让逆向的人花时间去读代码,如果直接用push或者pop,就很容易知道在干什么
1.入栈
-
从栈顶压入数据,即往低内存地址存入数据,存入后栈顶的地址编号-4,按照这个原理我们可以写汇编代码来模拟此过程。我们规定esp中的值为栈顶地址;ebp中的值为站低地址
-
方式一:先将数据入栈,栈顶地址编号再-4
mov dword ptr ss:[esp-4],0x12345678 lea esp,dword ptr ss:[esp-4]
-
方式二:先将栈顶地址编号-4,再把数据存到栈顶指针所指的内存块中
lea esp,dword ptr ss:[esp-4] mov dword ptr ss:[esp-4],0x12345678
-
方式三:使用sub来达到栈顶指针-4的效果,还是先存数据再移指针
mov dword ptr ss:[esp-4],0x12345678 sub esp,4
-
方式四:使用sub来达到栈顶指针-4的效果,还是先移指针再存数据
sub esp,4 mov dword ptr ss:[esp],0x12345678
-
2.查找
-
当我们想在堆栈中查找某个地址中数据时,可以通过栈底减偏移量或者栈顶加偏移量来得到数据所在地址
-
方式一:栈底减偏移量
mov esi,dword ptr ss:[ebp-4] #读第一个压入栈的数据(结果存入esi中) mov esi,dword ptr ss:[ebp-0x10] #读取第四个压入栈的数据
-
方式二:栈顶加偏移量
mov esi,dword ptr ss:[esp+4] #读倒数第二个压入栈的数据 mov esi,dword ptr ss:[esp+8] #读倒数第三个压入栈的数据
-
3.出栈
-
出栈表示将一个或多个数据从栈中移出去,比如我想将栈顶中的值出栈,则最终栈顶的地址应该+4,即栈顶指针下移,但是出栈的那个值还是在内存中,不是说数据出栈就是将这个数据移出内存中,出栈只是此时表示栈的内存结构中没有存储此数据了,但是它还在内存中。下图所示
-
方式一:先将数据出栈存入寄存器中待使用,再将栈顶指针下移
mov eax,dword ptr ss:[esp] lea esp,dword ptr ss:[esp+4]
-
方式二:向将栈顶指针下移,再将数据出栈存入寄存器中待使用
lea esp,dword ptr ss:[esp+4] mov eax,dword ptr ss:[esp-4]
-
三、误区易错
-
注意存入和读取的数据到底是寄存器中的还是内存中的。
-
mov ebx,dword ptr ss:[esp]
这命令的含义是根据esp寄存器中存的内存地址编号找到此内存地址编号对应的内存中的值!再将这个值存到ebx寄存器中 -
lea eax,dword ptr ss:[esp]
这命令的含义是直接将esp寄存器中存的内存地址编号存到eax寄存器中 -
mov dword ptr ss:[esp],ebx
这命令的含义是将ebx寄存器中存的值(内存地址编号),存到esp寄存器中存的内存地址编号对应的内存中
综上所述有没有一种感觉:dword ptr ss:[esp]类似于这种形式的命令,就是跟内存有关系的,一定最后是要定位到内存地址编号或者内存地址编号对应的内存中的值!
mov eax,ebx
或者mov eax,0x12345678
这种命令都是直接把寄存器中存的值或者立即数直接存入前面的寄存器中,跟内存没有什么关系。
-
-
所以你究竟是想读取寄存器中的值还是内存中的值;究竟是想存入寄存器中还是内存中;究竟是想要取寄存器中的地址编号还是寄存器中地址编号对应的内存中的值!一定要考虑清楚!!!!
四、入栈出栈汇编语言
1.注意点
- 不是每次push栈顶指针都是-4,也不是每次pop栈顶指针都是+4;
- push
- push如果跟的是32位的寄存器,或者取32位内存(dword),或者任何一个立即数,则每次push完确实栈顶指针是减4
- 但是如果push后面跟的是16位寄存器,或者取16位内存(word),则每次push完栈顶指针会减2
- pop
- 如果pop后面跟的是32位寄存器,或者32位内存(dword),则每此pop完栈顶指针会加4
- 但是如果pop后面跟的是16位寄存器,或者16位内存(word),则每次pop完栈顶指针会加2
- push和pop后面不能跟8位的容器(寄存器或内存)
2.入栈
-
push指令格式
PUSH r32 PUSH r16 PUSH m16 PUSH m32 PUSH imm8/imm16/imm32 #举例: push eax push ax push word ptr ss:[esp] #会从低位开始往高位取16位,即四个八进制,剩下的就不取了 push dword ptr ds:[edx] push 0x12/0x1234/0x12345678/0x1 #这些数都会被当成32位立即数
-
pushad:将八个32位通用寄存器中存的值,存入堆栈中,按照顺序存:eax中的值先入栈,接着是ecx,edx,ebx,esp,ebp,esi,edi。起到一个记录现场的作用。接着你就可以对寄存器做任意操作,最后反正能还原
pushad
3.出栈
-
pop指令格式
POP r32 POP r16 POP m16 POP m32 #举例: pop esi pop si pop word ptr ss:[esp] #注意一定不能写成sp,虽然sp是16位寄存器,但是ss:[]格式不允许16位,只允许32位寄存器,至于取多少内存宽度,取决的前面的word还是dword还是byte! pop dword ptr ss:[eax]
-
popad:将八个32位通用寄存器中的值还原成最初pushad存入堆栈中的八个32位通用寄存器的值。起到一个还原现场的作用。所以综上,pushad加popad就起到一个保护现场的作用
四、作业
1.堆栈读写数据
-
使用EBX存储栈底地址,EDX存储栈顶地址,连续存储5个不同的数
mov ebx,0x0019ff40 #栈底 mov edx,0x0019ff30 #栈顶 mov dword ptr ds:[edx-4],0x11111111 ------------------------------------- mov dword ptr ds:[edx-8],esi #004010F0 ------------------------------------- mov eax,dword ptr ss:[esp] #BBBBBBBB mov dword ptr ds:[edx-0xc],eax ------------------------------------- mov eax,dword ptr ss:[esp+8] #75b510f0 mov dword ptr ds:[edx-0x10],eax ------------------------------------- mov dword ptr ds:[edx-0x14],0xFFDC ------------------------------------- lea edx,dword ptr ds:[edx-0x14]
-
未添加数据前,栈结构如下图所示
-
连续添加五个数据后,栈结构如下图,栈顶地址变为0x0019ff1c
-
-
分别使用栈底加偏移、栈顶加偏移的方式读取这5个数,并存储到寄存器中
-
栈底加偏移
mov eax,dword ptr ds:[ebx-0x14] mov eax,dword ptr ds:[ebx-0x18] mov eax,dword ptr ds:[ebx-0x1c] mov eax,dword ptr ds:[ebx-0x20] mov eax,dword ptr ds:[ebx-0x24]
-
栈顶加偏移
mov eax,dword ptr ds:[edx] mov eax,dword ptr ds:[edx+4] mov eax,dword ptr ds:[edx+8] mov eax,dword ptr ds:[edx+0xc] mov eax,dword ptr ds:[edx+0x10]
-
-
弹出这5个数,恢复栈顶到原来的位置
-
变形的方式弹出
mov eax,dword ptr ds:[edx] mov eax,dword ptr ds:[edx+4] mov eax,dword ptr ds:[edx+8] mov eax,dword ptr ds:[edx+0xc] mov eax,dword ptr ds:[edx+0x10] --------------------------------- lea edx,dword ptr ds:[edx+0x14] #edx:0x0019ff30
-
-
使用2种方式实现:push ecx
-
方式一:
mov dword ptr ds:[esp-4],ecx lea esp,dword ptr ds:[esp-4]
-
方式二:
lea esp,dword ptr ds:[esp-4] mov dword ptr ds:[esp],ecx
-
-
使用2种方式实现:pop ecx
-
方式一:
mov ecx,dword ptr ds:[esp] lea esp,dword ptr ds:[esp+4]
-
方式二:
lea esp,dword ptr ds:[esp+4] mov ecx,dword ptr ds:[esp-4]
-
-
使用2种方式实现:push esp
将esp寄存器中存的地址编号取出来入栈
-
方式一:
mov dword ptr ss:[esp-4],esp lea esp,dword ptr ss:[esp-4]
-
方式二:
mov eax,esp #直接获取esp中的值 lea esp,dword ptr ss:[esp-4] mov dword ptr ss:[esp],eax
-
-
使用2种方式实现:pop esp
将栈顶地址对应的内存的值出栈,存入到esp中,则esp中的值作为新的栈顶地址。总结:实现栈顶的跳转
-
方式一:
mov esp,dword ptr ss:[esp]
-
方式二:
lea esp,dword ptr ss:[esp+4] mov esp,dword ptr ss:[esp-4]
-