(1)PUSH指令
—向堆栈中压入数据
—修改栈顶指针ESP寄存器(根据压入数据的位数修改ESP,默认ESP减小)
—当前ESP
—push之后(相当于ADD ESP,0C)
—发现压入了堆栈中
—ESP改变
#问题:需要ECX做计数器,但是ECX里面有值
—思路:将原ECX的值放入堆栈
—将ESI的连续10个堆栈[19FF74,19FF98]放入EDI[19FE00,19FE24]
—执行后ESI/EDI的值+4
—直接丢弃栈顶指针:add ESP,4
—丢弃栈顶指针元素,并且将元素赋值给EAX:pop EAX
—这两行代码可以被代替:pop ECX
(2)POP指令
—将栈顶元素存储到寄存器/内存
—删除栈顶元素
#修改EIP(存储CPU即将读取指令的地址)
—EIP的值是不能直接用MOV指令修改
—EIP是反汇编窗口的执行地址,注意:反汇编的窗口地址是根据指令来确定大小,如401120地址中55占1byte,所以下一行位401121;但是401121的指令占2byte,所以下一行的地址位401123
(3)JMP指令(JMP,r/m/imm32)
—修改EIP的指令,相当于mov eip,1
—执行后发现EIP跳转
—注意:EIP的值位CPU即将执行,但是还没有执行的指令
(4)CALL指令(敏感指令)
—PUSH下一行指令(ESP改变)
—MOV EIP,r/m/imm
—和JMP的区别:在堆栈中存储call指令的下一行地址
—在查看call指令效果时,按F7:单步步入
—即:在执行函数时,会查看函数中的每一个步骤,如果是F8就是直接执行完函数
—F7发现:CPU命令跳转到401135(EIP=401135)
—ESP-4,并将call指令的下一行压入的栈顶
—再按F7 ,发现EIP到下一行
—堆栈-4:执行了401135的PUSH EAX指令
—发现栈顶的值==EAX
#call指令总结:
—位EIP进行赋值
—将call指令的下一行的地址压入栈顶
—ESP的值(栈顶元素的指针)+/-4(根据DF来判断加减)
(5)RET指令(和call指令配合)
—我们调用函数的执行完了之后,需要返回调用指令的下一行(相当于call指令是调用函数),然后执行call指令的下一行。
—但是单纯的call指令执行后,EIP的值并没有返回(下一行存储在栈顶),因此,我们需要将栈顶的原EIP值拿出来重新赋值给EIP
—然后将栈顶的原EIP的值释放,堆栈恢复到调用函数之前的样子
—这里调用call指令,单步F7,将call指令下一行压入栈顶
—EIP为调用函数的地址,ESP为栈顶指针
—按F7:EIP变为call指令下一行
—ESP为原ESP+4
#RET总结
—EIP的值变为原栈顶的数据(call指令下一行)
—ESP的值+4,栈顶指针下移动
—相当于:JMP esp;add esp,4
十五:反调试之Fake F8
#断点
—进入exe程序,如果直接点三角符号,程序直接执行
—如果使用F7/F8就是一行一行的执行
—如果我想在401142下一个断点,按F2(再按F2取消断点)
—则直接按三角符号就会停留在断点处
—下断点之后,相当于int 3指令
—int 3指令对应的值:CD 03
—用其它的工具打开程序以十六进制显示,发现是cc
—cc就是int 3指令的十六进制显示
—单步步入和单步步过
#单步步入(F7)
—单步步入的实现依赖于单步异常。
—当我们需要观察每一行代码(包括函数内部的代码)执行之后寄存器与内存的变化,通常会采用单步步入。
—当使用单步步入时,可采用在下一行代码的首字节设置INT 3断点的方式实现。
—CPU为我们提供了一种更为方便的方法,即使用陷阱标志位(TF位),将TF=1,单步产生的异常与硬件断点产生的异常一致,都是STATUS_SINGLE_STEP(单步异常)
—这也是我们按F7,程序会一行一行的执行的原因
#单步步过(F8)
—当遇到CALL指令时,若无需进入函数内部进行调试,可以使用单步步过。
—与单步步入不同的是,单步步过的实现依赖于软件断点或硬件断点。
—判断当前指令是否为CALL指令;若不是CALL指令,设置TF为1触发单步异常(F8直接在call指令的下一行加1个cc)
—若是CALL指令,判断OPCODE是E8还是FF15
—若OPCODE是E8,在当前地址之后的第5个字节设置软件断点(E8指令占5个字节)
—若OPCODE是FF15,在当前地址之后的第6个字节设置软件断点(FF15指令占6个字节)
#让F8不能执行(废除F8,达到反调试的目的)
—call指令会让401125存储入栈顶,然后EIP=401140
—call执行完之后,会将栈顶元素赋值给EIP,然后删除栈顶元素
—通过给栈顶赋值,call执行完后,栈顶(EIP)的值为新的地址
—retn给EIP从新赋值
—按F7,发现跳转到401118,而不是call的下一行
—按F8,发现直接不行了(因为,F8是直接在call指令的下一行下断点,但是ESP已经改变不在call的下一行)
#反调试的思路
—多个call,多个指向来迷惑对方
【滴水基础】1.汇编语言(下)
十六:汇编眼中的函数
#什么是函数:函数是一系列指令的集合
#如何执行一个函数
(1)用JMP执行:jmp 401120
—缺点:jmp执行完就回不去
—解决方法:再执行一次JMP
(2)用call指令执行:call 401120
—需要配合retn
—call指令流程
#什么是参数?什么是返回值?
—如编写一个函数,得到任意2个整数的值
—将函数的参数传递给ecx和edx(使用寄存器传参最多传递8个参数)
—调用函数的结果存储在EAX
—先对寄存器进行传参,然后用call指令调用函数
—call指令使得40112F存储到栈顶,ESP-4
—retn:将栈顶赋值给EIP,然后删除栈顶(ESP+4)
—EAX、ECX均为3
十七:堆栈传参
—如果参数较多,8个寄存器远远不够,因此需要利用堆栈进行传参
—例如:5个参数相加,通过PUSH传入5个参数
—push会导致堆栈压入元素
—调用的函数部分,利用ADD将分别将堆栈元素添加进EAX
—执行,发现EAX存储函数相加的值
—虽然RETN将栈顶ESP释放
—但是堆栈中ESP由于5个PUSH,ESP-10,和调用函数前并不一致
十八:堆栈平衡
—-平衡函数中的堆栈变化
—平衡堆栈传参时的堆栈变化
—如这里在调用函数中,存在PUSH,即堆栈操作
—这会导致栈顶ESP不在是call的下一行,进而retn不到call的下一行
#调用函数的操作
(1)所有的函数不涉及堆栈操作
—在传参的时候,不使用堆栈传参(要么不传参,要么使用寄存器传参)
—在执行函数的时候,不进行堆栈操作
(2)平衡堆栈传参(外平衡)
—push传参导致ESP-8
—在call执行后,删除栈顶的2个元素
(2)平衡堆栈传参(内平衡)
—retn 8可以替换:retn ;esp+8
—可以看出ESP回到了调用函数和传参前
十九:外挂是什么?
—按回车就会进入while循环
—while循环内:调用attcak函数
—并且间隔3000ms(3s)调用1次
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
void attack()
{
printf(“******攻击******\n”);
return;
}
int main(int argc,char* argv[])
{
printf(“准备,按回车开始攻击:\n”);
getchar();//获取键盘输入
while(1)
{
attack();
Sleep(3000);//注意Sleep是大写
}
getchar();
return 0;
}
—在编译后,会生成相应的exe文件
—利用DTDBUG打开hello.exe
—如果打开exe里面反汇编界面是灰色的,就删除udd目录下的备份文件
—这里是程序的入口(但是我也不知道咋看出来的)
—这里调用的Sleep函数,0BB8转化为十进制就是3000
—emmm我也看不懂,PUSH指令对应的4010EC
—0B对应4010ED,B8对应4010EE,00对应4010EF,00对应4010F0
—修改攻击的频率:修改4010EC对应的3000ms改为1000ms
—如果会写c,就直接用c代码调用修改
—利用进程注入修改,这里要减少时间,就直接修改0B对应的4010ED
—注意0BB8对应的十进制3000,那么3000对应的程序地址:
—[4010ED, 4010EE],这里应该只需要从4010ED开始就行
—将原来的3000修改为500
—先在DTDBUG中点击蓝色的三角形,运行程序
—然后在命令行界面按回车,发现攻击的频率明显加快
—如果想修改call 40100A(Sleep完了之后就会执行这个函数)
—这个应该是调用Attack的吧
—目的:不用Sleep来调用程序,而是自己调用
—选中要执行的代码,复制过来
—在DTDBUG中点击倒三角形按钮
—在命令行窗口,原来是需要按回车才能开始攻击
—点击注入远程代码,发现可以控制攻击
—这一段就是控制攻击的代码,但是我看不懂
—跳转到401020,执行调用函数的代码
二十:ESP寻址
#函数传参(任意两个整数相加)
—寄存器传参(参数少)
—堆栈传参(参数多,包含ESP/EBP的内存使用ss:[],其它使用ds:[])
—外平衡(在call下一行平衡ESP)
—内平衡(在RETN处平衡ESP)
#总结ESP
—优点:灵活方便;缺点:函数复杂时不好用
—如堆栈传参时,需要先对寄存器的信息进行保留
—POP是先赋值,再删除栈顶(顺序和push的相反)
—这里需要先push三次,再pop三次,且ESP+14的计算很麻烦
二十一:EBP寻址(栈底指针寄存器)
—ESP寻址,需要不停的修正ESP的值,较麻烦
—借助EBP寻址:1.先将EBP原来的值保存2.让EBP指向原来ESP的值3.将ESP进行提升4.通过EBP的浮动来进行数据的查找
—如之前的两个整数相加
—push ebp //保存EBP原来的值,ESP-4
—mov ebp,esp //将ESP的值赋给ebp
—EBP+8:为函数传递的第二个参数
—EBP+C:为函数传递的第一个参数
—传参完成后,mov ESP,EBP //消除ESP的浮动(视频了在堆栈传参之前,sub esp,10:将ESP指针上移了4BYTE的堆栈),所以这里需要将ESP浮动的空间释放掉
—pop ebp //将备份的EBP还原
—retn 8 //内平衡调用函数时的push传参
—总结:引入EBP后,不需要修正ESP
二十一:JCC指令
—32位标志寄存器(EFL)的结构如下
(1)CF(bit 0):carry flag //进位标志(无符号数)
—判断无符号数计算,是否存在溢出
#无符号数的加法
—若计算的结果在最高有效位发生进位/借位则CF=1,否则CF=0
—al是8位寄存器,FE+2就超过最大空间FF,发生溢出
—增加前,C=0
—增加后,AL变为00,CF=1,AF=1
#无符号数的减法
—这里空间不够,需要向前借位
—(-1的原码:1000 0001,反码:1111 1110,补码:1111 1111)
—负数在计算机以补码存储,因此:FE-FF=FF(无符号数变为有符号数)
—执行之后,AL变为FF,CF=1存在借位,SF=1说明为负数
—AF=1:在结果的第三位发生进位/错位
(2)PF(bit 2):parity flag //奇偶标志
—如果计算的结果的最低有效字节(最后1byte)包含偶数个1位则PF=1,否则PF=0(用最后1byte可以减少出错的概率)
—作用:利用PF进行奇偶校验检测
—发生方1100 1110,存在5个1,PF=0
—接收方1100 1110存在奇数个1,发送方PF==接收方PF,传输未出错
#模拟传输,寄存器+0
—执行给寄存器赋值,发现P=1,即最后1byte存在奇数个1
—我发现赋值的时候,PF没有变红,但是执行ADD运算的时候PF变红
—猜测是:只有对原寄存器/堆栈进行四则/位运算等操作,才会使用PF
(3)AF(bit 4):auxiliary flag //辅助进位标志
—若计算结果在第三位发生进位/错位则AF=1,否则AF=0
—在BCD(binary-code decimal)运算中使用
—较少用到(在之前的无符号数的加减进位中AF位改变了)
(4)ZF(bit 6):Zero flag //零标志
—若计算结果为0则ZF=1,否则ZF=0
—经常与CMP/TEST等指令一起使用
#判断2个数是否相等
—CMP相当于SUB,但是结果不保存到第一个操作数
—ZF=1,说明EAX==ECX,且不改变EAX的值
—TEST相当于AND,但是结果不保存到第一个操作数
—AND操作,两个都为1时才为1
—自己和自己and,如果EAX为0,则AND之后为0,则ZF=1
—这里发现计算的结果为0(检验某个数的值是否为0)
(5)SF(bit 7):sign flag //符号标志
—有符号数(整形)的最高有效位,正数SF=0,负数SF=1
—如果是无符号数,SF位的大小是没有意义的。
—7F:0111 1111;7F+2:1000 0001
—计算机是无法识别有符号数/无符号数
—根据SF来对有符号数的最高位来进行判别,这里最高位1则SF=1
—FE:1111 1110,FE+2
—FE+2:已经满了溢出且EAX=00,所以SF=0
(6)OF(bit 11):overflow flag //溢出标志
—反映有符号数的计算结果是否溢出
—7F:0111 1111;7F+2:1000 0001
—CF=0,当成无符号数来看(注意:最高位和8bit位不一样,CF=1是发生堆栈溢出,超过了8bit)
—OF=1,则有符号数发生溢出。7F+2:1000 0001的最高位为1,即发生了溢出(7F+2的反码:1000 0000,7F+2的原码:1111 1111即为-1,这里我有点迷糊了)
(7)DF(bit 10):direction flag //方向标志
—方向控制串指令(MOVS、CMPS、SCAS、LODS、STOS)
—设置DF使得串指令自动递减(从高地址向低地址处理字符串,默认DF=0,即ESI/EDI向栈顶增加)
—清除该标志使得串指令自动递增
—STD(设置DF=1)和CLD(设置DF=0)指令分别用于设置及清除DF标志
—执行后,DF=0,则执行MOVS后,EDI/ESI+4
—这里发现EDI/ESI+4
—总结:
#JCC指令
—JCC指条件跳转指令,CC就是指条件码。
—这里是根据标志寄存器的标志来进行判断
#举例:JZ/JE
—若为0则跳转;若相等则跳转:通过零标志来判断,若ZF=1,则结果为0,发生跳转
#JCC指令总结
—所有的JCC指令,都是根据标志寄存器来修改EIP(EIP决定是否跳转)
—注意:区分有符号数/无符号数
#标志寄存器的总结
—判断是否发生溢出:无符号数CF(存在溢出CF=1),有符号数OF(存在溢出OF=1)
—判断最后1byte的1的个数:PF=1(偶数个1)
—判断计算结果是否为0:ZF(为0则ZF=1)
—判断有符号数是正数/负数:(正数SF=0,负数SF=1)
—判断方向控制指令的(DF=0时ESI/EDI+1/2/4) 作者:沙漠里的鲸 https://www.bilibili.com/read/cv19333800/?spm_id_from=333.999.0.0 出处:bilibili