zoukankan      html  css  js  c++  java
  • 20135203齐岳 信息安全系统设计基础第四周学习总结

    20135203齐岳 信息安全系统设计基础第四周学习总结

    学习计时:共17小时

    • 读书:10
    • 代码:1
    • 作业:3
    • 博客:3

    一、学习目标

    1. 理解逆向的概念以及
    2. 掌握X86汇编基础,能够阅读(反)汇编代码
    3. 了解ISA(指令集体系结构)
    4. 理解函数调用栈帧的概念,并能用GDB进行调试

    二、学习任务

    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.1历史观点

    X86 寻址方式经历三代:

    1. DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
    2. 8086的分段模式
    3. IA32的带保护模式的平坦模式

    Linux使用平坦寻址方式,使程序员将整个存储空间看做一个大的字节数组。

    3.2程序编码

    ISA:

    指令集体系结构,机器级程序的格式和行为,它定义了处理器状态、指令的格式以及每条指令对状态的影响。

    • 程序计数器(通常称为PC,用%eip表示),指示将要执行的下一条指令在存储器中的地址。

    • 整数寄存器文件:存储地(对应于C语言的指针)或整数数据。

    • 条件码寄存器:保存着最近执行的算数或逻辑指令的状态信息,用来实现控制或者数据流中的条件变化。

    • 浮点寄存器:用来存放浮点数据。

    编译过程:

    • C预处理器插入宏和头文件:gcc -E xxx.c -o xxx.i

    • 编译器产生源代码的汇编代码:gcc -S xxx.i -o xxx.s

    • 汇编器化成二进制目标代码:gcc -c xxx.s -o xxx.o

    • 链接器生成最终可执行文件:gcc xxx. -o xxx

    • 用objdump -d xxx.o -o xxx.s 反汇编

    建立函数调用栈帧的汇编代码:

    pushl	%ebp 将寄存器%ebp中的内容压入程序栈
    movl	%esp,%ebp  将%ebp中的内容放入寄存器%esp
    ......
    popl	%ebp 寄存器%ebp中内容出栈
    ret 返回结果
    

    注意:

    1. 64位机器上想要得到32代码:gcc -m32 -S xxx.c

    2. MAC OS中没有objdump, 有个基本等价的命令otool

    3. Ubuntu中 gcc -S code.c (不带-O1) 产生的代码更接近教材中代码(删除"."开头的语句)

    4. 找到程序的字节表示:

       (gdb) x/17xb sum
      
    5. 二进制文件可以用od命令查看,也可以用gdb的x命令查看。有些输出内容过多,我们可以使用 more或less命令结合管道查看,也可以使用输出重定向来查看

           od code.o | more
           od code.o > code.txt
      

    格式注解

    ·开头的行都是指导汇编器和链接器的命令,gcc -S 产生的汇编中可以把 以‘.’开始的语句都删除了再阅读

    Linux和Windows的汇编格式的区别:

    1. ATT格式(Linux下的汇编格式)&Intel格式(Windows的汇编格式):
    2. Intel代码省略了指示大小的后缀。我们看到指令mov而不是movl;
    3. Intel代码省略了寄存器名字前的%,使用的是esp而不是%esp;
    4. Intel代码用不同方式来描述存储器中的位置:
      例如,是‘DWORD PTR [ebp+8]’而不是'8(%ebp)'

    3.3数据格式

    大多数GCC生成的汇编代码指令都有一个字符后缀,表明操作数的大小。

    (P111表格)

    3.4访问信息

    寄存器

    一个IA32中央处理单元(CPU)包含一组8个存储32位值的寄存器。用来存储整数数据和指针。

    %eax	%ax	(%ah %al)  通用寄存器
    %ecx	%cx (%ch %cl)  通用寄存器
    %edx	%dx  (%dh %dl)   通用寄存器
    %ebx	%bx  (%bh %bl)   通用寄存器
    %esi	%si				用来操纵数组
    %edi	%di				用来操纵数组
    %esp	%sp				操纵栈帧
    %ebp	%bp				操纵栈帧
    

    注意:

    对于32位的eax,16位的ax,8位的ah,al都是独立的,我们通过下面例子说明:

    假定当前是32位x86机器,eax寄存器的值为0x8226,执行完addw $0x8266
    ,%ax指令后eax的值是多少? 
    解析:0x8226+0x826=0x1044c, ax是16位寄存器,出现溢出,最高位的1会
    丢掉,剩下0x44c,不要以为eax是32位的不会发生溢出.
    

    寻址方式

    根据操作数的不同类型,寻址方式可分为以下三种:

    • 立即数寻址方式:操作数为常数值,写作$后加一个整数。

    • 寄存器寻址方式:操作数为某个寄存器中的内容

    • 存储器寻址方式:根据计算出来的地址访问某个存储器的位置

    寻址模式:一个立即数偏移Imm,一个基址寄存器Eb,一个变址寄存器Ei,一个比例因子s(必须为1,2,4,8)有效地址计算为:Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s

    数据传送指令

    • MOV相当于C语言的赋值'='

        mov		S,D		S中的字节传送到D中
      

    注意:

    1. ATT格式中的方向;
    2. 不能从内存地址直接MOV到另一个内存地址,要用寄存器中转一下。
    3. 区分MOV,MOVS(符号扩展),MOVZ(零扩展)

    push和pop:

    	pushl	S		R[%esp] ← R[%esp]-4
    					M[R[%esp]] ← S
    
    	popl	D		D ← M[R[%esp]]
    					R[%esp] ← R[%esp]+4
    

    注意:

    1. 栈顶元素的地址是所有栈中元素地址中最低的,后进先出;
    2. 指针就是地址;局部变量保存在寄存器中。

    3.5算术和逻辑操作

    加载有效地址

    leal,从存储器读数据到寄存器,而从存储器引用的过程实际上是将有效地址写入到目的操作数。目的操作数必须是一个寄存器。

    一元操作和二元操作

    • 一元操作:只有一个操作数,既是源又是目的,可以是一个寄存器或者存储器。

    • 二元操作:第二个操作数既是源又是目的,两个操作数不能同时是存储器。

    移位

    先给出位移量,然后是位移的数值,可进行算数和逻辑右移。移位操作移位量可以是立即数或%cl中的数。

    3.6控制

    条件码:

    描述最近的算数或者逻辑操作的属性,可以检测这些寄存器来执行条件分支指令。

    	CF:进位标志,最近操作使高位产生进位,用来检测无符号操作数的溢出
    	ZF:零标志,最近操作得出的结果为0
    	SF:符号标志,最近操作得到的结果为负数
    	OF:溢出标志,最近操作导致一个补码溢出-正溢出或负溢出。
    

    注意:

    1. leal不改变条件码寄存器
    2. CMP与SUB的区别:CMP也是根据两个操作数之差设置条件码,但只设置条件码而不更新目标寄存器
    3. 有条件跳转的条件看状态寄存器(教材上叫条件码寄存器)

    访问条件码的读取方式:

    1. 根据条件码的某个组合,将一个字节设置成0或1;
    2. 跳转到程序某个其他的部分
    3. 有条件的传送数据。

    SET指令根据t=a-b的结果设置条件码

    跳转指令及其编码

    控制中最核心的是跳转语句:

    • 有条件跳转(实现if,switch,while,for)

    • 无条件跳转jmp(实现goto)

    当执行PC相关的寻址时,程序计数器的值是跳转指令后面那条指令的地址,而不是跳转指令本身的地址。

    翻译条件分支

    将条件和表达式从C语言翻译成机器代码,最常用的方式是结合有条件和无条件跳转。

    C语言中if-else语句的通用形式:

    if(test-expr)
    	then-statement
    else
    	else-statement
    

    汇编结构:

    	t=test-expr;
    	if!(t)
    		goto false;
    	then-statement
    	goto done;
    false:
    	else-statement
    done:
    

    循环

    do-while循环

    C语言中do-while语句的通用形式:

    do
    	body-statement
    	while(test-expr);
    

    汇编结构:

    loop:
    	body-statement
    	t=test-expr;
    	if(t)
    		goto loop;
    

    while循环

    C语言中while语句的通用形式:

    while(test-expr)
    	body-statement
    

    汇编结构:

    	t=test-expr;
    	if(!t)
    		goto done;
    loop:
    	body-statement
    	t=test-expr;	
    	if(t)
    		goto loop;
    done:
    

    for循环

    C语言中for语句的通用形式:

    for(init-expr;test-expr;update-expr)
    	body-statement
    

    汇编结构:

    	init-expr
    	t=test-expr;
    	if(!t)
    		goto done;
    loop:
    	body-statement
    	update-expr;
    	t=test-expr;
    	if(t)
    		goto loop;
    	done:
    

    switch语句

    根据一个整数索引值进行多重分支,执行switch语句的关键步骤是通过跳转表来访问代码位置,使结构更加高效。

    3.7过程

    数据传递、局部变量的分配和释放通过操纵程序栈来实现。

    栈帧结构

    • 为单个过程分配的栈叫做栈帧,寄存器%ebp为帧指针,而寄存器指针%esp为栈指针,程序执行时栈指针移动,大多数信息的访问都是相对于帧指针。

    • 栈向低地址方向增长,而栈指针%esp指向栈顶元素。

    转移控制

    call:目标是指明被调用过程起始的指令地址,效果是将返回地址入栈,并跳转到被调用过程的起始处。

    ret:从栈中弹出地址,并跳转到这个位置。

    函数返回值存在%eax中

    寄存器使用惯例

    程序寄存器是唯一能被所有过程共享的资源,调用者保存寄存器 和 被调用者保存寄存器是分开的,对于哪一个寄存器保存函数调用过程中的返回值要有统一的约定。

    3.11关于栈帧的gdb命令

    • backtrace/bt:打印当前的函数调用栈的所有信息。后面加n或-n表示打印栈顶上n层(或者下n层)的栈信息。

    • frame n:n为栈中的层编号,从0开始,类似C语言中数组的下标。移动到n指定的栈帧中去,并打印选中的栈的信息。如果没有n,则打印当前帧的信息。

    • up n:表示向栈的顶移动n层。

    • down n:表示向栈底移动n层。

    (此处为百度+自己翻译,如有不准请指正)

    四、实验练习

    C语言代码:

    int q(int x)
    {
    	return x + 2;
    }
    
    int y(int x)
    {
    	return q(x);
    }
    
    int main(void)
    {
    	 return y(7) + 9;
    }
    

    使用gcc –S –o main.s main.c -m32命令编译成汇编代码如下:

    q:
    	pushl	%ebp
    	movl	%esp, %ebp
    	movl	8(%ebp), %eax
    	addl	$2, %eax
    	popl	%ebp
    	ret
    y:
    	pushl	%ebp
    	movl	%esp, %ebp
    	pushl	8(%ebp)
    	call	q
    	addl	$4, %esp
    	leave
    	ret
    main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	pushl	$7
    	call	y
    	addl	$4, %esp
    	addl	$9, %eax
    	leave
    

    五、遇到的问题

    • 3.16

      testl %eax,%eax
      jle .L3

    如何能判断条件为a<=0?(对test语句理解不到位)

    • 3.23

        leal	(%eax,%eax),%edx
        movl	%ebx,%eax
      

    (%eax中存放着val,%ebx中存放x。)

    这两行代码如何实现val<<1的效果。

    • 3.30

        	call next
        next:
        	popl %eax
      

    call指令的效果是将 返回地址入栈,也就是call后面指令的地址,即popl %eax,而%eax的值又被设置为popl指令的地址,整个过程是顺序执行的而并没有发生跳转,所以无需ret弹出。这种理解正确吗?
    为什么它是实现将程序计数器 放到整数寄存器中的唯一方式?

    • 对于跳转指令和循环语句的汇编代码阅读还是不熟练,感觉书上的例题没有完全吃透,在分析栈帧结构时顺序执行的代码基本可以搞清楚,涉及到call和ret相关的语句时就感觉比较困难,还需要深入理解。
  • 相关阅读:
    Ubuntu上搭建Watir-Webdriver与Cucumber环境
    使--no-ri --no-rdoc成为gem安装的默认选项
    Ruby require 路径问题
    【原创】LoadRunner Java Vuser脚本的配置和调试指南
    【原创】LoadRunner Java Vuser开发环境配置指南
    【原创】使用Nmon_Analyzer处理较大nmon文件的方法
    2014年,马上要上班啦,希望一切顺利
    关于.jar的文件在cmd中无法连接数据库的问题
    ios 的通知机制
    ios开发小技巧-用宏化简代码
  • 原文地址:https://www.cnblogs.com/July0207/p/4869494.html
Copyright © 2011-2022 走看看