第七周学习内容

进制转换

二进制与十进制

  • 1.二进制转十进制要从右到左用二进制的每个数去乘以2的相应次方,小数点后则是从左往右。在这里插入图片描述

如果首位是0就表示正整数,如果首位是1则表示负整数,正整数可以直接换算,负整数则需要先取反再换算。
例:
如果11101011想转为*负的十进制,因为最高位是1,所以先减一取反 00010101,然后计算出00010101对应的十进制为21,所以11101011最终对应的十进制为 -21。

  • 2.十进制转二进制分为整数转二进制,和小数转二进制
    • 1)整数转二进制:

1.首先用2整除一个十进制整数,得到一个商和余数
2.然后再用2去除得到的商,又会得到一个商和余数
3.重复操作,一直到商为小于1时为止
4.然后将得到的所有余数全部排列起来,再将它反过来(逆序排列),切记一定要反过来!

例:在这里插入图片描述

    • 2)小数转二进制:采用"乘2取整,顺序排列"法

1.用2乘十进制小数,可以得到积,将积的整数部分取出
2.再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出
3.重复操作,直到积中的小数部分为零,此时0或1为二进制的最后一位,或者达到所要求的精度为止

例:将0.125转换为二进制:
在这里插入图片描述
如果小数的整数部分有大于0的整数时,将整数部分和小数部分先单独转为二进制,再合在一起
在这里插入图片描述
十进制与十六进制

  • 1.十进制转十六进制:
    在这里插入图片描述

  • 2.十六进制转十进制:

例:1A6.3B8=1* 16^2+A* 16 ^1+6* 16 ^0 +3* 16 ^(-1)+B* 16 ^(-2)+8* 16 ^(-3) =422.232422

字长与端序

字长
1.位(bit):简记为b,也称为比特,是计算机存储数据的最小单位。一个二进制位只能表示0或1。
2.字节(Byte):简记为B。字节是存储信息的基本单位。规定1B=8bit 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB
3.字:cpu一次处理的数据,一个字通常由一个字节或若干个字节组成。
4.字长:

字长是CPU的主要技术指标之一,指的是CPU一次能并行处理的二进制位数,字长总是8的整数倍,通常PC机的字长为16位。
PC机可以通过编程的方法来处理任意大小的数字,但数字越大,PC机就要花越长的时间来计算。PC机在一次操作中能处理的最大数字是由PC机的字长确定的。
我们先来看一下人脑是如何进行计算的,例如5×6则立即可以得到答案是30,但对于55×66,就不可能立即得到正确的答案,这就是说55或66已走出了人脑的“字长”,这是为了得出结果,就必须把复杂的问题(如55×66)分解成易于处理的问题(如55×66可分解为50×60,50×6,5×60,5×6),然后再综合起来,得出结果。
同样PC机也是这样处理问题的,一台16位字长的PC机,可以直接处理2的16次方(65536)之内的数字,对于超过65536的数字就需要分解的方法来处理。32位pc机比16位机优越的原因就在于它在一次操作中能处理的数字大,32位字长的PC机能直接处理的数字高达40亿(2的32次方),能处理的的数字越大,则操作的次数就越少,从而系统的效率也就越高。
CPU大多是64位的,但大多都以32位字长运行,都没能展示它的字长的优越性,因为它必须与64位软件(如64位的操作系统等)相辅相成,也就是说,字长受软件系统的制约,例如,在32位软件系统中64位字长的CPU只能当32位用。

端序:连续的字节要按什么样的顺序排列和解释,这就涉及到端序的范畴了。 字节的排列方式有两个通用规则: 大端序(Big-Endian)、小端序(Little-Endian)

  • 大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种 排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。

  • 小端序(Little-Endian)将数据的低位字节放在较小的地址处,高位放在较大的地址处。小端序与 人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地 址向高地址方向进行读取的。

例:

数值:0x12345678
存放在内存中:
78 56 34 12 小端序
12 34 56 78 大端序

IDA

下载:https://hex-rays.com/ida-free/

exeinfope

下载:https://www.52pojie.cn/thread-1837928-1-1.html

DIE

下载:https://github.com/horsicq/DIE-engine/releases

汇编基础指令

汇编语言使用实例:

.text:000011B1 var_4= dword ptr -4 是一个变量的定义。这个定义指定了一个名为 var_4 的变量,它是一个 dword 类型的指针,指向相对于当前栈帧的偏移量为 -4 的位置。

  1. 数据传送指令:
    mov:将数据从一个位置复制到另一个位置。
    push:将数据压入栈顶。
    pop:从栈顶弹出数据。

  2. 算术和逻辑指令:
    add:加法运算。
    sub:减法运算。
    mul:乘法运算。
    div:除法运算。
    and:按位与运算。
    or:按位或运算。
    xor:按位异或运算。
    not:按位取反运算。

  3. 控制转移指令:
    jmp:无条件跳转到指定地址。
    je、jne、jz、jnz:条件跳转指令,根据标志位判断是否跳转。
    call:调用子程序。
    ret:从子程序返回。

  4. 条件指令:
    cmp:比较两个值,并设置标志位。
    test:对两个值进行按位与操作,并设置标志位。

  5. 标志位操作指令:
    clc:清除进位标志位。
    stc:设置进位标志位。
    cld:清除方向标志位。
    std:设置方向标志位。

BUUCTF

easyre

使用IDA打开应用程序在这里插入图片描述
主页面中按shift+F12查找主函数,得到flag在这里插入图片描述

reserve1

下载文件并解压,用IDA打开
按shift+F12查找主函数,发现有可能是flag的位置,双击this is the right flag!,查看包含该字符串的位置在这里插入图片描述
双击红框处(红框处为引用该字符串的位置)在这里插入图片描述
跳转到下面这个界面(这个界面是流程视图,可以看出程序的执行流程)
在这里插入图片描述
这个界面比较难看懂,所以按F5一键反汇编(查看伪代码)
先找到关键字“this is the right flag!”,不难推测出sub_1400111D1可能是类似printf的作用​在这里插入图片描述
百度可知strncmp(str1,str2,v5)的意思是: 把字符串str1和字符串str2进行比较,最多比较前v5个字节
继续分析代码
根据前文已推出sub 1400111D1是类似于printf的作用sub 14001128F也能很明显推出是类似于printf的作用
所以此处的代码就表示: 输入Str1的字符串内容就是flag在这里插入图片描述
在这里插入图片描述
因为得到的最终结果是this is the right flag!所以可知Str1和Str2是相等的
for循环遍历Str2
str2中ascii码为111的字符会被转换成ascii码为48的字符
(IDA中,选中数字后按键盘”“R”键,即可将数字转换为字符)
在这里插入图片描述
在这里插入图片描述
接下来查看Str2字符串的内容
鼠标选中Str2 ,按下键盘“X”键查看Str2这个变量的交叉引用(或者选中Str2后,右键选择Jump to xref…)在这里插入图片描述
在这里插入图片描述
可以知道Str2字符串的内容是{hello_world},所以将该字符串中的o改为0,即可得到正确的flag

reserve2

下载压缩包并解压
下载后,发现这个文件没有后缀,先自行添加一个.txt后缀打开,由开头可知该文件为ELF文件在这里插入图片描述
将文件放入Exeinfo_PE中,可知该文件为64位在这里插入图片描述
用IDA打开,按住shift+F12查看主函数,发现有可能是flag的位置,双击在这里插入图片描述
双击关键词,查看引用该字符串的地方
在这里插入图片描述
跳转到流程图在这里插入图片描述
按F5查看伪代码在这里插入图片描述
分析代码,因为最后输出的是this is the right flag! ,可知&flag与s2是相同的在这里插入图片描述
双击flag,查看flag字符串的内容
在这里插入图片描述
可知flag字符串的内容是{hacking_for_fun},然后将字符串中的i和r都改成1
得到flag:flag{hack1ng_fo1_fun},提交

x86架构

X86架构(The X86 architecture)是微处理器执行的计算机语言指令集,指一个intel通用计算机系列的标准编号缩写,也标识一套通用的计算机指令集合。
扩: ARM架构
冯诺依曼体系结构:
冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同,如英特尔公司的8086中央处理器的程序指令和数据都是16位宽。
数学家冯·诺依曼提出了计算机制造的三个基本原则,即采用二进制逻辑、程序存储执行以及计算机由五个部分组成(运算器、控制器、存储器、输入设备、输出设备),这套理论被称为冯·诺依曼体系结构。
在这里插入图片描述
寄存器
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。
在计算机领域,寄存器是CPU内部的元件,包括通用寄存器、专用寄存器和控制寄存器。寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。

————————————

  • 1. 通用寄存器
    通用寄存器包括了8个16/32位的寄存器:AX/EAX、BX/EBX、CX/ECX、DX/EDX、SP/ESP、BP/EBP、DI/EDI及SI/ESI。其中AX/EAX、BX/EBX、CX/ECX、DX/EDX在一般情况下作为通用的数据寄存器,用来暂时存放计算过程中所用到的操作数、结果或其他信息。它们还可分为两个独立的8位寄存器使用,命名为AL、AH、BL、BH、CL、CH、DL和DH。这4个通用数据寄存器除通用功能外,还有如下专门用途:
    AX/EAX作为累加器用,所以它是算术运算的主要寄存器。在乘除指令中指定用来存放操作数。另外,所有的I/O指令都使用AX或AL与外部设备传送信息。
    BX/EBX在计算存储器地址时,可作为基址寄存器使用。
    CX/ECX常用来保存计数值,如在移位指令、循环指令和串处理指令中用作隐含的计数器。DX在作双字长运算时,可把DX和AX组合在一起存放一个双字长数,DX用来存放高16位数据。此外,对某些I/O操作,DX可用来存放I/O的端口地址。
    SP/ESP、BP/EBP、SI/ESI、DI/EDI四个16/32位寄存器可以象数据寄存器一样在运算过程中存放操作数,但它们只能以字(16/32位)为单位使用。此外,它们更经常的用途是在存储器寻址时,提供偏移地址。因此,它们可称为指针或变址寄存器。
    SP/ESP称为堆栈指针寄存器,用来指出栈顶的偏移地址。
    BP/EBP称为基址指针寄存器,在寻址时作为基地址寄存器使用,但它必须与堆栈段寄存器SS联用来确定堆栈段中的存储单元地址。

  • 2.指针和变址寄存器
    BP( Base Pointer Register):基址指针寄存器。
    SP( Stack Pointer Register):堆栈指针寄存器。
    SI( Source Index Register):源变址寄存器。
    DI( Destination Index Register):目的变址寄存器。
    这组寄存器存放的内容是某一段内地址偏移量,用来形成操作数地址,主要在堆栈操作和变址运算中使用。BP和SP寄存器称为指针寄存器,与SS联用,为访问现行堆栈段提供方便。通常BP寄存器在间接寻址中使用,操作数在堆栈段中,由SS段寄存器与BP组合形成操作数地址即BP中存放现行堆栈段中一个数据区的“基址”的偏移量,所以称BP寄存器为基址指针。
    SP寄存器在堆栈操作中使用,PUSH和POP指令是从SP寄存器得到现行堆栈段的段内地址偏移量,所以称SP寄存器为堆栈指针,SP始终指向栈顶。
    寄存器SI和DI称为变址寄存器,通常与DS一起使用,为访问现行数据段提供段内地址偏移量。在串指令中,其中源操作数的偏移量存放在SⅠ中,目的操作数的偏移量存放在DI中,SI和DI的作用不能互换,否则传送地址相反。在串指令中,SI、DI均为隐含寻址,此时,SI和DS联用,Dl和ES联用。

  • 3.段寄存器
    8086/8088CPU可直接寻址1MB的存储器空间,直接寻址需要20位地址码,而所有内部寄存器都是16位的,只能直接寻址6KB,因此采用分段技术来解决。将1MB的存储空间分成若干逻辑段,每段最长64KB,这些逻辑段在整个存储空间中可浮动。
    8086/8088CPU内部设置了4个16位段寄存器,它们分别是代码段寄存器CS、数据段寄存器DS、堆栈段寄存器SS、附加段寄存器ES、由它们给出相应逻辑段的首地址,称为“段基址”。段基址与段内偏移地址组合形成20位物理地址,段内偏移地址可以存放在寄存器中,也可以存放在存储器中。
    例如:代码段寄存器CS存放当前代码段基地址,IP指令指针寄存器存放了下一条要执行指令的段内偏移地址,其中CS=2000H,IP=001AH。通过组合,形成20位存储单元的寻址地址为2001AH。
    代码段内存放可执行的指令代码,数据段和附加段内存放操作的数据,通常操作数在现行数据段中,而在串指令中,目的操作数指明必须在现行附加段中。堆栈段开辟为程序执行中所要用的堆栈区,采用先进后出的方式访问它。各个段寄存器指明了一个规定的现行段,各段寄存器不可互换使用。程序较小时,代码段、数据段、堆栈段可放在一个段内,即包含在64KB之内,而当程序或数据量较大时,超过了64KB,那么可以定义多个代码段或数据段、堆栈段、附加段。现行段由段寄存器指明段地址,使用中可以修改段寄存器内容,指向其他段。有时为了明确起见,可在指令前加上段超越的前缀,以指定操作数所在段 [5]

  • 4.指令指针寄存器IP
    8086/8088CPU中设置了一个16位指令指针寄存器IP,用来存放将要执行的下一条指令在现行代码段中的偏移地址。程序运行中,它由BIU自动修改,使IP始终指向下一条将要执行的指令的地址,因此它是用来控制指令序列的执行流程的,是一个重要的寄存器。8086程序不能直接访问IP,但可以通过某些指令修改IP的内容。例如,当遇到中断指令或调用子程序指令时,8086自动调整IP的内容,将IP中下一条将要执行的指令地址偏移量入栈保护,待中断程序执行完毕或子程序返回时,可将保护的内容从堆栈中弹出到IP,使主程序继续运行。在跳转指令时,则将新的跳转目标地址送入IP,改变它的内容,实现了程序的转移。

  • 5.标志寄存器FR
    标志寄存器FR也称程序状态字寄存器。
    FR是16位寄存器,其中有9位有效位用来存放状态标志和控制标志。状态标志共6位,CF、PF、AF、ZF、SF和OF,用于寄存程序运行的状态信息,这些标志往往用作后续指令判断的依据。控制标志
    ————————————————

进程空间布局:
进程空间布局是指操作系统在运行一个程序时,为该程序分配的内存空间的组织结构。每个运行的进程都有自己的独立的内存空间,其中包含了代码、数据、堆、栈等不同的区域。

一般而言,进程空间布局包括以下几个主要的区域:

  • 1.代码段(Text Segment):也称为只读代码段,存储可执行程序的机器指令,通常是只读的,以防止意外修改代码。这部分内存通常是共享的,多个进程可以共享同一份代码。
  • 2.数据段(Data Segment):存储初始化的全局变量和静态变量,包括全局变量和静态数据。
  • 3.BSS段(Block Started by Symbol):存储未初始化的全局变量和静态变量,这些变量在程序开始执行前会被自动初始化为零或空值。
  • 4.堆(Heap):用于动态分配内存,例如使用malloc()或new来申请的内存。堆的大小和位置在程序运行时可以动态改变。
  • 5.栈(Stack):存储函数调用和局部变量等临时数据。每当函数被调用时,栈会分配一块内存用于存储函数的参数、返回地址和局部变量等信息。栈的大小在程序编译或运行时确定。
  • 6.环境变量区域:存储程序运行时使用的环境变量。
  • 7.动态链接库区域:存储程序所使用的动态链接库的代码和数据。

这些区域在进程空间中按照一定的布局顺序排列,并且在运行时可以根据需要进行动态调整。进程空间布局的具体实现可能因操作系统的类型和架构而有所差异。
在这里插入图片描述
在这里插入图片描述

堆与栈

栈(Stack):

栈是一种线性数据结构,采用后进先出(LIFO)的原则。
栈用于存储函数调用时的局部变量、函数参数、返回地址和其他与函数调用相关的信息。
栈的大小是固定的,在程序运行时会动态地分配和释放栈帧(Stack Frame)。
栈帧是函数在执行期间使用的一块内存区域,用于保存局部变量和其他与函数调用相关的信息。
栈的分配和释放是由编译器自动完成的,无需手动管理。

堆(Heap):

堆是一种动态分配的内存区域,用于存储程序运行时的动态数据。
堆的大小是可变的,可以根据需要动态地分配和释放内存。
堆的分配和释放是由程序员显式地进行管理的,需要手动分配内存和释放内存。
堆的分配通常使用动态内存分配函数(如malloc、new等),释放使用相应的释放函数(如free、delete等)。
堆上的数据可以在程序的任何地方被访问,具有较长的生命周期。

栈和堆在内存中的位置通常是相对的,栈位于高地址端,而堆位于低地址端。它们的使用方式和特性不同,适用于不同的数据存储需求。

需要注意的是,堆和栈是两个不同的概念,与数据结构中的堆(Heap)和栈(Stack)并不完全相同。在计算机科学中,堆和栈的概念有时会有重叠,但在内存管理的上下文中,它们指的是不同的东西。

练习

.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

  1. 该函数有几个参数
  2. 该函数使用了什么函数调用约定
  3. 当程序执行到 0x1228 时,eax 的值是多少
  4. 你能还原出 C 代码吗

.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

这行代码将值0存储到位于 [ebp+var_4] 的位置,其中 var_4 是一个局部变量。

.text:000011C8 83 7D 08 00 cmp [ebp+arg_0], 0
.text:000011CC 79 09 jns short loc_11D7

这两行代码比较位于 [ebp+arg_0] 的值与0,然后根据结果进行条件跳转。arg_0 是一个函数参数,它在调用函数时通过栈传递。

.text:000011CE C7 45 FC 01 00 00 00 mov [ebp+var_4], 1

这行代码将值1存储到位于 [ebp+var_4] 的位置。

类似地,后面的代码根据不同的条件将不同的值存储到 [ebp+var_4] 中。

最后,代码结束部分如下:

.text:0000120B loc_120B:
.text:0000120B 8B 45 FC mov eax, [ebp+var_4]
.text:0000120E C9 leave
.text:0000120F C3 retn

在这里,mov eax, [ebp+var_4] 将 [ebp+var_4] 的值加载到寄存器 eax 中。然后,leave 指令用于恢复栈帧,retn 指令用于返回函数结果。

.text:00001221 6A 3C push 3Ch

这行代码将值 3Ch 压入栈中,作为参数传递给函数。

.text:00001223 E8 85 FF FF FF call test1

这行代码调用函数 test1。E8 是相对跳转指令,后面的 85 FF FF FF 是一个相对偏移量,指示跳转到 test1 函数的位置。

.text:00001228 83 C4 04 add esp, 4

这行代码将栈指针 esp 增加 4,以清除调用函数时压入栈中的参数。

综上所述,这段汇编代码将值 3Ch 压入栈中作为参数,然后调用函数 test1,最后通过 add esp, 4 清除栈中的参数。

  1. 该函数有一个参数,参数的值存储在函数调用前的栈顶位置(esp)。
  2. 该函数使用的函数调用约定是 cdecl 调用约定。在 cdecl 调用约定中,参数通过栈传递,由调用者负责清理栈空间。
  3. 当程序执行到 0x1228 时,eax 的值取决于参数的值。根据给出的汇编代码,参数的值是 0x3C(60),因此 eax 的值将是 5。
  4. 下面是对应的 C 代码的还原:

int 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;
}

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值