zoukankan      html  css  js  c++  java
  • 汇编讲解(上)--逆向开发

    今天进入逆向开发的另一个部分--汇编知识的讲解,分为上下篇,希望通过两篇博客的讲解让大家对汇编知识有个大致的了解!!!

    汇编语言发展

    机器语言

    机器语言:有0和1共同组成的机器语言

    • 加: 0100 0000
    • 减: 0100 1000
    • 乘: 1111 0111 1110 0000
    • 除: 1111 0111 1111 0000

    汇编语言(assembly language)

    汇编语言:使用助记符来代替机器语言

    • 加:INC EAX 通过编译器 0100 0000
    • 减:DEC EAX 通过编译器 0100 1000
    • 乘:MUL EAX 通过编译器 1111 0111 1110 0000
    • 除:DIV EAX 通过编译器 1111 0111 1111 0000

    高级语言(High-level programming language)

    高级语言:比如Java,Objective-C

    • 加:A+B 通过编译器 0100 0000
    • 减:A-B 通过编译器 0100 1000
    • 乘:A*B 通过编译器 1111 0111 1110 0000
    • 除:A/B 通过编译器 1111 0111 1111 0000

    代码在手机终端上运行过程

    •  汇编语言与机器语言是一一对应,每条机器指令都是有相对应的汇编指令
    • 汇编语言是可以通过编译得到机器语言,机器语言也是可以通过反汇编来得到汇编语言
    • 高级语言是可以通过编译得到汇编语言和机器语言;但是汇编语言,机器语言几乎是不可能还原成高级语言

    汇编语言的特点与语言种类

    特点

    • 不区分大小写,DIV和div是一样的
    • 汇编指令是机器指令的助记符,是和机器指令是一一对应的,每一种的CPU都是有自己的机器指令集/汇编指令集
    • 目标代码短、占位内存少以及执行速度快
    • 能够不受编译器的控制,对生成的二进制代码是完全控制的
    • 能够直接访问,控制住硬件设备。

    语言种类

    iPhone使用的是ARM汇编,但是不同的手机设备以及架构是不同的

    APP/程序执行过程

    如果想深入了解汇编,下面将讲解几个插曲。

    硬件最重要就是CPU和内存

    插曲

    总线

    总线是一根根导线的集合

    总线分为地址总线、数据总线、控制总线三种

    •  地址总线

      地址总线宽度决定了CPU的寻址能力

      8086地址总线是20,其寻址能力是2^20 = 1M

    • 数据总线

      数据总线宽度决定了CPU单次传递的数据量,换句话说也是数据的传送速度

      8086的数据宽度为16,单次最大传送数据量为2个字节

    • 控制总线

      控制总线宽度决定了CPU对其他控件的控制能力

    数据宽度  

    • 位(bit):就是二进制位,就是0或者1
    • 字节(Byte):1个字节是由8个bit(8位)组成。内存中最小的存储单元为Byte
    • 字(word):1个字由两个字节Byte组成,两个字节是高字节和低字节
    • 双字:1个双字由2个字组成

    在存储数据时,分为有符号和无符号数

     寄存器

    内部部件之间是由总线来相连

    CPU最重要的部件是寄存器,日常工作中,通过改变寄存器的值来实现对CPU的值。

    通用寄存器 

    ARM64有31个64位通用寄存器X0-X30,通常用来存储一般性的数据,有时候也会有特定用途,称之为通用寄存器。

    W0-W28是32位,是X0-X32的低28位,而64位CPU是兼容32位的CPU,所以使用64位的低32位。W0就是X0。

     CPU一般先将内存的数据存储到通用寄存器中,然后再对此进行运算。

    pc寄存器

    • 指向了CPU当前要执行的指令地址
    • 在内存或者磁盘上,指令和数据无区别,都是二进制信息
    • CPU将pc指向的内存单元的内容看做指令,如果内存中的某段内容曾经被执行过,其内存单元必被pc指向过。

    CPU的补充

    CPU由计算器,控制器以及寄存器组成,其中,寄存器的作用将数据进行临行存储起来.

    CPU的运行速度非常快, 为了性能的提高, CPU会在内部开辟小块的临时存储区域, 并在运算时先将数据从内存复制到这小块临时存储区域中, 在这小块区域进行运算,这小块临时区域就称作寄存器.

    对于ARM64架构的CPU, 以 X 开头的就是64位的寄存器, 以 W 开头的就是32位的寄存器, 其中32位寄存器就是64位寄存器的低32位部分

     

     系统中没有提供16位 和 8位的寄存器供以访问(注意: 查看时要使用真机调试, 模拟器使用的X86的架构)

    高速缓存

    iPhoneX搭载的ARM64处理器A11, 它的一级缓存容量是64KB, 二级缓存容量是8M (现在普遍也只有二级缓存)

    CPU每执行一条指令都需要从内存中将指令读取到CPU中执行, 而寄存器运行速度要比内存的读写速度快很多, CPU还集成了一个高速缓存存储区域来提高性能. 当程序运行时, 先将要执行的指令代码存储到高速缓存存储区域中(由操作系统完成), CPU直接从高速缓存中依次读取指令来执行.

    寄存器的补充

    数据地址存储器

    数据地址存储器通常作为数据计算的临时存储, 做累加, 计数地址保存等功能. 这些寄存器的作用主要用于在CPU指令中保存操作数, 在CPU中当做常规变量使用.
    ARM64中
    • 64位: X0 - X30 , XZR(零寄存器)
    • 32位: W0 - W30 , WZR(零寄存器)
    注意: 8086汇编中有一种寄存器, 段寄存器: CS, ES, DS, SS四个寄存器用来保存段的基地址, 这属于Intel CPU中, 在ARM中没有.

    浮点和向量寄存器

     现在CPU支持向量运算(向量运算在图形处理中使用比较广泛), 为支持向量运算, 故提供了更多向量寄存器.

    • 向量寄存器: 128位 v0 - v128

     因为浮点数以及其运算的特殊性, CPU专门提供浮点寄存器来计算浮点数

    sp, fp寄存器

    • sp寄存器会在任意时刻保存栈顶的地址
    • fp寄存器也成为x29寄存器属于通用寄存器, 但是在某些时刻我们会用它保存栈底的地址

    注意: ARM64开始取消32位的LDM, STM, PUSH, POP指令. 与之替代的是 ldr/ ldp, str/ stp
    ARM64里面 对栈的操作都是16进制对齐的!!!

    内存的读写指令

    内存的读写指令

    str(store register)写入指令
    将数据从寄存器中读出来, 写入到内存中

    ldr(load register)读取指令
    将数据从内存中读取出来, 存到寄存器中

    ldp/stp 是 ldr/str 的衍生, 可以同时读/写两个寄存器, ldr/str只能读写一个

    sub sp, sp, #0x20 ; 拉伸栈空间32(20 = 2*16)个字节
    stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)个字节,存放x0 和 x1
    ldp x1 , x0, [sp, #0x10] ; 将sp偏移16个字节的值取出来,放入x1 和 x0

    注意: 拉伸栈空间是往低地址拉伸, 拉伸的字节数只能是16的倍数, 否则会崩溃(对照上面 16进制对齐理解 )

    bl 和 ret 指令

    bl: 跳转指令
    1. 将下一条指令的地址存放到 lr(x30) 寄存器中
    2. 跳转到对应函数中执行指令

    ret: 返回指令
    默认使用 lr(x30) 寄存器的值, 通过底层指令提示CPU这是下一条指令的地址.
    ARM64架构中的特色指令, 面向硬件做了优化处理

    lr (x30) 寄存器

    • x30寄存器存放的是函数返回的地址, 当ret执行指令时, 会寻找这个寄存器中保存的地址
    注意: 在函数嵌套调用中, 需要将x30入栈, 否则可能会造成死循环.

    状态寄存器

    CPU内部的寄存器中, 有一种特殊的寄存器(不同的处理器, 结构和寄存器个数都可能不同). 这种寄存器在ARM中, 称为 CPSR (current program status register) 状态寄存器.
    CPSR和其他寄存器不一样, 其他寄存器都是用来存放数据的, 一个寄存器对应一个含义 ; 而CPSR寄存器是按位起作用的, 每一位都有专门的含义,记录特定的信息.
    注意: CPSR 寄存器是32位的
    • CPSR的低8位(包括 I , F, T 和 M[4-0])称为控制位, 程序无法修改, 除非CPU运行于特权模式下, 才能修改控制位.

    • N Z C V均为条件码标志位, 它们的内容可被算术或逻辑运算的结果所改变, 并且决定某条指令是否被执行, 意义重

    N (negative) 标志

    CPSR 第31位是N, 符号标志位. 它记录相关指令执行后, 其结果是否为负, 如果为负,则 N 为1; 如果非负则N为0.

    Z (zero) 标志

    CPSR 第30位是Z, 0标志位. 它记录相关指令执行后, 其结果是否为0, 如果为0,则Z为1; 如果非0则Z为0.

    C (carry) 标志

    CPSR 第29位是C, 进位标志位. 一般情况下, 进行无符号的运算.
    加法运算: 当运算结果产生了进位时(无符号位溢出), 则 C = 1, 否则C=0.
    减法运算(包括CMP): 当运算结果产生了借位时(无符号数溢出), 则C=0,否则C=1.

    对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:

    进位

    当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令

    mov  w0,#0xaaaaaaaa0xa 的二进制是 1010
    adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
    adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
    adds w0,w0,w0; 重复上面操作
    adds w0,w0,w0

    借位

    当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:

    mov  w0,#0x0
    subs w0,w0,#0xff ;
    subs w0,w0,#0xff
    subs w0,w0,#0xff

    V(overflow)溢出标志

    CPSR的第28位, 溢出标志位. 在进行有符号数运算的时候, 如果超出了机器所能识别的范围, 称为溢出.
    • 正数 + 正数 为 负数, 溢出
    • 负数 + 负数 为 正数, 溢出
    • 正数 + 负数 不可能溢出

    以上就是关于汇编知识点内容,希望对大家有所帮助,进入了解技术语言的底层知识!!!




  • 相关阅读:
    thread同步测试
    thread互斥测试
    实验二测试
    第六章学习笔记
    opensslAPI
    第五章学习笔记
    stat命令的实现-mysate(必做)
    团队作业(三)
    第四章学习笔记
    学习笔记9
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/12046441.html
Copyright © 2011-2022 走看看