20145221 《信息安全系统设计基础》第5周学习总结
程序的机器级表示
历史观点
- Intel处理器系列:俗称x86,开始时是第一代单芯片、16位微处理器之一。
- DOS时代的平坦模式,不区分用户空间和内核空间,很不安全;
- 8086的分段模式;
- IA32的带保护模式的平坦模式
- 每个后继处理器的设计都是后向兼容的,可以保证较早版本上编译的代码在较新的处理器上运行。
程序编码
- GCC将源代码转化为可执行代码的步骤:
- C预处理器——扩展源代码-生成.i文件
- 编译器——产生两个源代码的汇编代码-——生成.s文件
- 汇编器——将汇编代码转化成二进制目标代码——生成.o文件
- 链接器——产生可执行代码文件
- 两种抽象:
- 指令集结构ISA:是机器级程序的格式和行为,定义了处理器状态、指令的格式,以及每条指令对状态的影响。
- 机器级程序使用的存储器地址是虚拟地址,看上去是一个非常大的字节数组,实际上是将多个硬件存储器和操作系统软件组合起来。
机器级代码
- 指令集结构ISA
- 是机器级程序的格式和行为,定义了处理器状态、指令的格式,以及每条指令对状态的影响
- 机器级程序使用的存储器地址是虚拟地址
- 看上去是一个非常大的字节数组,实际上是将多个硬件存储器和操作系统软件组合起来
- 一些通常对C语言程序员隐藏的机器代码在IA32中是可见的:
- 程序计数器(在IA32中,通常称为“PC”,用%eip表示)指示将要执行的下一条指令在存储器中的地址。
- 整数寄存器:包含8个命名的位置,分别存储32位的数值,这些寄存器可以存储地址(对应C语言的指针)或整数数据,有的寄存器被用来记录某些重要的程序状态,其他的寄存器用来保存临时数据,例如过程的局部变量和函数的返回值。
- 条码寄存器:保存着最近执行的算术或逻辑指令的状态信息,他们用来实现控制或数据流中的条件变化。
- 浮点寄存器:一组浮点寄存器存放浮点数据
- 一条机器指令只执行一个非常基本的操作。机器代码只是简单地将存储器看成一个很大的、按字节寻址的数组。
代码示例
- 课本107页代码如下
int accum = 0;
int sum(int x, int y)
{
int t = x + y;
accum += t;
return t;
}
-
反汇编结果
gcc -c code.c
: -
汇编之后的代码
gcc -S code.c
(已删除“.”后的部分): -
前两条:
- pushl %ebp 将寄存器%ebp的内容压入程序栈
- movl %esp,%ebp 得到新栈低,将当前栈顶赋予栈低
-
后两条:
- popl %ebp过程调用结束,恢复旧栈低
- ret 子程序的返回指令
-
二进制文件可以用
od
命令查看,也可以用gdb的x命令查看。有些输出内容过多,可以使用more或less命令结合管道查看,也可以使用输出重定向来查看。od code.o | more
od code.o > code.txt
数据格式
C声明 | 汇编代码后缀 | 大小(字节) |
---|---|---|
char | b- 字节 | 1 |
short | w- 字 | 2 |
int | l- 双字 | 4 |
long | l- 双字 | 4 |
long long int | - | 8 |
char * | l- 双字 | 4 |
float | s- 单精度 | 4 |
double | l- 双精度 | 8 |
long double | t- 扩展精度 | 10/12 |
- IA32不支持64位整数运算
- 大多数GCC生成的汇编代码指令都有一个字符后缀,表明操作数的大小。
访问信息
-
一个IA32的中央处理器单元包含一组8个存储32位数值的寄存器。所有八个寄存器都可以作为16位(字)或32位(双字)来访问:
- %esi,%edi可以用来操纵数组
- %esp,%ebp用来操纵栈帧
- 64位的%rax,32位的%eax,16位的%ax,8位的%ah,%al都是独立的
-
操作数三种类型:
- 立即数,即常数值
- 寄存器,表示某个寄存器的内容
- 存储器,根据计算出来的地址(有效地址)访问某个存储器位置
-
有效地址的计算方式:
Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
控制
设置条件码
- 有两类指令(有8、16和32位形式),它们只设置条件码而不改变任何其他寄存器
CMP
指令根据它们的两个操作数之差来设置条件码TEST
指令的行为与AND
指令一样,除了它们只设置条件码而不改变目的寄存器的值。典型的用法是,两个操作数是一样的,或其中的一个操作数是一个掩码,用来指示哪些位应该被测试。
跳转指令及其编码
- JUMP指令,同样是汇编中常用的指令,根据不同的条件和符号位进行不同的跳转动作,具体见书128页。
- 跳转指令有几种不同的编码,最常用的是PC(程序计数器)相关的。
- 需要注意的是,jump分为直接跳转和间接跳转:
- 直接跳转:后面跟标号作为跳转目标
- 间接跳转:*后面跟一个操作数指示符
- 当执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。
过程
栈帧结构:
-
IA32程序用程序栈来支持过程调用。它包括将数据(参数和返回值)和控制从代码的一部分传到另一部分,另外还包括进入时为过程的局部变量分配空间,并在退出时释放空间。一般地,机器只提供转移控制到过程和从过程中转移出控制的简单指令,数据传递、局部变量的分配和释放必然通过程序栈实现。
-
机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。为单个过程分配的那部分栈称为栈帧。最顶端的栈帧以两个指针界定,寄存器%ebp为帧指针,寄存器%esp为栈指针。
-
栈指针%esp可以移动,帧指针%esp不变化,因而大多数信息的访问都是相对于帧指针的。如下图所示:
-
支持过程调用和返回的指令
- leave指令:为返回准备栈,它等价于如下代码:
movl %ebp,%esp
popl %ebp //恢复已保存的%ebp寄存器
- ret指令:从过程调用中返回。指的是从栈中弹出地址,并跳转到这个位置
- 寄存器使用惯例
- 调用者调用被调用者时,要求被调用者不能覆盖某个调用者稍后会使用的寄存器值。根据惯例,寄存器%eax、%edx、%ecx称为调用者保存寄存器。P调用Q时,Q可以覆盖这些寄存器而不会破坏任何P需要的数据(因为会恢复)。另一方面,寄存器%ebx、%esi等被划分为被调用者保存寄存器。要求Q必须在覆盖这些寄存器值之前,先把它们保存到栈中,并在返回前恢复它们。
转移控制:
- call指令:call指令有一个目标,即指明被调用过程起始的指令地址; call指令的效果是将返回地址入栈。并跳转到被调用过程的起始处。
- ret指令:ret指从栈中弹出地址,并跳转到这个位置;ret指令返回到call指令后的那条指令。
- leave指令:leave指令可以使栈做好返回的准备
- 寄存器使用惯例:%eax,%edx,%ecx 调用者保存寄存器;%ebx,%esi,%edi 被调用者保存寄存器;%ebp,%esp 保持寄存器。
- 保存某值的两种方式:由调用者保存,在调用之前就压进栈;由被调用者保存,在刚被调用的时候就压进栈,并在返回之前恢复。
函数调用栈信息的GDB命令
- backtrace/bt n
- n是一个正整数,表示只打印栈顶上n层的栈信息。
- n表一个负整数,表示只打印栈底下n层的栈信息。
- frame n
- n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
- 这个指令的意思是移动到n指定的栈帧中去,并打印选中的栈的信息。如果没有n,则打印当前帧的信息。
- up n
- 表示向栈的上面移动n层,可以不打n,表示向上移动一层。
- down n
- 表示向栈的下面移动n层,可以不打n,表示向下移动一层。
教材学习中的问题和解决过程
实验楼课后习题
-
代码实现:
-
利用
gcc -S
指令进行编译,生成的汇编文件(去掉“.”)如下:
逆向工程的简单运用
- 家庭作业3.60题C语言代码
- 家庭作业3.60题汇编代码
- 分析如下:
- A题:数组本身其实没有二维三维之说,任何数组都可以说是线性结构,可以看成是从数组首位开始往后的线性排布,例如对于
A[R][S][T],A[i][j][k] 的位置是 A(,i*S*T+j*T+k,4)
。(A数组是int型所以占4个字节)。第一题,较为简单,暂时不涉及逆向工程。但这一题是求解第二题的关键所在 - B题:题目要求运用逆向工程技术,根据汇编代码,确定R、S和T的值。
- 首先,我们要根据汇编代码来求解此题,所以看懂汇编代码是关键。
- 其次,我们通过
movl $2772, %eax
可以得出,数组A占有R*S*T*4 = 2772
个字节,这是第一个等式。
movl 8(%ebp), %ecx ;get i
...
movl %ecx, %edx ;i -> %edx
sall $6, %edx ;i^6即64*i
subl %ecx, %edx ;64i-i=63i
- 上述几行汇编代码,因为我已经注释了,结合第一题答案,可以清楚的发现第二个等式:```S*T = 63```
movl 12(%ebp), %eax ;get j
leal (%eax, %eax, 8), %eax ;8*j+j -> j,最后 %eax = 9*j
- 上述几行汇编代码,因为我已经注释了,结合第一题答案,可以清楚的发现第三个等式:```T = 9```
- 综上,三个等式,可以解得该题的结果为:```R=11, S=7, T=9```
本周代码托管截图
-
统计代码如下:
-
托管如下:
其他(感悟、思考等,可选)
- 首先,我觉得本周的学习任务是对上学期汇编的一个巩固,在原有我们所学的8086汇编知识的基础上得到了拓宽,这次我们所学的汇编主要是在32位机器上的。在寄存器个数没有变化的前提下,所能支持的位数翻了一倍,引入了
%eax, %ebx, %ebp...
等寄存器,操作指令也由原来的向左赋值变为向右赋值。虽然变化挺多,但本质是不变的一条机器指令只执行一个非常基本的操作以及汇编语言是近似机器语言,其造作指令与机器码一一对应(这在我们反汇编的实践中体现的很好)。 - 其次,在课后的练习以及家庭作业中,首次接触到了逆向工程的概念,即通过阅读汇编代码,得出原有C语言文件里的相关知识的工作。我也用该方法成功解决了几道题目,让我再次感受到了汇编语言的巨大魅力,虽然汇编语言可移植性不强,但专门用来对付某台机器上的C语言可是绰绰有余,可以帮我们挖掘到更多的信息。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | 学习了Linux常用命令 |
第二周 | 79/279 | 1/3 | 30/50 | 了解vim,gcc,gdb基本操作 |
第三周 | 182/461 | 1/4 | 25/75 | 更深层次了解信息处理 |
第四周 | 36/497 | 2/6 | 3/78 | 第二章知识简单的运用 |
第五周 | 194/691 | 1/7 | 28/106 | 汇编知识与了解逆向 |