20135321余佳源-信息安全系统设计基础第五周学习总结
第五周(10.05-10.11):
学习计时:共10小时 读书:5 代码:1 作业:2 博客:2 |
一、学习目标 |
1. 理解逆向的概念以及 2. 掌握X86汇编基础,能够阅读(反)汇编代码 3. 了解ISA(指令集体系结构) 4. 理解函数调用栈帧的概念,并能用GDB进行调试 |
二、学习资源 |
1. 教材:第三章《程序的机器级表示》,详细学习指导见这:重点是3.7,3.11 2. 课程资料:https://www.shiyanlou.com/courses/413 实验四,课程邀请码:W7FQKW4Y 3. 教材中代码运行、思考一下,读代码的学习方法见这。 |
三、学习方法 |
1. 进度很重要:必须跟上每周的进度,阅读,练习,问答,项目。我会认真对待每一位同学,请你不要因为困难半途而废。 2. 问答很重要:遇到知识难点请多多提问,这是你的权利更是您对自己负责的义务。问答到博客园讨论小组:http://group.cnblogs.com/103791/ 3. 实践很重要:解决书中习题,实践书中实例,完成每周项目,才算真的消化了这本好书。通过实验楼环境或自己安装的虚拟机在实践中进行学习 4. 实验报告很重要:详细记录你完成项目任务的思路,获得老师点评和帮助自己复习。学习完成后在博客园中(http://www.cnblogs.com/)把学习过程通过博客发表,博客标题"学号-信息安全系统设计基础第五周学习总结" |
四、学习任务 |
1. 阅读教材,完成课后练习(书中有参考答案) 3.1-3.7中练习,重点:3.1,3.3,3.5,3.6,3.9,3.14,3.15,3.16,3.22,3.23,3.27,3.29,3.30,3.33,3.34 2. 考核:练习题把数据变换一下 3. 实验:需要动手的到实验楼中练习一下 4. 深化、实践题目,额外加分 |
五、后续学习预告(可选): |
第四章《处理器体系结构》 |
六、学习过程 |
(提示:此处由学生填写,学习过程,学习笔记,代码编译,运行结果,思考等) |
七、遇到的问题及解决 |
(提示:此处由学生填写,是重要的得分点,要写出遇到的问题和解决方案以及对出现问题的思考) |
八、其他 |
(提示:此处由学生填写,灵感,领悟等) |
第五周:学习任务教材第三章
本章学习内容是汇编语言,现在直接写汇编的机会不多了,但一定要能读懂,信息安全的核心思维方式"逆向"在这有很好很直接的体现,反汇编就是直接的逆向工程。
本章重点是3.7,但没有3.1-3.6的基础也是不行,如果想真正的提高动手能力,3.11如何用GDB调试汇编要好好练习一下,不过大多GDB技巧大家都会了。
3.1-3.7中练习,重点:3.1,3.3,3.5,3.6,3.9,3.14,3.15,3.16,3.22,3.23,3.27,3.29,3.30,3.33,3.34
p104, p105: X86 寻址方式经历三代:
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
p106: ISA的定义,ISA需要大家能总结规律,举一反三,比如能对比学习ARM的ISA;PC寄存器要好好理解;
p107: gcc -S xxx.c -o xxx.s 获得汇编代码,也可以用objdump -d xxx 反汇编; 注意函数前两条和后两条汇编代码,所有函数都有,建立函数调用栈帧,应该理解、熟记。
注意: 64位机器上想要得到32代码:gcc -m32 -S xxx.c
MAC OS中没有objdump, 有个基本等价的命令otool
Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)
p108: 二进制文件可以用od 命令查看,也可以用gdb的x命令查看。
有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看
od code.o | more
od code.o > code.txt
p109: gcc -S 产生的汇编中可以把 以"."开始的语句都删除了再阅读
p110: 了解Linux和Windows的汇编格式有点区别:ATT格式和Intel格式
p111: 表中不同数据的汇编代码后缀
p112: 这几个寄存器要深入理解,知道它们的用处。esi edi可以用来操纵数组,esp ebp用来操纵栈帧。
对于寄存器,特别是通用寄存器中的eax,ebx,ecx,edx,大家要理解32位的eax,16位的ax,8位的ah,al都是独立的,我们通过下面例子说明:
假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266, %ax指令后eax的值是多少?
解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出.
p113: 结合表,深入理解各种 寻址方式;理解操作数的三种类型:立即数、寄存器、存储器;掌握有效地址的计算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
p114: MOV相当于C语言的赋值"=",注意ATT格式中的方向, 另外注意不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。能区分MOV,MOVS,MOVZ,掌握push,pop
p115/p116: 栈帧与push pop; 注意栈顶元素的地址是所有栈中元素地址中最低的。
p117: 指针就是地址;局部变量保存在寄存器中。
p119: 结合表理解一下算术和逻辑运算, 注意目的操作数都是什么类型
特别注意一下减法是谁减去谁
注意移位操作移位量可以是立即数或%cl中的数
p123: 结合C语言理解一下控制部分,也就是分支(if/switch),循环语句(while, for)如何实现的。考验大家举一反三的学习能力。控制中最核心的是跳转语句:有条件跳转p128(实现if,switch,while,for),无条件跳转jmp(实现goto)
p124: 有条件跳转的条件看状态寄存器(教材上叫条件码寄存器)
注意leal不改变条件码寄存器
思考一下:CMP和SUB用在什么地方
p125: SET指令根据t=a-b的结果设置条件码
p127: 跳转与标号
p130/p131: if-else 的汇编结构
p132/p133: do-while
p134/p135: while
p137/p138: for
p144/p145: switch
p149: IA32通过栈来实现过程调用。掌握栈帧结构,注意函数参数的压栈顺序.
p150/p151: call/ret; 函数返回值存在%eax中
p174: bt/frame/up/down :关于栈帧的gdb命令
第三章 程序的机器级表示
3.1 历史观点
Intel处理器系列俗称x86
3.2程序编码
3.2.1机器级代码
机器级程序的格式和行为定义为指令集体系结构,它定义了处理器状态、指令的格式,以及每条指令对状态的影响。
机器级程序会用的存储器地址是虚拟地址,提供的存储器模型看上去是一个非常大的数组。
程序计数器:通常称为PC,用%eip表示,只是将要执行的下一条指令在存储器中的地址。
3.2.2代码示例
"-S"会使GCC产生一个汇编文件code.s
"-c"会产生的目标代码文件code.o,二进制格式文件
要查看目标代码文件的内容,要使用反汇编器。"-d"使用OBJDUMP 即object dump。
反汇编标示特性:
- IA32指令长度从1到15字节不等。常用指令以及操作数较少的指令所需的字节数少,而那些不太常用或者操作数多的指令所需字节数较多。
- 设计指令格式的方式是,从某个给定位置开始,将字节唯一地解码成机器指令。
- 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码,不需要访问程序的源代码或汇编代码。
- 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些细微差别。
生成实际可执行代码需要对一组目标代码文件运行链接器,必须包含一个main函数。
3.2.3关于格式的注解
Intel与ATT格式的不同
- Intel代码省略了指示大小的后缀。
- Intel代码省略了寄存器名字前面的'%'。
- Intel代码用不同的方式来描述存储器中的位置。
- 在带有多个操作数的指令情况下,列出操作数的顺序想法。
3.3数据格式
Intel用"字"表示16位数据类型,32位为"双字",64位为"四字"
C语言数据类型在IA32中的大小。
3.4访问信息
一个IA32的CPU包含一组8个存储32位值的寄存器,用来存储整数数据和指针。大多数情况下前6个寄存器可以看成通用寄存器,没有使用限制。前三个%eax、%ecx、%edx的保存和回复惯例不同于%ebx、%edi、%esi。最后两个寄存器%ebp和%esp保存着指向程序栈中重要位置的指针。只有根据栈管理的标准惯例才能修改这两个寄存器的值。
3.4.1操作数指示符
大多数指令有一个或多个操作数,指示执行一个操作中要引用的源数据值,以及放置结果的目标位置。各种不同的操作数的可能性被分为三种类型:立即数,存储器,寄存器。有效地址的计算方式 Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
3.4.2数据传送指令
我们把许多不同的指令分成了指令类,一类中的指令执行一样的操作只不过操作数大小不同。
对于movl来说可以使8个32位寄存器的其中一个,对于movw来说可以使8个16位寄存器中的一个,对于movb来说可以使单字节寄存器中的一个。MOVS和MOVZ指令类都是讲一个较小的元数据复制到一个较大的数据位置,高位用符号位扩展,或者零扩展进行填充。
栈是一个数据结构,可以添加过或者删除数据,不过要遵循"后进先出"原则,push入栈,pop出栈,弹出的值永远是最近被压入而仍然在栈中的值,栈可以实现为一个数组,总是从数组的一端插入和删除数据,这一端成为栈顶。任何存储在栈顶至外地数据都被认为是无效的。
3.4.3数据传送示例
C语言中的指针其实就是地址,间接引用指针就是将该指针放在一个寄存器中,然后在寄存器引用中使用这个寄存器。局部变量通常保存在寄存器中。寄存器访问比存储器访问要快得多。
3.5算数和逻辑操作
每个指令类都有对字节、字和双字数据进行操作的指令。分为四组:加载有效地址,一元操作,二元操作,移位。
3.5.1加载有效地址
Leal实际上是movl指令的变形,从存储器读数据到寄存器,实际上根本没有引用存储器。
3.6控制
直线代码,就是指令一条接着一条地顺序执行。C语言中的语句和机器代码中的指令都是按照它们在程序中出现的次序顺序执行的。Jump指令可以改变一组机器代码指令的执行顺序。
3.6.1条件码
CF:进位标志,最近的操作使得最高位产生了进位。用于检查无符号操作数的溢出。
ZF:零标志。最近的操作得出结果为0。
SF:符号标志。最近的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出。
3.6.2访问条件码
条件码通常不会直接读取,通常的使用方法有三种:1.根据条件码的组合,把一个字节设为0或者1。2.条件跳转到程序的某个部分。3.有条件地传送数据。
SET指令
某些底层的机器指令可能有多个名字,成为"同义名"。
注意机器代码如何区分有符号和无符号值是很重要的,大多数情况下,机器代码对于有符号和无符号两种都使用一样的指令,因为许多算数运算对无符号和补码算术都有一样的行为。
3.6.3跳转指令以及编码
跳转jump会导致执行切换到程序中一个全新的位置,汇编代码中这些跳转的目的地通常用一个标号指明。
跳转指令都是有条件的,它们根据条件码的某个组合或者跳转,或者继续执行代码序列中下一条指令。
Jump指令
3.6.4翻译条件分支
将条件表达式和语句从C语言翻译成机器代码,最常用的方式是结合有条件和无条件跳转。
3.6.5循环
1.do-while
效果是重复执行body-statement,对test-expr求值,如果为非零,继续循环。
2.while
第一次执行body-statement之前,循环就可能终止。
3.for
程序首先对初始表达式init-expr求值,然后进入循环,循环中它先对测试条件test-expr求值,如果为"假"就会推出,否则执行循环体body-statement,最后对更新表达式update-expr求值
3.6.6条件传送指令
3.6.7switch语句
Switch语句是可以根据一个整数索引值进行多重分支。处理具有多种可能结果的测试。提高了C的可读性,通过使用跳转表使得更加高效。
3.7过程
3.7.1栈帧结构
IA32陈旭用程序栈来支持过程调用。单个过程分配的那部分成为栈帧。栈帧最顶端两个指针界定,寄存器%ebp为帧指针,寄存器%esp为栈指针。
栈帧结构
假设过程调用者P调用过程Q被调用者,则Q的参数放在P的栈帧中,当P调用Q时,P的返回地址被压入栈中,形成P的栈帧末尾。返回地址就是当程序从Q返回应该继续执行的地方。Q的栈帧从保存的帧指针的值开始,后面是保存的其他寄存器的值。
过程Q也用栈来保存其他不能存放在寄存器中的局部变量。另外,Q会用栈帧来存放它调用的其他过程的参数。
3.7.2转译控制
支持过程调用和返回的指令
Call指令有一个目标:指明被调用过程起始的指令地址。
Call指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。
Call指令将控制转移到一个函数的起始,而ret指令返回到call指令后的那条指令。
如果函数要返回整数或者指针的话,寄存器%eax可以用来返回值。
3.7.3寄存器使用惯例
程序寄存器组是唯一能被所有过程共享的资源。IA32采用了统一的寄存器使用惯例。
寄存器%eax、%edx、%ecx被划分为调用者保存寄存器。
寄存器%esi、%ebx、%edi被划分为被调用者保存寄存器。
过程P在调用Q之前计算,必须保证y的值再Q返回后是可用的。
3.7.5递归过程
终止条件和递归调用,代码都会继续到完成部分,恢复栈和被调用者保存寄存器,然后返回。
递归调用一个函数本身与调用其他函数是一样的,栈规则提供了一种机制,每次函数调用都有它自己私有的状态信息存储。
3.8数组分配和访问
C语言中的数组是一种将标量数据聚集成更大数据类型的方式。
3.8.1基本原则
3.8.2指针运算
C语言允许对指针进行运算而计算出来的值会根据该指针引用的数据类型的大小进行伸缩。
单操作数的操作符&和*可以产生指针和简介引用指针。
例子:
3.8.3嵌套的数组
当我们创建数组的数组时,数组分配和引用一般原则也是成立的。按照"行优先"顺序排列。
3.8.4定长数组
C语言编译器能够优化定长多维数组上的操作代码。机器代码认为每个指针都指向的一个字节地址,因此在编译指针运算中,每次都应该增加底层数据类型大小。
3.9异质的数据结构
C语言提供了两种结合不同类型的对象来创建数据类型的机制:结构,联合。
3.9.1结构
Struct声明一个数据类型,将可能不同类型的对象聚合到一个对象中。
3.9.2联合
Union提供一种方式,规避C语言的类型系统,允许多种类型引用一个对象。
3.9.3数据对齐
许多计算机系统对基本数据类型合法地址做出了一些限制,要求某些类型对象的地址必须是某个K(2,4,8)的倍数,对齐限制简化了形成处理器和存储器系统之间接口的硬件设计。
3.10综合:理解指针
- 每个指针都对应一个类型
- 每个指针都有一个值
- 指针都用&运算符创建
- 操作符用于指针的简介引用
- 数组与指针紧密联系
- 将指针从一种类型强转成另一种类型只改变他的类型而不改变值。
- 指针也可以指向函数
3.11应用:使用GDB调试器
方法:在感兴趣的地方设置断点。可以设置在函数入口后面,或者一个程序的地址处,执行过程中遇到一个断点就会停下来,并返回控制给用户,在断点处,我们能够查看各个寄存器和存储器的位置。也可以单步跟踪程序,一次执行几条命令,或是直接前进到下一个断点。
GDB命令示例
遇到的问题及解决:上学期的汇编并没有学得很好,本章内容与汇编还是有莫大的联系,只能通过一些皮毛知识来跟着进度走。