汇编、逆向工程基础

Posted by Mr.Be1ieVe on Monday, January 13, 2020

原文链接:https://blog.csdn.net/qq_21508727/article/details/80559270

单位

  1. ·WORD(字) - 一个字由两个字节组成,共有16位。一个字的最大值是0FFFFh (或者是 65535d) (h代表16进制,d代表10进制)。
  2. ·DOUBLE WORD(双字DWORD) - 一个双字包含两个字,共有32位。最大值为0FFFFFFFF (或者是 4294967295d)。

一个16进制占4bit

寄存器

Win+Intel CPU组成的计算机通常有9个32位寄存器 (w/o 标志寄存器)。它们是:

  1. EAX: 累加器
  2. EBX: 基址寄存器
  3. ECX: 计数器
  4. EDX: 数据寄存器
  5. ESI: 源变址寄存器
  6. EDI: 目的变址寄存器
  7. EBP: 扩展基址指针寄存器
  8. ESP: 栈指针寄存器
  9. EIP: 指令指针寄存器

==大小都是4个字节==

  1. EBP: EBP在栈中运用最广,刚开始没有什么需要特别注意的 ;)

  2. ESP: ESP指向栈区域的栈顶位置。栈是一个存放即将会被用到的数据的地方,你可以去搜索一下push/pop 指令了解更多栈知识。

  3. EIP: EIP指向下一个将会被执行的指令。


栈(stack):后进先出的数据结构

堆(heap):经过排列的树形数据结构,通常指二叉堆,存取是随意的

有一些寄存器是16位甚至8位的,是不能直接寻址的。

1565342053303

一个寄存器可以这样看:

1565342088084

EAX是名字,AX是低16位部分,又分为低8位AL,高8位AH

了解:

i.单字节(8位)寄存器:

  1. AL and AH
  2. BL and BH
  3. CL and CH
  4. DL and DH

ii.单字(16位)寄存器:这些寄存器大小为一个字(=2字节=16位)。一个单字寄存器包含两个单字节寄存器。通常根据功能区分

1.通用寄存器:

  1. AX (单字=16位) = AH + AL -> 其中‘+’号并不代表把它们代数相加。AH和AL寄存器是相互独立的,只不过都是AX寄存器的一部分,所以你改变AH或AL (或者都改变) ,AX寄存器也会被改变。
  2. -> ‘accumulator’(累加器):用于进行数学运算
  3. BX -> ‘base’(基址寄存器):用来连接栈(之后会说明)
  4. CX -> ‘counter’(计数器):
  5. DX -> ‘data’(数据寄存器):大多数情况下用来存放数据
  6. DI -> ‘destination index’(目的变址寄存器): 例如将一个字符串拷贝到DI
  7. SI -> ‘source index’(源变址寄存器): 例如将一个字符串从SI拷贝

2.索引寄存器(指针寄存器):

  1. BP -> ‘base pointer’(基址指针寄存器):表示栈区域的基地址
  2. SP -> ‘stack pointer’(栈指针寄存器):表示栈区域的栈顶地址

3.段寄存器:

  1. CS -> ‘code segment’(代码段寄存器):用于存放应用程序代码所在段的段基址(之后会说明)
  2. DS -> ‘data segment’(数据段寄存器):用于存放数据段的段基址(以后会说明)
  3. ES -> ’extra segment’(附加段寄存器):用于存放程序使用的附加数据段的基地址
  4. SS -> ‘stack segment’(栈段寄存器):用于存放栈段的段基址(以后会说明)

4.指令指针寄存器:

IP -> ‘instruction pointer’(指令指针寄存器):指向下一个指令 ;)

iii. 双字(32位)寄存器:

2 字= 4 字节= 32 位, EAX、EBX、ECX、EDX、EDI……

如果16位寄存器前面加了‘E’,就代表它们是32位寄存器。例如,AX=16位,EAX=32位。

标志寄存器

32位的CPU中有32个不同的标志寄存器,只关心期中三个:ZF、OF、CF。标志寄存器是一个标志,==只能是0或者1,决定了是否要执行某个指令==,可以知道程序在这一步是否会跳转。

Z-Flag(零标志)

ZF是破解中用的最多的寄存器(通常90%)。若上一个运算结果为0,则值为1,否则为0。

The O-Flag(溢出标志)

OF占约4%,若上一步改变了某寄存器的最高有效位,则会被设置为1。操作产生溢出时也会被设置为1。

The C-Flag(进位标志)

CF占1%,产生溢出,就会被设置为1。

段偏移

内存中的一个段储存了指令(CS)、数据(DS)、堆栈(SS)或者其他段(ES)。每一个段都有一个偏移量,在32位应用程序下,这些偏移量由00000000到FFFFFFFF。段和偏移量的标准形式如下:

段:偏移量 = 把它们放在一起就是内存中一个具体的地址。

比喻:一个段是一本书的某一页;偏移量是一页的某一行。

堆和栈

内存中的栈区处于相对较高的地址以地址的增长方向为上的话,栈地址是向下增长的。

栈中分配局部变量空间,堆区是向上增长的用于分配程序员申请的内存空间。另外还有静态区是分配静态变量,全局变量空间的;只读区是分配常量和程序代码空间的;以及其他一些分区。

PUSH 命令是向栈中压入数据,POP命令就是从栈中取出最后放入的数据并且把它存进具体的寄存器中。

1、堆和栈申请和回收方式不同

  • 栈:由系统自动分配空间,自动回收
  • 堆:由程序员自己申请空间,不释放就可以一直访问

2、申请后系统响应

  • 栈:只要栈剩余空间,就自动分配空间,否则提示栈溢出
  • 堆:==操作系统有一个记录空闲内存地址的链表==,系统收到空间申请会遍历该链表,寻找第一个大于申请空间的结点。并将结点从空闲结点链表删除,将空间分配给程序。==在这块内存空间的首地址会记录分配空间的大小,所以delete才能正确释放空间。==多余空间会放回空闲链表。

3、堆申请效率比栈低

4、申请大小的限制

  • 栈:Win下,栈是==连续的内存区域==,==向低地址扩展的数据结构。栈顶地址和栈的最大容量是系统预先规定好的。==Win下栈大小为1M,申请空间超出剩余会提示overflow。
  • 堆:向高地址拓展的数据结构,不连续的内存区域。(因为用链表储存)**链表遍历从低地址到高地址。**堆的大小受限于计算机系统中有效的虚拟内存。

5、堆和栈中的存储内容

  • 栈大小有限,用子函数有物理意义(???)
  • 栈:调用函数时,第一个进栈的是主函数中函数调用后的下一条指令(下一条可执行语句)==的地址==,然后是函数的各个参数,在大多数C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。==静态变量不入栈。==

==调用结束后,局部变量先出栈,然后是参数,最后栈顶指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行==

  • 堆:一般是在堆的头部用一个字节存放堆的大小。堆中具体内容由程序员安排。

6、存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 

aaaaaaaaaaa是在运行时刻赋值的;(字符数组有连续空间存放值)放在栈中。 而bbbbbbbbbbb是在编译时就确定的;(只有4字节的空间用于存放地址)放在堆中。 ==在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快==

指令

==所有值通常以16进制形式储存的。==

大部分指令有两个操作符(例如add EAX,EBX),有些是一个(例如not EAX),还有一些是三个操作符(例如IMUL EAX,EDX,64)。如果使用“DWORD PTR [XXX]”就表示使用了内存中偏移量为XXX的数据。Win+Intel的电脑大多采用“低尾端法”,所以字节在内存中储存方式是倒过来的。

大部分有两个操作符的指令都是以下这种格式(以add举例)

add eax,ebx                           寄存器, 寄存器
add eax,123                           寄存器, 数值
add eax,dword ptr [404000]            寄存器, Dword  指针 [数值]
add eax,dword ptr [eax]               寄存器, Dword  指针 [寄存器值]
add eax,dword ptr [eax+00404000]      寄存器, Dword  指针 [寄存器值+数值]
add dword ptr [404000],eax            Dword 指针[数值], 寄存器
add dword ptr [404000],123            Dword 指针[数值], 数值
add dword ptr [eax],eax               Dword 指针[寄存器值], 寄存器
add dword ptr [eax],123               Dword 指针[寄存器值], 数值
add dword ptr [eax+404000],eax        Dword 指针[寄存器值+数值], 寄存器add dword ptr [eax+404000],123        Dword 指针[寄存器值+数值], 数值

ADD

ADD 被加数,加数

将一个数值加在一个寄存器上或者一个内存地址上。

add eax,123 == eax = eax +123

加法指令对ZF,OF,CF都有影响

AND

AND 目标数,原数

AND运算对两个数进行逻辑与运算。

会清空OF,CF标记,设置ZF标记。

CALL

CALL 404000 最常见: CALL 地址 CALL EAX CALL 寄存器 - 如果寄存器存的值为404000,那就等同于第一种情况 CALL DWORD PTR [EAX] CALL [EAX]偏移量所指向的地址 CALL DWORD PTR [EAX+5] CALL [EAX+5]偏移量所指向的地址

==CDQ==

通常出现在除法前面,将EDX的所有位变成EAX最高位的值

比如当EAX>=80000000h时,其二进制最高位为1,则EDX被32位全赋值为1,即FFFFFFFF

若EAX<80000000,则其二进制最高位为0,EDX为00000000。

然后将EDX:EAX组成新数(64位):FFFFFFFF 80000000

CMP(compare)

CMP 目标数,原数

比较两个值并且标记CF、OF、ZF:

CMP EAX, EBX 比较eax和ebx是否相等,如果相等就设置ZF为1 CMP EAX,[404000] 比较eax和偏移量为[404000]的值是否相等 CMP [404000],EAX 比较[404000]是否与eax相等

DEC(decrease)

DEC something

自减

dec eax eax自减1 dec [eax] 偏移量为eax的值自减1 dec [401000] 偏移量为401000的值自减1 dec [eax+401000] 偏移量为eax+401000的值自减1

可以标记ZF、OF

DIV(division除)

DIV 除数

无符号除法

DIV指令用来将EAX除以除数(无符号除法),被除数通常是EAX,结果也储存在EAX中,而被除数对除数取的模存在除数中。

mov eax,64 EAX = 64h = 100 mov ecx,9 ECX = 9 div ecx EAX除以ECX

EAX = 100/9 = 0B(11)并且ECX = 100 MOD 9 = 1

可以标记CF、OF、ZF

IDIV(整除)

IDIV 除数

有符号除法

执行方法同div一样

INC(increase)

自加

可以标记ZF、OF

INT

语法: int 目标数

INT 的目标数必须是产生一个整数(例如:int 21h),类似于call调用函数,INT指令是调用程序对硬件控制,不同的值对应着不同的功能。

具体参照硬件说明书。

JUMPS

这些都是最重要的跳转指令和触发条件(重要用*标记,最重要用**标记):

指令                条件                    条件
JA*          -    如果大于就跳转(无符号)      - CF=0 and ZF=0
JAE          -    如果大于或等于就跳转(无符号)- CF=0
JB*          -    如果小于就跳转(无符号)   - CF=1
JBE          -    如果小于或等于就跳转(无符号)- CF=1 or ZF=1
JC           -    如果CF被标记就了跳转       - CF=1
JCXZ         -    如果CX等于0就跳转      - CX=0
JE**         -    如果相等就跳转        - ZF=1
JECXZ        -    如果ECX等于0就跳转       - ECX=0
JG*          -    如果大于就跳转(有符号)   - ZF=0 and SF=OF (SF = Sign Flag)
JGE*         -    如果大于或等于就跳转(有符号) - SF=OF
JL*          -    如果小于就跳转(有符号)    - SF != OF (!= is not)
JLE*         -    如果小于或等于就跳转(有符号 - ZF=1 and OF != OF
JMP**        -    跳转             - 强制跳转
JNA          -    如果不大于就跳转(无符号)   - CF=1 or ZF=1
JNAE         -    如果不大于等于就跳转(无符号) - CF=1
JNB          -    如果不小于就跳转(无符号)   - CF=0
JNBE         -    如果不小于等于就跳转(无符号) - CF=0 and ZF=0
JNC          -    如果CF未被标记就跳转     - CF=0
JNE**        -    如果不等于就跳转       - ZF=0
JNG          -    如果不大于就跳转(有符号)   - ZF=1 or SF!=OF
JNGE         -    如果不大于等于就跳转(有符号) - SF!=OF
JNL          -    如果不小于就跳转(有符号)   - SF=OF
JNLE         -    如果不小于等于就跳转(有符号) - ZF=0 and SF=OF
JNO          -    如果OF未被标记就跳转     - OF=0
JNP          -    如果PF未被标记就跳转     - PF=0
JNS          -    如果SF未被标记就跳转      - SF=0
JNZ          -    如果不等于0就跳转      - ZF=0
JO           -    如果OF被标记就跳转     - OF=1
JP           -    如果PF被标记就跳转     - PF=1
JPE          -    如果是偶数就跳转       - PF=1
JPO          -    如果是奇数就跳转       - PF=0
JS           -    如果SF被标记就跳转     - SF=1
JZ           -    如果等于0就跳转      - ZF=1

LEA (有效地址传送)

语法:LEA 目的数、源数

LEA可以看成和MOV差不多的指令LEA ,它本身的功能并没有被太广泛的使用,反而广泛运用在快速乘法中:

lea eax,dword ptr [4*ecx+ebx]

将eax赋值为 4*ecx+ebx

MOV (传送)

语法: MOV 目的数,源数

这是一个很简单的指令,MOV指令将源数赋值给目的数,并且源数值保持不变

这里有一些MOV的变形:

MOVS/MOVSB/MOVSW/MOVSD EDI, ESI:这些变形能将ESI指向的内容传送到EDI指向的内容中去

MOVSX:MOVSX指令将单字或者单字节扩展为双字或者双字节传送,原符号不变

MOVZX:MOVZX扩展单字节或单字为双字节或双字并且用0填充剩余部分(通俗来说就是将源数取出置于目的数,其他位用0填充)

MUL (multiply乘法)

语法:MUL 数值

这个指令同IMUL一样,不过MUL可以乘无符号数。

NOP (无操作)

语法:NOP

这个指令说明不做任何事

所以它在逆向中运用范围最广

OR (逻辑或)

语法:OR 目的数,源数

OR指令对两个值进行逻辑或运算

这个指令会清空OF、CF标记,设置ZF标记

为了更好的理解OR,思考下面二进制串:

10010101100101001101

如果对它们进行逻辑与运算,结果将是1101011111。

只有当两边同为0时其结果为0,否则就为1。你可以用计算器尝试计算。希望你能理解为什么,最好自己动手算一算

POP

语法:POP 目的地址

POP指令将栈顶第一个字传送到目的地址。 每次POP后,ESP(栈指针寄存器)都会增加以指向新栈顶

PUSH

语法:PUSH 值

PUSH是POP的相反操作,它将一个值压入栈并且减小栈顶指针值以指向新栈顶。

REP/REPE/REPZ/REPNE/REPNZ

语法: REP/REPE/REPZ/REPNE/REPNZ ins

重复上面的指令:直到CX=0。ins必须是一个操作符,比如CMPS、INS、LODS、MOVS、OUTS、SCAS 或 STOS

RET (return返回)

语法:RET

RET digit

RET指令的功能是从一个代码区域中退出到调用CALL的指令处。

RET digit在返回前会清理栈

SUB (减)

语法:SUB 目的数,源数

SUB与ADD相反,它将源数减去目的数,并将结果储存在目的数中

SUB可以标记ZF、OF、CF

TEST

语法:TEST 操作符、操作符

这个指令99%都是用于”TEST EAX, EAX”,它执行与AND相同的功能,但是并不储存数据。如果EAX=0就会标记ZF,如果EAX不是0,就会清空ZF

XOR

语法:XOR 目的数,源数

XOR指令对两个数进行异或操作

这个指令清空OF、CF,但会标记ZF

为了更好的理解,思考下面的二进制串:

1001010110
0101001101

如果异或它们,结果将是1100011011

如果两个值相等,则结果为0,否则为1。

很多情况下我们会使用”XOR EAX, EAX”,这个操作是将EAX赋值为0,因为当一个值异或其自身,就过都是0。你最好自己动手尝试下,这样可以帮助你理解得更好

MOV

MOV AX , 2000H 将AX传给2000H

逻辑操作符

Snipaste_2019-08-12_22-36-43

MIPS汇编

link

xori 异或

「真诚赞赏,手留余香」

Mr.Be1ieVe's Treasure

真诚赞赏,手留余香

使用微信扫描二维码完成支付