# 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取出堆栈