1、进制之间的转化方法
1.十进制:以0-9这九个数字组成。
2.二进制:由0和1两个数字组成。
3.八进制:由0-7数字组成,不存在8和9。
4.十六进制:由0-9和A-F组成。A-F对应的是10-15。
十进制转二进制:一直除以2直到商为0,再反向取余数。
二进制转十进制:最后一位数开始是2^0,然后一直按照指数递增的方式进行加法运算十进制转八进制:一直除以8直到商为0,再反向取余数。
十进制转八进制:一直除以8直到商为0,再反向取余数。
八进制转十进制:最后一位数开始是8^0,然后一直按照指数递增的方式进行加法运算
十进制转十六进制:一直除以16直到商为0,再反向取余数。
十六进制转十进制:最后一位数开始是16^0,然后一直按照指数递增的方式进行加法运算
二进制转八进制:最后一位数开始取三合一,不够3位前面补0,参照下图顺序排列取得八进制数。
八进制转二进制:操作反过来,取一分三。将1个八进制数分为3个二进制数,参照下图顺序排列取得二进制数。
二进制转十六进制:最后一位数开始取四合一,不够4位前面补0。再用8421码的方法顺序排列取得十六进制数。
十六进制转二进制:所有数拆分成1位,再用8421码的方法顺序排列取得二进制数。高位为0可省略。
2、字长与端序
字
字是表示计算机自然数据单位的术语,在某个特定计算机中,字是其用来一次性处理事务的一个固定长度的位(bit)组,在现代计算机中,一个字等于两个字节(Byte)等于8位(bit),一个字可以表示2^8=256个数。
字长
1.机器字长,是指CPU一次能处理数据的位数,通常与CPU的寄存器位数有关。
2.指令字长,计算机指令字的位数。指令字长取决于从操作码的长度、操作数地址的长度和操作数地址的个数。不同的指令的字长是不同的。
3.存储字长,是一个存储单元存储一串二进制代码(存储字),这串二进制代码的位数称为存储字长。
4.数据字长:计算机数据存储所占用的位数。
通常早期计算机:存储字长 = 指令字长 = 数据字长。故访问一次便可取一条指令或一个数据,随着计算机应用范围的不断扩大,三者可能各不相同,但它们必须是字节的整数倍。
端序
大端:数据的高字节储存在内存的低地址中,低字节储存在内存的高地址中。
小端:数据的低字节储存在内存的低地址中,高字节储存在内存的高地址中。
3、汇编基础指令
数据传送指令:
MOV:将数据从一个位置(寄存器或内存)传送到另一个位置。
XCHG:交换两个位置的数据。
算术和逻辑运算指令:
ADD:将两个操作数相加,并将结果存储在目标位置。
SUB:从第一个操作数中减去第二个操作数,并将结果存储在目标位置。
MUL:将两个操作数相乘,并将结果存储在目标位置。
DIV:将第一个操作数除以第二个操作数,并将商存储在目标位置。
AND:对两个操作数进行逻辑与操作,并将结果存储在目标位置。
OR:对两个操作数进行逻辑或操作,并将结果存储在目标位置。
控制流指令:
JMP:无条件跳转到指定的地址。
JE、JNE、JZ、JNZ:根据比较结果进行条件跳转。
CALL:调用一个函数或子程序,并将返回地址存储在栈中。
RET:从函数或子程序返回,并跳转到调用该函数的地址。
栈操作指令:
PUSH:将数据压入栈顶。
POP:将栈顶数据弹出并存储到指定位置。
比较指令:
CMP:将两个操作数进行比较,并设置标志寄存器以供后续条件跳转指令使用。
字符串操作指令:
MOVS:将一个字符串从源地址复制到目标地址。
LODS:从源地址加载一个字节或一个字,并存储到寄存器中。
STOS:将寄存器中的字节或字存储到目标地址中。
SCAS:将累加器中的字节或字与目标地址中的内容进行比较。
4、BUUCTF Reverse模块前三题
Reverse第一题
先将文件下载使用Exeinfope查看是否有壳
发现无壳,放到IDA中,点击shift+f12,查看所有字符串
这样发现flag{this_Is_a_EaSyRe}
Reverse第二题
下载后同样使用Exeinfope查看是否有壳
发现无壳,放到IDA中,点击shift+f12,查看所有字符串
双击this is the right flag
继续Ctrl+x
点击“ok"之后f5查看伪代码观察一下
根据字符串的提示,str1应该是我们输入的内容,再找一个str2双击,看一下str2是什么
发现为hello_world
将111和48选中之后按R键转化
根据这两行代码可以知道,所有的o都换成了0
则flag{hell0_w0rld}
Reverse第三题
根据PE信息可以看到,该软件是用ELF写的,64位文件
因此,采用IDA 64打开
进入后空格跳转之后用F5查看伪代码。
通过按“R键”可知:105 = 'i'; 114 = 'r'; 49 = '1'。分析第25-26行,比较flag与s2的字符串,才能得到“this is thw right flag!”。
重新回到流程图。
观察右侧流程发现flag被放入了eax寄存器中,于是双击进去观察一下。
又往上看,lag中的“i”和“r”被替换成了“1”,那么最终的flag就是:flag{hack1ng_fo1_fun}。
5、初步了解x86架构
冯诺依曼体系结构是一种将程序指令存储器和数据存储器合并在一起的电脑设计概念结构。它由运算器、控制器、存储器、输入设备和输出设备组成,其中运算器和控制器共同构成中央处理器(CPU)。冯诺依曼体系结构的特点是采用二进制、程序存储和顺序执行。
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果以及一些CPU运行需要的信息。寄存器的容量很小,通常只有几十个字节,因此它只能存储少量的数据。寄存器中存储的数据是二进制格式的,即由0和1组成的数字序列。
堆是为动态分配预留的内存空间。和栈不一样,从堆上分配和重新分配块没有固定模式;你可以在任何时候分配和释放它。这样使得跟踪哪部分堆已经被分配和被释放变的异常复杂;有许多定制的堆分配策略用来为不同的使用模式下调整堆的性能。
栈是为执行线程留出的内存空间。当函数被调用的时候,栈顶为局部变量和一些 bookkeeping 数据预留块。当函数执行完毕,块就没有用了,可能在下次的函数调用的时候再被使用。栈通常用后进先出(LIFO)的方式预留空间;因此最近的保留块(reserved block)通常最先被释放。进程空间布局是指进程在内存中的分区和组织方式。Linux进程的地址空间通常被划分为几个区域,每个区域都有不同的用途。下面是Linux进程地址空间布局的常见区域:
文本段(Text Segment):也称为代码段,它包含了程序的机器代码。这个区域通常是只读的,因为它包含了程序的指令,不允许程序修改它。
数据段(Data Segment):也称为静态数据段,它包含了已经被初始化的全局变量和静态变量。
堆(Heap):它是一个动态分配内存的区域,用来存放程序运行时创建的对象和数据结构。堆的大小可以根据程序的需要增长或缩小。
栈(Stack):它是一个后进先出的数据结构,用来存放函数的局部变量、参数和返回地址。每个函数调用都会在栈上分配一个栈帧(stack frame),函数返回时栈帧被弹出。
内存映射区域(Memory Mapped Region):它是一个用来映射文件或设备到内存的区域,可以实现文件或设备的高效访问。例如,动态链接库(DLL)就是通过内存映射的方式加载到进程的地址空间中的。
练习
.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
- 如果参数小于 0 ,返回 1 ;
- 如果参数等于 0 ,返回 2 ;
- 如果参数在 1 到 29 之间(包括 1 和 29 ),返回 3 ;
- 如果参数在 30 到 69 之间(包括 30 和 69 ),返回 4 ;
- 如果参数大于 69 ,返回 5 。
.text:00001221 6A 3C push 3Ch
.text:00001223 E8 85 FF FF FF call test1
.text:00001228 83 C4 04 add esp, 4
该函数有一个参数,即 3Ch (60) ,它是通过栈传递的。push 3Ch 将参数压入栈中,call test1 调用函数,add esp, 4 将栈指针恢复到原来的位置。
该函数使用了 cdecl 函数调用约定,它是一种常见的函数调用约定,特点是参数从右向左压入栈中,由调用者负责清理栈空间,返回值存放在 eax 寄存器中。
当程序执行到 0x1228 时,eax 的值是 test1 函数的返回值,取决于 test1 函数的内容。这里没有test函数的具体表达,无法确定 eax 的值。
还原为如下代码
// 假设 test1 函数原型是 int test1(int x);
int main(void) {
int i = 60; // 3Ch
int j = test1(i); // 调用 test1 函数
return 0;
}