第七周学习心得与体会
1.不同进制之间的转换方法
1.1进制形式
十进制: 都是以0-9这九个数字组成,不能以0开头。
二进制: 由0和1两个数字组成。
十六进制:由0-9和A-F组成。为了区分于其他数字的区别,开头都是以0x开始。
1.2十进制转二进制
1.2.1整数转换
采用"除2取余,逆序排列"法:
- 首先用2整除一个十进制整数,得到一个商和余数
- 然后再用2去除得到的商,又会得到一个商和余数
- 重复操作,一直到商为小于1时为止
- 然后将得到的所有余数全部排列起来,再将逆序排列
例如:9(十进制)→1001(二进制)
1.2.2小数转换
采用"乘2取整,顺序排列"法:
- 用2乘十进制小数,可以得到积,将积的整数部分取出
- 再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出
- 重复操作,直到积中的小数部分为零,此时0或1为二进制的最后一位,或者达到所要求的精度为止
例题: 0.68D = ______ B(精确到小数点后5位)
如下所示,0.68乘以2,取整,然后再将小数乘以2,取整,直到达到题目要求精度。得到结果:0.10101B.
例如:十进制小数0.68转换为二进制数
具体步骤:
0.68* 2=1.36 -->1
0.36* 2=0.72 -->0
0.72* 2=1.44 -->1
0.44* 2=0.88–>0
0.88* 2=1.76 -->1
已经达到了题目要求的精度,最后将取出的整数部分顺序输出即可
则为:0.68D–>0.10101B
1.3二进制转十进制
1.3.1整数转换
二进制转为十进制要从右到左用二进制的每个数去乘以2的相应次方,然后把得到的数字相加
1.3.2小数转换
小数部分从小数点后一位指数为-1开始算起,以后依次为-2、-3……
例题: 25.68D = ______ H(精确到小数点后3位)
解析:如下图所示,整数部分除以16取余数,直到无法整除。小数部分0.68乘以16,取整,然后再将小数乘以16,取整,直到达到题目要求精度。得到结果:19.ae1H.
(1)整数部分
25/16=1 -->9
1/16=0 -->1
倒序输出为:19
(2)小数部分
0.68* 16=10.88 -->a(即十进制中的10)
0.88* 16=14.08 -->e
0.08* 16=1.28 -->1
已经达到了要求的精度,顺序输出为:ae1
则:25.68D -->19.ae1H
1.4十进制转十六进制
1.4.1整数转换
十进制小数转换成十六进制小数采用 “乘16取整,顺序输出” 法
例如:796–>31c
需要注意的是,十六进制数是由0-9和A-F(或者a-f)组成的,A相当于十进制中的10,B相当于11,依次类推,F相当与15,上述事例中取得的余数12即为十六进制中的c
1.4.2小数转换
原理:十进制小数转换成十六进制小数采用 “乘16取整,顺序输出” 法。
例题: 25.68D = ______ H(精确到小数点后3位)
如下图所示,整数部分除以16取余数,直到无法整除。小数部分0.68乘以16,取整,然后再将小数乘以16,取整,直到达到题目要求精度。得到结果:19.ae1H.
(1)整数部分
25/16=1 -->9
1/16=0 -->1
倒序输出为:19
(2)小数部分
0.68* 16=10.88 -->a(即十进制中的10)
0.88* 16=14.08 -->e
0.08* 16=1.28 -->1
已经达到了要求的精度,顺序输出为:ae1
则:25.68D -->19.ae1H
总结:小数部分转换原理都是乘进制数取整数部分,再将整数部分顺序输出。
1.5十六进制转十进制
原理:整数运算一样,小数部分换成16即可
具体方法步骤如下:
例:1A6.3B8=1* 16^2+A* 16 ^1+6* 16 ^0 +3* 16 ^(-1)+B* 16 ^(-2)+8* 16 ^(-3) =422.232422
总结:十六进制转换为十进制当中的整数部分从右往左指数从0开始递增,小数部分从左往右从-1开始递减。
2.字长与端序
2.1字
字是表示计算机自然数据单位的术语,在某个特定计算机中,字是其用来一次性处理事务的一个固定长度的位(bit)组,在现代计算机中,一个字等于两个字节(Byte)等于8位(bit)。
2.2字长
1.机器字长,是指CPU一次能处理数据的位数,通常与CPU的寄存器位数有关。
2.指令字长,计算机指令字的位数。指令字长取决于从操作码的长度、操作数地址的长度和操作数地址的个数。不同的指令的字长是不同的。
3.存储字长,是一个存储单元存储一串二进制代码(存储字),这串二进制代码的位数称为存储字长。
4.数据字长:计算机数据存储所占用的位数。
通常早期计算机:存储字长 = 指令字长 = 数据字长。故访问一次便可取一条指令或一个数据,随着计算机应用范围的不断扩大,三者可能各不相同,但它们必须是字节的整数倍。
2.3端序
大端:数据的高字节储存在内存的低地址中,低字节储存在内存的高地址中。
小端:数据的低字节储存在内存的低地址中,高字节储存在内存的高地址中。
3.x68架构
x86架构是一种基于CISC(复杂指令集计算机)的处理器架构,最初是为个人电脑设计的。x86处理器具有高性能和广泛的软件支持等优点,因此在个人电脑、服务器等领域得到广泛应用。
3.1冯诺依曼体系结构
3.1.1介绍
冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同。
1945年,冯•诺依曼联合计算机科学家们提出了计算机系统结构具体设计的报告,其遵循图灵机的设计,并且还提出用电子元件构造计算机,同时也约定了用二进制来进行计算和存储;体系结构将计算机系统具体定义为五个部分,分别是运算器,控制器,存储器,输入设备,输出设备;其中运算器与控制器并称为中央处理器(CPU),存储器也就是我们常说的内存,常见的输出设备有键盘,鼠标,网卡,硬盘等;常见的输出设备有显示器,音响,硬盘等;我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
3.1.2特点
现代计算机发展所遵循的基本结构形式始终是冯·诺依曼机结构。这种结构特点是“程序存储,共享数据,顺序执行”,需要 CPU从存储器取出指令和数据进行相应的计算。 主要特点有:
- 单处理机结构,机器以运算器为中心
- 采用程序存储思想
- 指令和数据一样可以参与运算
- 数据以二进制表示
- 将软件和硬件完全分离
- 指令由操作码和操作数组成
- 指令顺序执行
3.1.3局限性
CPU 与共享存储器间的信息交换的速度成为影响系统性能的主要因素,而信息交换速度的提高又受制于存储元件的速度、存储器的性能和结构等诸多条件。
3.2寄存器
3.2.1基本含义
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。
3.2.2通俗解释
-
寄存器就像你的手袋,可以放一些最常用或马上要用的东西,比如钱包、手机、钥匙等。它的容量有限,但装东西、取东西很便捷。
-
内存就像相当于你私家车,可以放更多的东西,比如相亲时临时准备的衣服、裙子、化妆品等,空间大但去取东西会浪费更多的时间。
-
车钥匙相当于“指针变量(内存地址)”。
-
当你去约会时,你在牌子为“x86_64”的手袋,提前将手机、电影票、口红、一些零花钱、车的钥匙(它们相当于函数的参数)放入其中。
-
对象提出什么问题,你从手袋拿出对应的物品,这相当于从寄存器读取数据。
-
对象送个你小礼物,你放入手袋中临时存放。这相当于将数据值写入寄存器。
更多关于寄存器的知识请点击:认识寄存器
3.3堆
前置知识:二叉树
- 满足以下两个条件的树就是二叉树:
- 本身是有序树
- 树中包含的各个节点的度不能超过 2,即只能是 0、1 或者 2
- 二叉树的性质:
- 二叉树中,第 i 层最多有 2^( i-1)个结点。
- 如果二叉树的深度为 K,那么此二叉树最多有 2^K-1 个结点。
- 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1
- 如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树
- 如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树
3.3.1堆的概念
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,即是一种顺序储存结构的完全二叉树
3.3.2堆的性质
- 堆中某个结点的值总是不大于或不小于其父结点的值
- 堆总是一棵完全二叉树
- 除了根结点和最后一个左子结点可以没有兄弟结点,其他结点必须有兄弟结点
3.3.3最大堆和最小堆
- 最大堆:根结点的键值是所有堆结点键值中最大者,且每个结点的值都比其孩子的值大
- 最小堆:根结点的键值是所有堆结点键值中最小者,且每个结点的值都比其孩子的值小
3.4栈
前置知识:线性表
- 线性,我们可以理解为,相关性,即数据之间是相关的。说的官方一点就是数据元素之间存在一对一的线性关系。而数据元素之间不一定是物理地址上的连续才是相关的,线性的,主要看我们如何利用,所以,线性结构的存储可以分为顺序存储和链式存储
- 数据元素在物理地址上不是连续存放的,而我们利用的它其中的时候可以看成用一条链串起来。所以,我们可以把它称为链式存储。这个线性表称为链表
3.4.1栈的概念
- 栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方
- 栈是限制插入和删除只能在一个位置上进行的线性表
3.4.2栈的性质
- 只能从栈顶进行插入和删除,栈顶是会随着栈里面的数据元素的多少变化的,是动态变化的,一直指向栈的最上面一个数据元素
- 栈底的元素是不允许直接插入和删除的
- 向栈中存放数据叫做压栈,取出数据叫做弹栈
- 栈中的数据“先进后出,后进先出”
3.4.3动态栈和静态栈
- 可以动态申请内存的叫动态栈,比如链表实现的栈,或者数组借助malloc实现的动态栈
- 静态栈是指数组不借用malloc,把栈的大小固定好了,叫做静态栈
malloc函数:动态内存分配函数,用于申请一块连续的指定大小的内存块区域以void类型返回分配的内存区域地址
3.5进程空间布局
当把一个可执行文件加载到内存后,就变成了一个进程
通常进程的内存地址空间可分为以下几个部分:栈、堆、BSS段、数据段、代码段
各内存段说明:
- 栈:栈用于存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中
- 堆(heap):堆用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。若程序员不释放,则会发生内存泄漏
- bss段(bss segment):通常是指用来存放程序中未初始化的全局变量的一块内存区域。bss段属于静态内存分配
- data段(data segment):通常是指用来存放程序中已初始化的全局变量的一块内存区域。data段属于静态内存分配
- 代码段(code segment/text segment):通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等
4.BUUCTF
4.1 easyre
首先我们将程序解压后使用ida打开
再使用shift+F12直接查看exe文件的字符串,即可得到flag
4.2 reverse 1
首先依旧是将程序解压后打开,使用shift+F12查看文件字符串
看到关键字串“This is the right flag!”,双击进入后,ctrl+x打开程序框图
按tab打开伪代码
将‘o’转换为‘0’后得到flag
4.3 reverse 2
前置步骤与前两题相同,不重复论述,直接查看伪代码
找到flag存储位置后,略微转换,得到flag
但是此时输入显示并不正确,我们需要将flag带回伪代码中处理一遍,即可得到真正的flag
5.汇编基础
5.1汇编基础指令
-
MOV:将数据从一个位置复制到另一个位置。例如,
MOV AX, BX
将BX寄存器的内容复制到AX寄存器。 -
ADD:将两个值相加并将结果存储在指定的位置。例如,
ADD AX, BX
将AX寄存器和BX寄存器的内容相加,并将结果存储在AX寄存器中。 -
SUB:从一个值中减去另一个值,并将结果存储在指定的位置。例如,
SUB AX, BX
将BX寄存器的内容从AX寄存器的内容中减去,并将结果存储在AX寄存器中。 -
JMP:无条件跳转到指定的位置。例如,
JMP label
将程序控制转移到标签处的指令。 -
CMP:比较两个值,并根据比较结果设置标志位。例如,
CMP AX, BX
将比较AX寄存器和BX寄存器的内容,并根据比较结果设置标志位。 -
JE、JNE、JG、JL:根据先前的比较结果进行条件跳转。例如,
JE label
将在相等时跳转到标签处的指令。 -
INC:将指定位置的值加1。例如,
INC AX
将AX寄存器的内容加1。 -
DEC:将指定位置的值减1。例如,
DEC AX
将AX寄存器的内容减1。 -
Call:将当前的程序执行流程转移到指定的子程序,并在子程序执行完成后返回到调用点。CALL 指令通常与 RET(返回)指令配合使用,RET 指令用于从子程序返回到调用点。当执行 CALL 指令时,会将当前的指令地址(返回地址)压入堆栈,并跳转到指定的子程序开始执行。在子程序执行完成后,使用 RET 指令将返回地址从堆栈中弹出,并跳转回调用点继续执行。
5.4练习
练习:
.text:000011B1 var_4= dword ptr -4
.text:000011B1 arg_0= dword ptr 8
.text:000011B1 55 push ebp
.text:000011B2 89 E5 mov ebp, esp
.text:000011B4 83 EC 10 sub esp, 10h
.text:000011C1 C7 45 FC 00 00 00 00 mov [ebp+var_4], 0
.text:000011C8 83 7D 08 00 cmp [ebp+arg_0], 0
.text:000011CC 79 09 jns short loc_11D7
.text:000011CC
.text:000011CE C7 45 FC 01 00 00 00 mov [ebp+var_4], 1
.text:000011D5 EB 34 jmp short loc_120B
.text:000011D5
.text:000011D7 loc_11D7:
.text:000011D7 83 7D 08 00 cmp [ebp+arg_0], 0
.text:000011DB 75 09 jnz short loc_11E6
.text:000011DB
.text:000011DD C7 45 FC 02 00 00 00 mov [ebp+var_4], 2
.text:000011E4 EB 25 jmp short loc_120B
.text:000011E4
.text:000011E6 loc_11E6:
.text:000011E6 83 7D 08 1D cmp [ebp+arg_0], 1Dh
.text:000011EA 7F 09 jg short loc_11F5
.text:000011EA
.text:000011EC C7 45 FC 03 00 00 00 mov [ebp+var_4], 3
.text:000011F3 EB 16 jmp short loc_120B
.text:000011F3
.text:000011F5 loc_11F5:
.text:000011F5 83 7D 08 45 cmp [ebp+arg_0], 45h
.text:000011F9 7F 09 jg short loc_1204
.text:000011F9
.text:000011FB C7 45 FC 04 00 00 00 mov [ebp+var_4], 4
.text:00001202 EB 07 jmp short loc_120B
.text:00001202
.text:00001204 loc_1204:
.text:00001204 C7 45 FC 05 00 00 00 mov [ebp+var_4], 5
.text:00001204
.text:0000120B loc_120B:
.text:0000120B 8B 45 FC mov eax, [ebp+var_4]
.text:0000120E C9 leave
.text:0000120F C3 retn
main 函数按照如下方式进行调用:
.text:00001221 6A 3C push 3Ch
.text:00001223 E8 85 FF FF FF call test1
.text:00001228 83 C4 04 add esp, 4
- 该函数有几个参数
- 该函数使用了什么函数调用约定
- 当程序执行到 0x1228 时,eax 的值是多少
- 你能还原出 C 代码吗
解答:
这段提供的汇编代码是x86汇编语言的片段,可能是一个函数的一部分。让我们逐步解释这段代码:
1.函数前言:
.text:000011B1 var_4= dword ptr -4
.text:000011B1 arg_0= dword ptr 8
.text:000011B1 55 push ebp
.text:000011B2 89 E5 mov ebp, esp
.text:000011B4 83 EC 10 sub esp, 10h
- 函数前言以将基址指针(ebp)推送到栈上开始。
- 然后将ebp寄存器设置为当前栈指针(esp)的值。
- 通过从栈指针减去16字节(10h)的值,在栈上为局部变量保留空间。
函数开头的几行指令是典型的函数前奏,设置栈帧和局部变量。
2.变量初始化:
.text:000011C1 C7 45 FC 00 00 00 00 mov [ebp+var_4], 0
- 使用值 0 初始化 [ebp+var_4] 处的变量。
3.条件语句:
.text:000011C8 83 7D 08 00 cmp [ebp+arg_0], 0
-
在 83 7D 08 00 处,比较参数 arg_0 的值是否大于等于0
loc_120B .text:000011D5 .text:000011D7 loc_11D7: .text:000011D7 83 7D 08 00 cmp [ebp+arg_0], 0 .text:000011DB 75 09 jnz short loc_11E6 .text:000011DB .text:000011DD C7 45 FC 02 00 00 00 mov [ebp+var_4], 2 .text:000011E4 EB 25 jmp short loc_120B .text:000011E4 .text:000011E6 loc_11E6: .text:000011E6 83 7D 08 1D cmp [ebp+arg_0], 1Dh .text:000011EA 7F 09 jg short loc_11F5 .text:000011EA .text:000011EC C7 45 FC 03 00 00 00 mov [ebp+var_4], 3 .text:000011F3 EB 16 jmp short loc_120B .text:000011F3 .text:000011F5 loc_11F5: .text:000011F5 83 7D 08 45 cmp [ebp+arg_0], 45h .text:000011F9 7F 09 jg short loc_1204 .text:000011F9 .text:000011FB C7 45 FC 04 00 00 00 mov [ebp+var_4], 4 .text:00001202 EB 07 jmp short loc_120B .text:00001202 .text:00001204 loc_1204: .text:00001204 C7 45 FC 05 00 00 00 mov [ebp+var_4], 5 .text:00001204 .text:0000120B
-
如果小于0(jns 的条件不满足),则在 C7 45 FC 01 00 00 00 处将 var_4 设置为1,并跳转到 loc_120B。
-
如果大于等于0,进入 loc_11D7,在 83 7D 08 00 处再次比较参数 arg_0 是否等于0。
-
如果不等于0,将 var_4 设置为2,并跳转到 loc_120B
-
如果等于0,进入 loc_11E6,在 83 7D 08 1D 处比较参数 arg_0 是否大于等于0x1D。
-
如果小于等于0x1D,将 var_4 设置为3,并跳转到 loc_120B。
-
如果大于0x1D,进入 loc_11F5,在 83 7D 08 45 处比较参数 arg_0 是否大于0x45。
-
如果小于等于0x45,将 var_4 设置为4,并跳转到 loc_120B。
-
如果大于0x45,将 var_4 设置为5。
4.函数尾声:
.text:0000120B 8B 45 FC mov eax, [ebp+var_4]
.text:0000120E C9 leave
.text:0000120F C3 retn
- 将 [ebp+var_4] 处的值移动到 eax 寄存器。
- 恢复栈和基址指针到它们的先前值(函数尾声)。
- 从函数返回。
回答问题
- 该函数有1个参数,即 arg_0。
- 该函数使用的是 cdecl(调用者清理栈)调用约定,因为在函数返回之前,调用者负责清理堆栈。
- 当程序执行到 0x1228 时,eax 的值是 5,因为 var_4 最后被设置为 5。
- 下面是根据汇编代码还原的 C 代码:
cint test1(int arg_0) {
int var_4 = 0;
if (arg_0 < 0) {
var_4 = 1;
} else {
if (arg_0 != 0) {
var_4 = 2;
} else {
if (arg_0 >= 0x1D) {
var_4 = 3;
} else {
if (arg_0 > 0x45) {
var_4 = 4;
} else {
var_4 = 5;
}
}
}
}
return var_4;
}