# reverse
**Repository Path**: fenghuangniepan_yuhuochongsheng/reverse
## Basic Information
- **Project Name**: reverse
- **Description**: 逆向工程学习笔记,相关文档
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-10-21
- **Last Updated**: 2022-12-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
英特尔®64和IA-32架构软件开发人员手册
8086寄存器, 每个寄存器16位
>ax, 累加器(Accumulator)
>bx,
>cx, 计数器(Counter)
>dx, 数据寄存器(Data),除了作为通用寄存器外,还专门用于和外设之间进行数据传送
>si, 源索引寄存器(Source Index)
>di, 目标索引寄存器(Destination Index),用于数据传送操作
>bp, 基址寄存器,栈底指针
>sp 基址寄存器,栈顶指针
>数据段寄存器 ds 存放数据段物理地址的起始地址
>附加段寄存器 es 跟ds差不多
>代码段寄存器 cs 存放代码段物理地址的起始地址
>堆栈段寄存器 ss 只想堆栈的基地址
>ip 代码指令偏移量
32位处理器
>在32位处理器上增加了FS和GS段寄存器,
>32位段寄存器cs,ds,es,ss,fs,gs都是80位的(16位段选择器和64位描述符高速缓存器)
>
>在32位保护模式下,段寄存器称为段选择器,在保护模式下访问一个段时,传送到段选择器的时段选择子,它由三部分组成,第一部分时描述符的索引号,用来在描述符表中选择一个段描述符,TI时描述符表指示器(Table Indicator), TI=0时,表示描述符在GDT中;TI=1时,描述符在LDT中。RPL时请求特权级,表示给出当前选择子的那个程序的特权级别,正是该程序要求访问这个内存段。
>
> cr0寄存器
>它是一个32位的寄存器,包含了一系列用于控制处理器操作模式的运行状态的标志位。它的第0位是保护模式允许位(Protection Enable, PE).是开启保护模式大门的门把手,如果把该位置1则进入保护模式,
保护模式下段寄存器的权限检查
>
文本模式的显示缓冲区 0xb8000
div指令详解:无符号除法运算
除数位数
被除数存放位置
余数存放位置
商存放位置
8
ax
ah
al
16
dx:ax
dx
ax
32
edx:eax
edx
eax
64
rdx:rax
rdx
rax
idiv指令
> 有符号的除法运算
jmp指令 转移指令详解:
> short < near < far
> short 段内短转移 修改ip 范围-128~127 2的8次方
> near 段内近转移 修改IP 范围-32768字节~32767字节 2的16次方
> far 段间转移 修改cs:ip 2的20次方
伪指令 汇编识别
>db--声明字节数据
>dw--声明字数据(两个字节)
>dd--声明双字数据(四个字节)
>dq--声明四字数据(8给字节)
movsb指令, movsw指令传输数据
>原数据由DS:SI指定,要传送的目的地址由ES:DI指定,传送的字节数(movsb)或字数(movsw)由CX指定,正向传送SI和DI加1或加2,反向传送SI,DI减1和减2,每传送一次cx自动减1,传输方向由标志寄存器第10位方向标志DF控制
标志寄存器
>
1
>> zf零标志位:
>>执行一条算数或逻辑运算指令后,算数逻辑部件送出的结果除了送到指令中指定位置外,还会送到或非门,或非门的输入全为0,zf=1,否则zf=0
>>sf符号位:
>>= 运算结果的最高位(二进制)
>>pf奇偶标志位:
>>运算结果的最低8位中1的奇偶个数,偶数个1 pf=1,基数给1 pf = 0
>>cf进位标志位:
>>进行算数运算时,最高位由向前进位或借位的情况发生,cf=1,否则cf=0,cf标志始终记录仅为或借位的发生,但少数指令除外,如inc,dec
>>of溢出标志位:
>>检测运算结果,可以理解位计算机把运算结果先当作无符号数, 然后再把结果容器的当成有符号容器计算它的最小值和最大值, 然后看之前无符号数是否在有符号数的最大最小之间,超出了就算溢出,of=1,否则=0,
>>>例如:mov ah, 0x70
>>> add ah, ah
>>>即112+112=224,这是把结果当成无符号看,符合8位0-255的范围,然后再把ah容器当作有符号看待范围位-128~127,224超出范围,此时of=1
>>af辅助仅为标志位:
>>是看操作数的第四位(下标1开始数),即D3位(下标0开始数),是否产生进位,或需要借位。为真,则标志为1,反之为0
>>DF传输方向标志位:
>>数据传输方向,为0数据将正向传输,低端到高端,为1数据将反向传输,高端到低端
>>IF中断标志位:
>>IF位0时,所有从处理器INTR引脚来的中断信号都被忽略掉,位1时,处理器可以接受和相应中断,可以用指令cli和sti来改变IF位,cli(CLear Interrupt flag)清除IF标志位,sti(SeT Interrupt flag)用于置位IF标志
>>影响标志位的指令
>>
>>
cld指令
> 将标志寄存器第十位DF置为0,数据传输方向将正向传输,低端到高端
std指令
>将标志寄存器第十位DF置为1,数据传输方向将反向传输,高端到低端
rep指令(repeat的缩写)
>单纯的movsb或movsw只会执行一次, 在前面加上req指令它将重复执行直到cx的内容为零
inc指令
> 加1指令 例如:inc ax == ax = ax + 1
dec指令
>减1指令 例如:dec ax == ax = ax - 1
neg指令
> 用0减去指令中指定的操作数,
>例如:neg al
>al的内容位00001000(10进制数8),执行完后AL为11111000(十进制数-8)
cbw指令
>cbw没有操作数,操作码为98。它的功能是,将寄存器AL中的有符号数扩展到整个AX。举个例子,如果AL中的内容为010010111,那么执行该指令后,AX中的内容为000000001111;如果AL中的内容为10001101,执行该指令后,AX中的内容为11111100011010
cwd指令
>cwd也没有操作数,操作码为99。它的功能是,将寄存器AX中的有符号数扩展到DX:AX。
举个例子,如果AX中的内容为01001011101111001, 那么执行该指令后,DX中的内容为0000000000000,AX中的内容不变;如果AX中的内容为1000110110001011,那么执行该指令后,DX中的内容为111111111111,AX中的内容同样不变。
jns指令
>条件转移指令, 如果标志寄存器的SF位为0则执行跳转到后面的标号地址,
js指令
>条件转移指令, 如果标志寄存器的SF位为1则执行跳转到后面的标号地址,
jz指令
>条件转移指令, 如果ZF=1则转移
jnz指令
>条件转移指令, 如果ZF=0则转移
jo指令
>条件转移指令, 如果OF=1则转移
jno指令
>条件转移指令, 如果OF=0则转移
jc指令
>条件转移指令, 如果CF=1则转移
jnc指令
>条件转移指令, 如果CF=0则转移
jp指令
>条件转移指令, 如果PF=1则转移
jnp指令
>条件转移指令, 如果PF=0则转移
cmp指令
> cmp指令在功能上和sub指令相同,唯一不同之处在于,cmp指令仅仅根据计算的结果设置相
应的标志位,而不保留计算结果,因此也就不会改变两个操作数的原有内容。cmp指令将会影响到CF、OF、SF、ZF、AF和PF标志位。
各种比较结果和相应的条件转移指令
>
>
jcxz指令
>当寄存器cx的内容为0则转移
adc指令
>带进位加法指令,类似add,还要加上当前标志寄存器的CF位,加0或加1,影响of, sf,zf,af,cf,pf
call指令
>第一种:16位相对近调用
>>近调用的意思是被调用的目标过程位于当前代码段,而非另一个不同的代码段,所以只需要得到偏移地址即可。
>>16位相对近调用是三字节指令,操作码为0xE8,后跟16位的操作数,因为是相对调用,故该
操作数是当前call指令相对于目标过程的偏移量。计算过程如下:用目标过程的汇编地址减去当前
call指令的汇编地址,再减去当前call 指令以字节为单位的长度(3), 保留16位的结果。举个例子:
call near proc_ 1
近调用的特征是在指令中使用关键字“near”。“proc_ 1”是程序中的-一个标号。在编译阶段,编译器用标号proc_ 1处的汇编地址减去本指令的汇编地址,再减去3,作为机器指令的操作数。
关键字“near” 不是必需的,如果call指令中没有提供任何关键字,则编译器认为该指令是近
调用。因此,上面的指令与这条指令等效: .
call proc_ 1
因为16位相对近调用的操作数是两个汇编地址相减的相对量,所以,如果被调用过程在当前
指令的前方,也就是说,论汇编地址,它比call指令的要大,那么该相对量是一一个正数;反之,就
是一个负数。所以,它的机器指令操作数是一个16位的有符号数。换句话说,被调用过程的首地
址必须位于距离当前call指令-32768~32767字节的地方。
在指令执行阶段,处理器看到操作码0xE8,就知道它应当调用-一个过程。于是,它用指令指
针寄存器IP的当前内容加上指令中的操作数,再加上3,得到-一个新的偏移地址。接着,将IP的
原有内容压入堆栈。最后,用刚才计算出的偏移地址取代IP原有的内容。这直接导致处理器的执
行流转移到目标位置处。
冉看一个例子:
call 0x0500
很多人认为0x0500会原封不动地出现在该指令编译后的机器码中,我相信这只是他们一时糊
涂。在call指令后跟-一个标号,和跟- 一个数值没有什么不同。标号是数值的等价形式,是代表标号
处的汇编地址。在指令编译阶段,它首先会被转化成数值。
所以,你在call指令后跟-一个数值,只是帮了编译器的忙,帮它省了一个转化步骤,它依然会
用这个数值减去当前指令的汇编地址,来得到-一个偏移量。
>
>第二种:16位间接绝对近调用
>>这种调用也是近调用,只能调用当前代码段的过程,指令中的操作数不是偏移量,而是被调用过程的真实偏移地址,故称为绝对地址。不过,这个偏移地址
不是直接出现在指令中,而是由16位的通用寄存器或者16位的内存单元给出。比如:
>>
>>以上,第一条指令的机器码为FF D1,被调用过程的偏移地址位于寄存器CX内,在指令执行的时候由处理器从该寄存器取得,并直接取代指令指针寄存器IP原有的内容。第二条指令的机器码为FF 16 00 30。当这条指令执行时,处理器访问数据段(使用段寄存器DS),从偏移地址0x3000处取得一个字, 作为目标过程的真实偏移地址,并用它取代指令指针寄存器IP原有的内容。后面两条指令没什么好说的,只是寻址方式不同而已。间接绝对近调用指令在执行时,处理器首先按以上的方法计算被调用过程的偏移地址,然后将指令指针寄存器IP的当前值压栈,最后用计算出来的偏移地址取代寄存器IP原有的内容。由于间接绝对近调用的机器指令操作数是16位的绝对地址,因此,它可以调用当前代码段任何位置处的过程。
>
>第三种:16位直接绝对远调用
>>这种调用属于段间调用,即调用另一个代码段内的过程,所以称为远调用(FarCall)。很容易想到,远调用既需要被调用过程所在的段地址,也需要该过程在
段内的偏移地址。
“16位”是针对偏移地址来说的,而不用于限定段地址;“直接”的意思是,段地址和偏移地址
直接在call指令中给出了。当然,这里的地址也是绝对地址。比如:call 0x2000: 0x0030
这条指令编译后的机器码为9A30000020,0x9A是操作码,后面跟着的两个字分别是偏移地
址和段地址,按规定,偏移地址在前,段地址在后。
处理器在执行时,首先将代码段寄存器CS的当前内容压栈,接着再把指令指针寄存器IP的当
前内容压栈。紧接着,用指令中给出的段地址代替CS原有的内容,用指令中给出的偏移地址代替
IP原有的内容。这直接导致处理器从新的位置开始执行。
处理器是没有脑子的。如果被调用过程位于当前代码段内,而你又用这种指令格式来调用它,
那么,处理器也会不折不扣地从当前代码段“转移”到当前代码段。
>
>第四种:间接绝对远调用
>>这也属于段间调用,被调用过程位于另一个代码段内,而且,被调用过程所在的段地址和偏移地址是间接给出的。还有,这里的“16位”同样是用来限定偏移地
址的。下面是这种调用方式的几个例子:
>>
>>间接远调用必须使用关键字“far”, 这-一点务必牢记。
>>因为是远调用,也就是段间调用,所以,必须给出被调用过程的段地址和偏移地址。但是,段
>>地址和偏移地址在内存中的其他位置,指令中仅仅给出的是该位置的偏移地址,需要处理器在执行
>>指令的时候自行按图索骥,找到它们。
>>以上,前两条指令是等效的,不同之处仅仅在于,第一条指令直接给出的是数值,而第二条指
>>令用的是标号。但这无关紧要,在编译后,标号也会变成数值。
>>为了进一步说清间接远调用是怎么发生的,下面是一个实例。
>>假如在数据段内声明了标号proc_ 1并初始化了两个字:
>>proc_ 1 dw 0x0102, 0x2000
>>这两个字分别是某个过程的段地址和偏移地址。按处理器的要求,偏移地址在前,段地址在后。
>>也就是说,0x0102 是偏移地址; 0x2000 是段地址。
>>那么,为了调用该过程,可以在代码段内使用这条指令:
>>call far [proc_ 1]
>>当这条指令执行时,处理器访问由段寄存器DS指向的数据段,从指令中指定的偏移地址处取
>>得两个字(分别是段地址0x2000和偏移地址0x0102);接着,将代码段寄存器CS和指令指针寄存
>>器IP的当前内容分别压栈;最后,用刚才取得的段地址和偏移地址分别取代CS和IP的原值。
>>至于后面的两条指令call far [bx]和call far [bx+si],仅仅是寻址方式上有所区别,指令执行过程
>>大体上是一样的。
ret指令
>近返回指令,从堆栈中弹出一给字到指令指针寄存器IP中
retf指令
>远返回指令,处理器分别从堆栈中弹出两个字到指令指针寄存器IP和代码段寄存器CS中
shr指令
>逻辑右移指令,会将操作数连续的向右移动指定的次数,每移动一次,‘挤’出去的比特被移到标志寄存器CF位,左边空出来的位置用0填充
shl指令
>逻辑左移指令,会将操作数连续的向左移动指定的次数,每移动一次,‘挤’出去的比特被移到标志寄存器CF位,右边空出来的位置用0填充
ror指令
>循环右移指令,每右移动一位,移出的比特位即送到标志寄存器CF位,也送到左边空出的位
rol指令
>循环左移指令,每左移动一位,移出的比特位即送到标志寄存器CF位,也送到右边空出的位
iret指令
>中断返回指令,处理器一次从堆栈中弹出数值到IP,CS和标志寄存器
not指令
>按位取反指令,会将操作数的每一位反转,1=0,0=1
hlt指令
>使处理器停止执行指令,并处于停机状态,这将降低处理器的功耗。处于停机状态的处理器可以被外部中断唤醒并恢复执行,而且会继续执行hlt后面的指令。
test指令
>测试指令,和and类似,只是不保存运算结果
int3指令
>断点中断指令,机器指令码为0xCC
into指令
>溢出中断指令,机器指令码为0xCE,当处理执行这条指令时,如果标志寄存器的OF位为1,那么僵产生4号中断,否则这条指令什么都不做
32位寻址
>
>描述符
>高32位
>低32位
>G位是粒度(Granularity) 位,用于解释段界限的含义。当G位是“0”时,段界限以字节为
>单位。此时,段的扩展范围是从1字节到1兆字节(1B~ 1MB),因为描述符中的界限值是20位
>的。相反,如果该位是“1”,那么,段界限是以4KB为单位的。这样,段的扩展范围是从4KB
>到4GB。
>
>S位用于指定描述符的类型( Descriptor Type)。当该位是“0”时,表示是一个系统段;为
>“1”时,表示是一个代码段或者数据段(堆栈段也是特殊的数据段)。系统段将在以后介绍。
>
>DPL表示描述符的特权级( Descriptor Privilege Level, DPL)。这两位用于指定段的特权级。共有4种处理器支持的特权级别,分别是0、1、2、3,其中0是最高特权级别,3是最低特权级别。
>刚进入保护模式时执行的代码具有最高特权级0(可以看成是从处理器那里继承来的),这些代码通常都是操作系统代码,因此它的特权级别最高。每当操作系统加载一一个用户程序时,它通常都会指定一个稍低的特权级,比如3特权级。不同特权级别的程序是互相隔离的,其互访是严格限制的,而且有些处理器指令(特权指令)只能由0特权级的程序来执行,为的就是安全。
>
>P是段存在位( Segment Present)。P位用于指示描述符所对应的段是否存在。一般来说, 描述符所指示的段都位于内存中。但是,当内存空间紧张时,有可能只是建立了描述符,对应的内存空间并不存在,这时,就应当把描述符的P位清零,表示段并不存在。另外,同样是在内存空间紧张的情况下,会把很少用到的段换出到硬盘中,腾出空间给当前急需内存的程序使用(当前正在执行的),这时,同样要把段描述符的P位清零。当再次轮到它执行时,再装入内存,然后将P位置1。
>P位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果P位是“0”,处理器就
>会产生一个异常中断。通常,该中断处理过程是由操作系统提供的,该处理过程的任务是负责将该段从硬盘换回内存,并将P位置1。在多用户、多任务的系统中,这是一种常用的虚拟内存调度策略。当内存很小,运行的程序很多时,如果计算机的运行速度变慢,并伴随着繁忙的硬盘操作时,说明这种情况正在发生。
>
>D/B位是“默认的操作数大小”(Default Operation Size)或者“默认的堆栈指针大小”( Default Stack Pointer Size),又或者“上部边界”(Upper Bound)标志。设立该标志位,主要是为了能够在32位处理器上兼容运行16位保护模式的程序。尽管这种程序现在已经非常罕见了,但它毕竟存在过,兼容,这是Intel公司能够兴旺发达的重要因素。该标志位对不同的段有不同的效果。对于代码段,此位称做“D”位,用于指示指令中默认的偏移地址和操作数尺寸。D=0表示指令中的偏移地址或者操作数是16位的; D=1,指示32位的偏移地址或者操作数。举个例子来说,如果代码段描述符的D位是0,那么,当处理器在这个段上执行时,将使用16位的指令指针寄存器IP来取指令,否则使用32位的EIP。对于堆栈段来说,该位被叫做“B”位,用于在进行隐式的堆栈操作时,是使用SP寄存器还是ESP寄存器。隐式的堆栈操作指令包括push、pop 和call等。如果该位是“0”,在访问那个段时,使用SP寄存器,否则就是使用ESP寄存器。同时,B位的值也决定了堆栈的上部边界。如果B=0,那么堆栈段的上部边界(也就是SP寄存器的最大值)为0xFFFF;如果B=1,那么堆栈段的.上部边界(也就是ESP寄存器的最大值)为0xFFFFF。
>
>L位是64位代码段标志( 64-bit Code Segment),保留此位给64位处理器使用。
>
>TYPE字段共4位,用于指示描述符的子类型,或者说是类别。如表11-1所示,对于数据段来说,这4位分别是X、E、W、A位;而对于代码段来说,这4位则分别是X、C、R、A位。
>
>表11-1中,X表示是否可以执行(eXecutable)。 数据段总是不可执行的,X=0;代码段总是可以执行的,因此,X=1。
>对于数据段来说,E位指示段的扩展方向。E=0是向上扩展的,也就是向高地址方向扩展的,
>是普通的数据段; E=1是向下扩展的,也就是向低地址方向扩展的,通常是堆栈段。W位指示段
>的读写属性,或者说段是否可写,W=0的段是不允许写入的,否则会引发处理器异常中断; W=1
>的段是可以正常写入的。.
>对于代码段来说,C位指示段是否为特权级依从的(Conforming)。 C=0表示非依从的代码>段,
>这样的代码段可以从与它特权级相同的代码段调用,或者通过门调用; C=1表示允许从低特权级的
>程序转移到该段执行。关于特权级和特权级检查的知识将在第14 章介绍。R位指示代码段是否允许
>读出。代码段总是可以执行的,但是,为了防止程序被破坏,它是不能写入的。至于是否有读出的可
>能,由R位指定。R=0表示不能读出,如果企图去读-一个 R=0的代码段,会引发处理器异常中断;
>如果R=1,则代码段是可以读出的,即可以把这个段的内容当成ROM一样使用。
>数据段和代码段的A位是已访问( Accessed)位,用于指示它所指向的段最近是否被访问过。在描述符创建的时候,应该清零。之后,每当该段被访问时,处理器自动将该位置“1”。对该位的清零是由软件(操作系统)负责的,通过定期监视该位的状态,就可以统计出该段的使用频率。当内存空间紧张时,可以把不经常使用的段退避到硬盘上,从而实现虚拟内存管理。
>
>AVL是软件可以使用的位( Available),通常由操作系统来用,处理器并不使用它。如果你把它理解成“好吧,该安排的都安排了,最后多出这么一位,不知道干什么用好,就给软件用吧”,我也不反对,也许Intel公司也不会说些什么。
lgdt指令
>加载描述符表的线性基地址和界限到GDTR寄存器,该指令的操作数是一个内存地址,指向一个包含了48位(6字节)的内存区域,在16位模式下,改地址是16位的,在32位模式下,该地址是32位的。该指令在是模式和保护模式都可以使用,在这6字节的内存区域中,低16位是GDT的界限值,高32位是GDT的基地址,在初始状态下(计算机启动之后),GDTR寄存器的基地址初始化位0x00000000,界限值位0xffff.
xchg指令
>交换指令,用于交换2个操作数的内容,操作数不能同时为内存单元
bswap指令
>字节交换指令,将寄存器内的值的高低位进行交换,如:eax= 0x1234 5678 交换后位eax = 0x7856 3412。
pushad:
> 将所有的32位通用寄存器压入堆栈
pusha:
>将所有的16位通用寄存器压入堆栈
pushfd:
>然后将32位标志寄存器EFLAGS压入堆栈
pushf:
>:将的16位标志寄存器EFLAGS压入堆栈
popad:
>将所有的32位通用寄存器取出堆栈
popa:
>将所有的16位通用寄存器取出堆栈
popfd:
>将32位标志寄存器EFLAGS取出堆栈
popf:
>将16位标志寄存器EFLAGS取出堆栈