一、逻辑运算
-
逻辑运算:与(and)、或(or)、亦或(xor)、非(not)
-
计算机除了数据,还要做运算,二进制之间会进行逻辑运算,计算机不会加减乘除,这些运算计算机都是通过移位或者通过逻辑运算实现的。因为计算机只认识二进制数,所以所有的加减等运算都是通过CPU对二进制数据做逻辑运算实现的
-
比如我现在要让计算机计算2+3,计算机CPU是如何运算出结果的呢
x:0010 y:0011 0010 xor 0011 ------------ 0001 R:0001 0010 and 0011 ------------ 0010 <<1 得 0100 #<<1表示左移一位 此时计算机会判断0100是否全为0,如果全为0表示运算结束,将R中此时的值作为结果输出,如果不为0则将R中的值赋给x。0100赋给y再做一次同样运算 0001 xor 0100 ------------- 0101 R:0101 0001 and 0100 ------------- 0000 <<1 得 0000 判断0000全为0,则运算结束,将R中的值0101作为运算的结果,化成十进制即为5
-
逻辑运算还和通信过程对数据加密解密有些关系,虽然现在有很多的加解密算法,但是目前有些人还在用亦或运算加密解密
二、寄存器和内存
-
程序在计算的时候,需要用到数据,那么这些数据都需要地方来存储,寄存器和内存就是用来存储数据的容器
-
寄存器位与CPU内部,内存多指内存条中的内存
-
CPU中的寄存器读取存取数据速度快但是昂贵,内存速度相对较慢,成本低,容量可以做很大
-
32位通用寄存器及其作用:
什么是32位,就是下面的寄存器可以用32位来存储数据,通俗来说就是一个寄存器可以用32个0或者1来存数据。虽然当初设计时不同的寄存器建议存什么东西做什么运算,但是实际上自己想怎么用寄存器就怎么用,知道有哪些寄存器即可
- EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI
-
寄存器的使用:
MOV EAX,12345678 #MOV是汇编指令,EAX是寄存器,后面的数叫立即数,此指令的作用就是将立即数存在EAX中 ADD EAX,1 #将1与EAX中已经存储的数做加法运算,结果再存储在EAX中 MOV ECX,2 ADD EAX,ECX #将EAX与ECX中的值相加,将结果再放入EAX中 SUB EAX,3 #用EAX中的值-3,再将结果放入EAX中
-
但是如果要运算一亿个数,CPU寄存器中只有8个32位通用寄存器,所以此时需要内存帮忙
-
计算机的32位与64位是什么意思?
- 我们要知道,内存划分为很多的内存单位来存储数据,每个内存单位的宽度是8,即一个内存单位中可以存放8个0和1,即一个字节大小。此时每个内存单元都可以有一个编号称为地址,比如0x00000000或者0xFFFFFFFF,当计算机想要从内存中读取或写入数据时,首先应该找到要读、写的位置,即地址。那么32位就表示,计算机在内存中的寻址宽度不能超过32位,可以找到0,001,11111等,但是不能超过32个1。比如我现在找一个地址为33个1的,32位计算机无法做到(但是不是说32位计算机最大的寄存器宽度为32位!!有很多寄存器都是大于32位的,之所以叫32位是CPU的最大寻址宽度不超过32bit)。所以32位计算机表示计算机的最大的寻址宽度为32个1,十六进制即为八个F(0xFFFFFFFF)。那么也就是说32位计算机能识别的地址从0x00000000到0xFFFFFFFF,一共有多少个地址呢?用十进制表示:4294967295+1=4294967285,我们前面说过内存单位大小为1字节,那么32位计算机的内存最多可以存放4294967285*1字节,除以1024换算成K,再除以1024换算成M,再除以1024换算成G为4294967285/1024/1024=4,所以32位计算机能识别的最大内存为4G。但是操作系统有一些固定的组件已经占用了一部分地址了,所以电脑上显示的内存肯定小于4G
三、寻址方式
1.寻址方式一[立即数]
-
直接通过[立即数]即上面提到的地址,来寻址
-
应用如下:
-
读取内存的值
MOV EAX,DWORD PTR DS:[0x13FFC4]
MOV就是将DWORD PTR DS:[0x13FFC4]这个值放到32位EAX寄存器中;DWORD表示读32位的数字,可以省略但不建议;PTR是固定写法;DS是段寄存器,32位汇编中段寄存器和内存的属性有关系,如果后面地址直接用数表示,则用DS,如果后面写ESP或者EBP,就写SS,如果EDI的话就用ES。所以32位汇编中段寄存器就是后面内存属性的一个标识。所以整条指令的作用就是用立即寻址的方式将内存中0x13FFC4地址中存储的值读出来放到EAX里
-
向内存中写入数据
MOV DWORD PTR DS:[0x13FFC4],ebx
将ebx寄存器中的值读出来存到编号为0x13FFC4的内存中
-
获取内存编号
LEA EAX,DWORD PTR DS:[0x13FFC4]
这一条指令表示将内存中0x13FFC4这个地址编号放入到EAX寄存中
2.寻址方式二[reg]
-
通过[reg]来寻址,reg可以是8个通用寄存器中的任意一个
-
读取内存的值
MOV ECX,0x13FFD0 #将0x13FFD0这个地址编号,放到到ECX寄存中 MOV EAX,DWORD PTR DS:[ECX] #将ECX寄存中存储的0x13FFD0这个地址中的值取出来放到EAX里
-
向内存中写入数据
MOV EDX,0x13FFD8 #将0x13FFD8存入EDX中 MOV DWORD PTR DS:[EDX],0x87654321 #将0x87654321存入到以EDX中存的值为地址编号的内存中
-
获取内存编号
LEA EAX,DWORD PTR DS:[EDX]
3.寻址方式三[reg+立即数]
-
通过[reg+立即数]
-
读取内存的值
MOV ECX,0x13FFD0 MOV EAX,DWORD PTR DS:[ECX+4] #将地址编号为0x13FFD0+0x000004中的值取出存到EAX中
-
向内存中写入数据
MOV EDX,0x13FFD0 MOV DWORD PTR DS:[EDX+0xC],0x87654321
-
获取内存编号
LEA EAX,DWORD PTR DS:[EDX+4]
4.寻址方式四[reg+reg*{1,2,4,8}]
-
通过[reg+reg*1或者2或者4或者8]来寻址,只能是1,2,4,8,后面学硬编码就知道了
-
读取内存的值
MOV EAX,0x13FFC4 MOV ECX,2 MOV EDX,DWORD PTR DS:[EAX+ECX*4] #将0x13FFC4+0x8后的地址编号中的值取出来存到EDX
-
向内存中写入数据
MOV EAX,0x13FFC4 MOV ECX,2 MOV DWORD PTR DS:[EAX+ECX*4],87654321 #将87654321这个值写入到0x13FFC4+0x8这个内存中
-
获取内存编号
LEA EAX,DWORD PTR DS:[EAX,ECX*4] #只是将EAX,ECX*4这个地址编号存入到EAX中
5.寻址方式五[reg+reg*{1,2,4,8}+立即数]
-
通过[reg + reg*1或者2或者4或者8 + 立即数 ]
-
读取内存的值
MOV EAX,0x13FFC4 MOV ECX,2 MOV EDX,DWORD PTR DS:[EAX+ECX*4+4]
-
向内存中写入数据
MOV EAX,0x13FFC4 MOV ECX,2 MOV DWORD PTR DS:[EAX+ECX*4+4],87654321
-
获取内存编号
LEA EAX,DWORD PTR DS:[EAX+ECX*4+2]
四、堆栈
-
堆栈的本质就是内存
-
栈涉及到最重要的寄存器是ESP和EBP,ESP存放栈顶的地址编号,EBP存放栈底的地址编号
-
栈的本质是内存,而内存一个单元可以存4字节,即栈的一个存储单元也就是存4个字节,所以相邻的栈单元地址编号之间相差4,所以我们可以看到工具中显示的堆栈的地址之间相差4
-
栈是是用来存储临时变量、函数传递的中间结果
-
堆栈是操作系统维护的,不需要正向程序员来管理维护,但是逆向需要花时间去理解堆栈
-
常用入栈出栈汇编指令
#入栈push push 0x123456 push eax #将EAX寄存器中的值取出来压入栈顶上面一个存储单元,栈顶指针地址编号减小4。因为栈从栈顶到栈底的地址编号逐渐减小 push ebx #出栈pop pop eax #将栈顶指针指向的地址中的值取出来放入到EAX寄存器中,栈顶指向下移一个,栈顶指针地址编号加4 pop ebx
-
但是搞安全的人员会重点关注push,pop语句,所以我们可以用另外一种命令变相代替push和pop命令,执行push和pop的功能
-
比如现在我不想直接使用PUSH EAX,而用下面的命令代替:
LEA ESP,DWORD PTR SS[ESP-4] #将ESP中存的栈顶地址-4的结果存入ESP,即将栈顶指针上移一个单元 MOV DWORD PTR SS[ESP],EAX #将EAX中的值存入到ESP里存的上移后的栈顶地址中 或者反过来写 MOV DWORD PTR SS[ESP-4],EAX #先将EAX中的值存入到栈顶地址-4的地址所指的空间中 LEA ESP,DWORD PTR SS[ESP-4] #再将栈顶地址-4,存入到ESP中,则此时栈顶指针上移了一个单元
-
-
修改栈中内容的变形思想:
MOV EAX,88888888 LEA EDI,DWORD PTR SS:[ESP] #ESP的地址即为栈顶的地址,将栈顶地址存入EDI寄存器中 MOV ECX,2 #ECX中的值决定下面的REP指令执行多少次 REP STOS DWORD PTR ES:[EDI] #REP表示重复执行后面的指令,STOS表示将EAX中的值存入EDI里存的地址中 #因为此时EDI的地址即为栈顶地址,所以会将栈顶的地址中的值改为88888888;而REP重复了两边,默认情况下将EDI地址值+4,再将88888888存入[EDI+4]这个地址中,即覆盖栈顶的下面一个。 #将DF标志位从0修改为1,则覆盖小地址,则向上覆盖!即栈顶的上面一个空间[EDI-4]
五、课后作业
1.Ollydebug的使用
-
打开可执行文件:直接拖入可执行文件或者file–open–可执行文件
-
设置断点:选中某行按F2
-
跳转到某地址:CTRL+G–>输入地址
-
从断点处单步调试:F8
-
进入函数调试:F7(进入到函数中)
-
页面布局:
-
插件显示的命令的窗口的使用:
db 0x0019ff74 dd 0x0019ff74
d表示数据,b表示byte,后面的0x0019ff74表示内存地址。整命令的意思为为以一个字节为单位显示0x0019ff74地址对应内存块中的值,如果这个地址为栈中的地址,因为堆栈中用0x0019ff74可以表示0x0019ff74到0x0019ff77这四个内存块,所以一个0x0019ff74可以存4字节值,但是其实还是由4块内存存的,此时用此命令db 0x0019ff74显示如下。数据窗口中的数据是反的,从低位往高位存,但是堆栈窗口中的数据是从高位往低位存
dd 0x0019ff74显示如下:
2.指令执行时的堆栈情况
-
未操作前栈结构如下:
-
push ebp
:ebp中存的是栈底的地址,所以将栈底地址入栈,即在0x0019ff74-4=0x0019ff7地址处存入。且栈顶地址也-4,新栈顶地址为0x0019ff70 -
mov ebp,esp
:将esp中存放的栈顶地址存入ebp中,那么会覆盖栈底地址,此时栈顶栈底地址相同 -
sub esp,40
:将esp中的值+0x40,即0x0019ff70-0x40=0x0019ff30,再将结果存入esp中,故此时栈顶地址变为0x0019ff30,栈底地址还为0x0019ff70 -
push ebx
:将ebx中存的值–栈底地址入栈,即将0x0019ff70存入esp-4=0x0019ff2c地址中,且栈顶地址变为0x0019ff2c -
push esi
:将esi中的值0x00401000入栈,esp中的值变为0x0019ff2c-4=0x0019ff28 -
push edi
:将edi中的值0x00401000入栈,栈顶 - 4 -
lea edi,dword ptr ss:[ebp-40]
:将0x0019ff70-0x40=0019ff30这个地址值存到edi中 -
mov ecx,10
:0x10存入ecx中 -
mov eax,0xcccccccc
:同理,如果eax中已经有值则覆盖 -
rep stos dword ptr es:[edi]
:重复执行后面的命令ecx中的值次,即0x10(十进制为16)次,后面的命令时将eax中的值存入edi寄存器里的地址中,因为前面有这条lea edi,dword ptr ss:[ebp-40]
指令,edi中此时为0x0019ff30,刚好属于栈中的存储单元地址,则第一次执行此语句表示将0xcccccccc这个值存到栈中的0x0019ff30这个地址中,并且将ecx中的值减1,而第二次执行时,由于是默认情况DF位为0,所以是向下存入栈中,则edi中的值会加0x4,即再将0xcccccccc存入0x0019ff30 + 0x4 = 0x0019ff34地址中,ecx中的值再减1。以此类推,向下存16次,直到ecx中的值减为0。所以最后edi中的值为0x0019ff30 + 0x4 * 0x10(十进制为16)= 0x0019ff70。 -
mov eax,dword ptr ss:[ebp+8]:把ebp中存的栈底地址编号+0x8=0x0019ff70+0x8=0x0019ff78中的值取出来,存入eax寄存器中
发现没:涉及到存栈顶或者栈底或者栈的地址的信息如果地址要变也只能是加4的倍数或者减4的倍数,因为栈的一个存储单元为4个字节宽度
-
add eax,dword ptr ss:[ebp+c]:把ebp中存的地址值+0xc=0x0019ff70+0xc的地址中的值取出来,存入eax中
-
pop edi
:将栈顶地址中的值取出来存入edi,并且栈顶地址加4,即容量减小一个单元 -
pop esi
:将栈顶地址中的值取出来存入esi,并且栈顶地址加4 -
pop ebx
:同理 -
mov esp,ebp
:将ebp中存的值取出存入esp中,即栈顶地址为0x0019ff70,又是栈顶和栈底地址相同 -
pop ebp
:将栈顶地址中的值取出存入ebp,并且将栈顶地址+4,即减小一个存储单元(不要忘了栈顶指针下移) -
retn
:retn表示pop eip,然后跳转到eip中的地址指向的指令准备开始执行现在跳转到此指令,如果再按F8执行,则会执行
push eax
这条语句
3.思考:push esp可以写成哪种方式?
-
#因为esp中的存放的是栈顶的地址,push esp就是将esp寄存器中的值取出来入栈,再将栈顶地址减4。所以如果使用变形的写法,可以先将esp中的值即栈顶的地址取出来存入到一个寄存器中比如eax,然后再将将eax中的值存到栈顶地址减4的地址所指空间中,最后将栈顶地址变为栈顶地址减4,即可 ------------------------------------ lea eax,dword ptr ss:[esp] mov dword ptr ss:[esp-4],eax lea esp,dword ptr ss:[esp-4]
-
最开始可以看到esp中的值为0019ff74,表示栈顶地址;ebp中的值为0019ff80,表示栈底地址。那么就可以在右下方的栈区域中找到栈的开始与结束位置
-
当我们执行
push esp
指令时发现在栈顶的上一个存储单元的地址中存入了开始esp存储器中存的栈顶地址0019ff74,且现在栈顶的地址变为了0019ff70(esp-4) -
变形的思想用其他的指令来代替这个
push esp
命令,如下图,也可以达到同样的效果