硬件相关知识
source link: https://www.ascotbe.com/2018/12/05/HardwareRelatedKnowledge/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
对两年前的知识进行了下温习,发现好多坑都填上了,然后把之前的文章删了总结到了一篇,到时候还有些点需要填上
Windows/Linux的内存结构
首先会把EBP
压入栈中,然后把ESP
的值保存到EBP
中,接着ESP
会像低地址增加,EBP
保持在栈底不动(高地址),所有运算结束后弹出所有的值ESP
会到和EBP
相同的值(大部分是这样的)
Windows的内存结构分布
首先是内存结构分布图,
windows
的内存默认是从0x80000000
位置开始的栈的增长方向是从高地址到低地址递增的,用下面一个例子来解释
-
esp
是栈指针,是cpu
机制决定的,push
、pop
指令会自动调整esp
的值;ebp
只是存取某时刻的esp
,这个时刻就是进入一个函数内后,cpu
会将esp
的值赋给ebp
,此时就可以通过ebp
对栈进行操作,比如获取函数参数,局部变量等,实际上使用esp
也可以;假设执行
print
函数之前esp=Q
main() {
//执行test前
print(int p1,int p2);
//执行test后
}
#############开始执行print函数#############
push p2; //函数参数p2入栈,esp=Q-4H
push p1; //函数参数p1入栈,esp=Q-8H
call print; //函数返回地址入栈,esp=Q-0CH
#现在进入print内,做些准备工作:
push ebp; //保护先前ebp指针,ebp入栈,esp=Q-10H
mov ebp,esp; //设置ebp等于当前的esp
#此时,ebp+0CH=Q-4H,即p2的位置
#同样,ebp+08H=Q-8H,即p1的位置
#下面是print内的一些操作:
sub esp,20H; //设置长度为10H大小的局部变量空间,esp=Q-20H
#... ...
#一系列操作
#... ...
add esp,20H; //释放局部变量空间,esp=Q-10H
pop ebp; //出栈,恢复原先的ebp的值,esp=Q-0CH
ret 8; //ret返回,弹出先前入栈的返回地址,esp=Q-08H,后面加操作数8H为平衡堆栈
#之后,弹出函数参数,esp=Q,恢复执行print函数前的堆栈;- 整个的流程图如下,要执行函数之前把
p2
和p1
压入栈中,栈向低地址递增(Q-4h
),接着把函数地址放入栈中,然后把ebp
的值放入栈中保存,下图中应该是ebp在address_return上面,图片有问题
- 整个的流程图如下,要执行函数之前把
-
EBP
是当前函数的存取指针,即存储或者读取数时的指针基地址;ESP
就是当前函数的栈顶指针。每一次发生函数的调用(主函数调用子函数)时,在被调用函数初始时,都会把当前函数(主函数)的EBP
压栈,以便从子函数返回到主函数时可以获取EBP
。下面是按调用约定
__stdcall
调用函数test(int p1,int p2)
的汇编代码#假设执行函数前堆栈指针ESP为0xAAAAAAA ;EBP为0xAAAAAB0
push p2 ;参数2入栈, ESP -= 4h , ESP = 0xAAAAAAA - 4h = 0xAAAAAA6
push p1 ;参数1入栈, ESP -= 4h , ESP = 0xAAAAAAA - 8h = 0xAAAAAA2
call test ;压入返回地址 ESP -= 4h, ESP = 0xAAAAAAA- 0Ch = 0xAAAAA9D,注意:这里是test函数的返回地址,即在代码段中的地址(偏移)。
;//进入函数内
{
push ebp ;保护先前EBP指针, EBP入栈(即0xAAAAAB0入栈,注意与返回地址区别), ESP-=4h, ESP = 0xAAAAA99
mov ebp, esp ;设置EBP指针指向栈顶 0xAAAAA99
mov eax, dword ptr [ebp+0ch] ;ebp+0ch为0xAAAAAA6即参数2的位置
mov ebx, dword ptr [ebp+08h] ;ebp+08h为0xAAAAAA2,即参数1的位置
sub esp, 8 ;局部变量所占空间ESP-=8, ESP = 0xAAAAA91
...
add esp, 8 ;释放局部变量, ESP+=8, ESP = 0xAAAAA99
pop ebp ;出栈,恢复EBP, ESP+=4, ESP = 0xAAAAA9D,即把栈中地址0xAAAAA99的内容pop到ebp中
ret 8 ;ret返回,弹出返回地址,ESP+=4, ESP=0xAAAAAA2, 后面加操作数8为平衡堆栈,ESP+=8,ESP=0xAAAAAAA, 恢复进入函数前的堆栈.
}原来
ESP
就是一直指向栈顶的指针,而EBP
只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。
-
堆分为堆块和堆表
引用大佬的图
-
为了合理地组织堆区中的空闲堆块,提出了堆表的概念。堆表的数据结构决定了整个堆区的组织方式,一般位于堆区的起始位置,用于索引堆区中空闲堆块的重要信息,包括堆块的位置、大小、状态(空闲或占用)。
-
传统内存统计单位往往是以字节位标准,但处于性能的考虑,堆内存按照大小不同组成不同的块,以堆块为单位进行标识。一个堆块包括两个部分:
header
部分和data
部分。header
是一个堆块头部的几个字节,用来标识这个堆块自身的信息。data
是用来在最终分配给用户使用的数据区。-
堆块的分配可以分为三类,
Lookaside
分配、普通Freelist
分配以及0
号Freelist(free[0])
分配。Lookaside
分配:寻找到大小匹配的空闲堆块 -> 修改状态为占用 -> 从堆表中解链 -> 给程序返回一个指向堆块的指针普通
Freelist
分配:寻找最优的空闲堆块 -> 若失败,寻找次优空闲堆块分配0
号Freelist
分配:从free[0]
反向寻找最后一个堆块(最大的堆块) -> 若满足要求,再正向搜索最小的满足要求的空闲堆块。堆块分配中的“找零钱”现象:当在
Freelist
中无法找到刚好合适的堆块时,此时会分配一个稍微大一点的空闲堆块给程序使用,其过程是首先在这个大块中分配出大小刚好等于请求堆块大小的堆块给程序,然后剩下的部分修改堆块的header
信息,重新链入到Freelist
合适的位置。这种方法节约了内存的使用,不会造成大量的内存浪费。由于
Lookaside
只有在精确匹配时才会分配,因此不存在“找零钱”现象。 -
堆块的释放主要是将堆块修改为空闲状态,然后将堆块链入相应的堆表。所有的释放块都链入堆表的末尾,分配的时候也会首先从堆表末尾分配。
-
为了减少内存中的内存碎片,合理有效地利用内存,堆管理系统还需要进行堆块合并操作。
当两个空闲堆块彼此相邻的时候就会进行堆块合并操作。其过程大致为:
将两个块从
Freelist
中解链 -> 合并堆块 -> 调整合并后堆块的header
信息 -> 将合并后的堆块放入Freelist
合适的位置
-
堆上的漏洞
堆溢出漏洞
堆溢出与栈溢出在本质上是相通的,都是精心构造特制的数据去覆盖正常数据,覆盖到某个特定位置后跳转到自己的shellcode的地址去执行shellcode。
但从技术层面来讲,堆溢出比栈溢出难度更大。而且现在基本很少有软件存在典型的栈溢出漏洞,相反由于堆的复杂性,很多软件仍然存在诸多的堆溢出漏洞。UAF
漏洞Use After Free(UAF),释放后重引用漏洞, 一块内存已经被释放后,在程序中仍然存在对该块内存的引用,并且在一定情况下可能使用内存中的数据。
由于这块原本已经被释放不应该再使用的内存被程序中的其他地方进行了使用,因此该块内存中的数据是不可信的。这种方式甚至会造成内存崩溃或者任意代码执行。此类型的漏洞在浏览器中比较常见。
UAF漏洞比较有名的是CVE-2013-1347 Microsoft IE CGenericElementUAF漏洞,该漏洞被用在了当时著名的“水坑”事件中,影响巨大。Double Free
漏洞双重释放漏洞,主要是由于对同一块内存进行二次重复释放。在释放过程中,邻近的已释放的堆块存在合并动作,这会导致原有的堆header信息发生改变,同时前向指针和后向指针也会发生改变,随后再对其中的地址进行引用,就会导致访问异常,最终导致程序崩溃或者任意代码执行。
从另外一个角度来说,由于发生了对释放后的堆块内存的引用,因此Double Free漏洞也是UAF漏洞的一个子集。
双重释放漏洞比较经典的是CVE-2014-1767,该漏洞位于Windows AFD.sys文件中。
在2014年的Pwn2Own上,Siberas团队使用该漏洞进行内核提权,绕过了Windows 8.1平台上的IE11沙箱,并在随后获得了Pwnie Awards的“最佳提权漏洞奖”。该漏洞通杀Windows系统,影响较大。
Linux的内存结构分布
首先依旧是先放个内存结构图,
linux
内存默认是从0xC0000000
位置开始的栈地址也是依旧是高地址到低地址递增
堆(待更新)
Windows/Linux的汇编区别
x86汇编一直存在两种不同的语法,在intel的官方文档中使用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一直使用AT&T语法。而linux是UNIX衍生的一种系统所有也是使用AT&T语法。
相关区别:
- AT&T使用$表示立即操作数,而Intel的立即操作数是不需要界定的。因此,使用AT&T语法引用十进制值4时,使用$4,使用Intel语法时只需使用4。
- AT&T在寄存器名称前加上前缀%,而Intel不这样做。因此,使用AT&T语法引用EAX寄存器写为%eax。
- AT&T语法处理源和目标操作数时使用相反的顺序。把十进制值4传送给EAX寄存器,AT&T的语法是movl $4, %eax,而Intel语法是mov eax, 4。
- AT&T语法在助记符后面使用一个单独的字符来引用操作中使用的数据长度,而Intel语法中数据长度被声明为单独的操作数。AT&T的指令movl $test, %eax等同于Intel语法的mov eax, dword ptr test。
- 长调用和跳转使用不同语法定义段和偏移值。AT&T语法使用ljmp $section, $offset,而Intel语法使用jmp section:offset。
用一张表就能解释:
CPU架构
x86(IA-32)
x86是16位和32位处理器的统称。
x86泛指一系列英特尔公司用于开发处理器的指令集架构,这类处理器最早为1978年面市的intel 8086(并不是32位的处理器,而是16位微处理器,直到1985年32位的80386才是真正的32位处理器),由于之前上市的CPU都是以86结尾的,所以被称为x86,但是数字没办法注册称为商标,最后将其IA-32,全名为“Intel Architecture, 32-bit”。IA-32在第三代x86架构才使用上,也就是真正意义上的32位处理器
IA-64
IA-64,又称英特尔安腾架构(Intel Itanium architecture),使用在Itanium处理器家族上的64位元指令集架构,由英特尔与惠普共同开发。此架构与x86及x86-64并不相容,操作系统与软件需使用IA-64专用版本。
第一款安腾于2001年推出,2017年推出最后一代安腾处理器,并停止开发。
x86-64(AMD64)
目前市面上除了英特尔安腾的64位CPU,其他Intel和AMD的支持64位的CPU都是使用这个架构的。
x86-64( 又称x64,即英文词64-bit extended,64位拓展 的简写)是x86架构的64位拓展,向后兼容于16位及32位的x86架构。x64于1999年由AMD设计,AMD首次公开64位集以扩展给x86,称为“AMD64”。其后也为英特尔所采用,现在英特尔称之为“Intel 64”,在之前曾使用过“Clackamas Technology” (CT)、“IA-32e”及“EM64T”来称呼。
其他各类叫法:
苹果公司和RPM包管理员以“x86-64”或“x86_64”称呼此64位架构。
甲骨文公司及Microsoft称之为“x64”。
BSD家族及其他Linux发行版则使用“amd64”,32位版本则称为“i386”(或 i486/586/686)。
Arch Linux用x86_64称呼此64位架构。
ARM架构,过去称作高级精简指令集机器(英语:Advanced RISC Machine),是一个精简指令集(RISC)处理器架构家族,其广泛地使用在许多嵌入式系统设计。常见的ARM架构CPU设备:树莓派(Broadcom)、手机(HiSilicon、Qualcomm)、大型工作站、超级计算机
截止2020年CPU架构种类
PowerPC
PowerPC(英语:Performance Optimization With Enhanced RISC – Performance Computing,有时简称PPC)是一种基于精简指令集(RISC)的指令集架构 ISA(Instruction set architecture),其基本的设计源自IBM的POWER(Performance Optimized With Enhanced RISC)架构。PowerPC是1991年,Apple、IBM、Motorola组成的AIM联盟所发展出的微处理器架构。PowerPC是整个AIM联盟平台的一部分,并且是到目前为止唯一的一部分。但苹果电脑自2005年起,将旗下电脑产品转用Intel x86。
较广为人知的产品应用包含:
- 苹果公司:Power Macintosh系列、PowerPC PowerBook系列(1995年以后的产品)、iBook系列、iMac系列(2005年以前的产品)、eMac系列产品。
- 任天堂:GameCube、Wii和Wii U(之后换成了ARM架构处理器)。
- 微软:Xbox 360(之后的产品换成了AMD的处理器)
- 索尼:PlayStation 3(之后的产品换成了AMD的处理器)
MIPS(Microprocessor without Interlocked Pipeline Stages),是一种采取精简指令集(RISC)的指令集架构(ISA),1981年出现,由 MIPS 公司开发。最早的MIPS架构是32比特,最新的版本已经变成64比特。
MIPS 架构有多个版本。其中包括 MIPS I、II、III、IV,以及 MIPS V,早期的 MIPS 架构只有 32 位的版本,而其 64 位的版本随后才被开发。截至 2017 年 4 月,MIPS32/64 的当前版本是 MIPS32/64 Release 6。MIPS32/64 与
- 北京君正集成电路股份有限公司:Xburst系列微架构半导体IP核
- 中国龙芯系列产品
LoongISA
LoongISA(简称LISA)是龙芯中科注册的自主CPU指令集架构(由MIPS指令集拓展而来,采用MIPS的指令集格式)。LoongISA 指令集架构包括MIPS64 Release 2全套指令集和MIPS64 Release 5中的部分指令模块,以及其他一系列龙芯中科自主扩展的指令集。
SPARC
SPARC,名称源自于可扩展处理器架构(Scalable Processor ARChitecture)的缩写,是一种RISC指令集架构,最早于1985年由Sun微系统所设计,也是SPARC国际公司的注册商标之一。
RISC-V
RISC-V(发音为“risk-five”)是一个基于精简指令集(RISC)原则的开源指令集架构(ISA),简易解释为开源软件运动相对应的一种“开源硬件”。该项目2010年始于加州大学柏克莱分校,但许多贡献者是该大学以外的志愿者和行业工作者。RISC-V指令集可以自由地用于任何目的,允许任何人设计、制造和销售RISC-V芯片和软件而不必支付给任何公司专利费。
X86架构汇编
Intel汇编和AT&T汇编的区别
- Intel汇编
- DOS、Windows,包括我们之前了解的8086处理器
- Windwos派系:VC编译器
- AT&T汇编
- Linux、Unix、Mac OS、iOS模拟器
- Unix派系:GCC编译器
以下使用的是Intel汇编语法讲解
32位CPU所含有的寄存器
- 8个32位通用寄存器
- 4个数据寄存器(EAX、EBX、ECX和EDX)
- 2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP)
- 6个段寄存器(ES、CS、SS、DS、FS和GS)
- 1个指令指针寄存器(EIP)
- 1个标志寄存器(EFlags)
每个寄存器都可作为一个32位值或两个16位值来寻址使用。某些16位的寄存器能够按照8位值寻址使用。
下面几个没有8位模式:
EAX的低16位称为AX,AX的高8位称为AH,低8位称为AL。
寄存器EAX存储了值0xA9DC81F5
,代码可以使用其他三种方式来引用EAX中的这个数据:AX(2字节)是0x81F5
,AL(1字节)是0xF5
,AH(1字节)是0x81
,如图下图所示
数据寄存器
数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。
32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。
- 对低16位数据的存取,不会影响高16位的数据。
- 寄存器EAX通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。可用于乘、 除、输入/输出等操作,使用频率很高;
- 寄存器EBX称为基地址寄存器(Base Register)。它可作为存储器指针来使用;
- 寄存器ECX称为计数寄存器(Count Register)。 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数;
- 寄存器EDX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。
关于乘法和除法注意点:
如果乘法的结果过大,会把结果分别存入EDX和EAX寄存器中,EDX存储高32位数据,EAX存储低32位数据
如果要做除法之前,先要把除数赋值到EDX和EAX寄存器中,然后将结果存入EAX中,余数存入EDX中
变址寄存器
32位CPU有2个32位通用寄存器ESI和EDI。
寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量, 它们可作一般的存储器指针使用。
指针寄存器
32位CPU有2个32位通用寄存器EBP和ESP。
它们主要用于访问堆栈内的存储单元,并且规定:
EBP为基指针(Base Pointer)寄存器,一般作为当前堆栈的最后单元,用它可直接存取堆栈中的数据;
ESP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。
指令指针寄存器
32位CPU把指令指针扩展到32位,并记作EIP
指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。
标志寄存器
32位CPU的标志寄存器为EFLAG
CF:进位标志
OF:溢出标志
SF:符号标志
ZF:零标志
AC:辅助进位标志
PF:奇偶标志
以下使用的是AT&T汇编语法讲解
Intel
用字(word
)来表达16
位数据类型,因此32
位为双字(double words
),64
位为四字(quad words
)
整数寄存器
X86-64
中央处理单元(CPU
)中有一组16
个存储64
位值的通用目的寄存器
初代8086
中有8
个16
位的寄存器(%ax-%sp
)
扩展带IA32
架构时变成了8
个32
位寄存器(%eax-%esp
)
扩展到X86-64
架构时变成16
个64
位寄存器(%rax-%rsp
,%r8-%r15
)前8
个寄存器是做了扩充,后8
个寄存器是新加的
16
位操作可以访问最低的2
个字节,32
位操作可以访问最低的4
个字节,而64
位操作可以访问整个寄存器。
操作数指示符
普通的数据传送如图做了零扩展的数据传送做了符号扩展的数据传送图中给了一个cltq
指令,这条指令没有操作数:它总是以寄存器%eax
作为源,%rax
作为符号扩展结果为目的(它的效果与指令movslq %eax,%rax
完全一致,不过编码更紧凑)
movb
&movsbq
&movzbq
之间的差别:movabsq $0x0011223344556677,%rax # %rax=0011223344556677压入和弹出栈数据
movb $0xAA,%dl # %dl=AA
movb %dl,%al # %RAX=00112233445566AA
movsbq %dl,%rax # %rax=FFFFFFFFFFFFFFAA
movzbq %dl,%rax # %rax=00000000000000AA
#movb指令不改变其他字节
#movsbq将其他7个字节设为全1或全0,由于十六进制A表示二进制值1010,符号扩展会把高位字节都设置为FF
#movzbq讲其他7个字节都设置为0
将一个四字值压人栈中,首先要将栈指针减8
,然后将值写到新的栈顶地址。因此,指令pushq %rbp
的行为等价于下面两条指令:
subq $8,%rsp
movq %rbp,(%rsp)
弹出一个四字的操作包括从栈顶位置读出数据,然后将栈指针加8。因此,指令popq %rax
的行为等价于下面两条指令:
movq (%rsp),%rax算术和逻辑操作
addq $8,%rsp
这些操作被分成4组:加载有效地址,一元操作,二元操作和移位。二元操作有两个操作数,而一元操作有一个操作数。
加载有效地址
指令leaq
实际上是movq
指令的变形,它的指令形式是从内存读数据到寄存器,但实际上它根本没有引用内存。
假设寄存器%rax
的值为X
,%rcx
的值为y
,结果为%rdx
中的值
一元和二元操作
这里没有什么可以写的直接上例题吧
左移指令有两个名字:SAL
和SHL
。两者的效果都是一样的,都是将右边填上0
右移指令有两个名字:SAR
和SHR
。但是效果不同SAR
执行算术移位(填上符号位),SHR
执行逻辑移位(填上0
)
特殊的算术操作
CF
:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数据的溢出。ZF
:零标志。最近的操作得出的结果为0
.SF
:符号标志。最近的操作得到的结果为负数。OF
: 溢出标志。最近的操作导致一个补码溢出–正溢出或负溢出。比较和测试指令
CMP
和TEST
的区别:test
逻辑与运算结果为零,就把ZF
(零标志)置1
cmp
算术减法运算结果为零,就把ZF
(零标志)置1
访问条件码
-
直接跳转:跳转目标是作为指令的一部分编码。写法
jmp .L1
间接跳转:跳转目标是从寄存器或内存位置中读出。写法
jmp *%rax
或者jmp *(%rax)
用寄存器
%rax
中的值作为跳转目标jmp *%rax
用%rax
中的值作为读地址,从内存中读出跳转目标jmp *(%rax)
call
指令有一个目标,指明被调用过程起始的指令地址。同跳转一样,也有直接和间接区别。直接调用的目标是一个标号,而间接调用的目标是*
后面跟一个操作数指示符。
-
是传送指令的变形,用法和跳转指令类似
指针类型不是机器代码的一部分
每个指针都是一个值,这个值是某个指定类型的对象地址。
后续在更新
关于数组,栈,一些像if
,switch
等书上讲的更详细
https://www.cnblogs.com/nufangrensheng/p/3893272.html
https://blog.csdn.net/lpwstr/article/details/78817831
《深入了解计算机系统》
https://wikipedia.org
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK