美文网首页
汇编(一)

汇编(一)

作者: 浅墨入画 | 来源:发表于2021-03-27 11:59 被阅读0次

前言:

逆向开发中,非常重要的一个环节就是静态分析。我们知道,手机上安装App本质是一个二进制文件,而静态分析是建立在分析二进制上面的。所以在学习逆向之前,了解汇编知识非常有必要。

汇编语言的发展

机器语言

由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)

后期为了更加高效的编程,在汇编语言的基础之上有了更加接近人类的自然语言例如C/C++/Java/OC/Swift等

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

我们的代码在终端设备上是这样的过程:

image.png

汇编语言与其他语言的对应关系

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

汇编语言的特点

  • 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能
  • 能够不受编译器的限制,对生成的二进制代码进行完全的控制
  • 目标代码简短,占用内存少,执行速度快
  • 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性
  • 知识点过多,开发者需要对CPU等硬件结构有所了解,不易于编写、调试、维护
  • 不区分大小写,比如mov和MOV是一样的

汇编语言的用途

任何高级语言最终都会被编译成汇编,了解汇编的相关知识,可以更好的开发、学习探索中帮助我们更好的排查问题、理解底层运行的机制。

  • 编写驱动程序、操作系统(比如Linux内核的某些关键部分)
  • 对性能要求极高的程序或者代码片段,可与高级语言混合使用(内联汇编)
  • 软件安全
    1. 病毒分析与防治
    2.逆向\加壳\脱壳\破解\外挂\免杀\加密解密\漏洞\黑客
  • 理解整个计算机系统的最佳起点和最有效途径
  • 为编写高效代码打下基础
  • 弄清代码的本质

汇编语言的种类

目前讨论比较多的汇编语言有

  • 8086汇编(8086处理器是16bit的CPU)
  • Win32汇编
  • Win64汇编
  • ARM汇编(嵌入式、Mac、iOS)
    iPhone里面用到的是ARM汇编,但是不同的设备也有差异.因CPU的架构不同。下面是iPhone中不同架构所对应的设备
image.png

汇编常识

  • 要想学好汇编,首先需要了解CPU等硬件结构
  • APP/程序的执行过程
image.png
  1. 可执行文件:程序/App在本地磁盘中即为可执行文件
  2. image(镜像文件):可执行文件装载到内存即为镜像文件,因为与可执行文件是一模一样,相当于copy过来
  3. 内存中除了指令,还有数据,但是都是0和1组合,CPU是通过部件PC寄存器来区分
  • 硬件相关最为重要是CPU/内存
  • 在汇编中,大部分指令都是和CPU与内存相关的

总线

总线是CPU与内存之间的桥梁,下图所示为iPhone X上的A11芯片

image.png image.png

每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
总线:一根根导线的集合
总线的分类,如下图所示

image.png
  • 地址总线:CPU是通过地址总线来指定存储单元的
    1. 它的宽度决定了CPU的寻址能力
    2. 8086的地址总线宽度是20,所以寻址能力是1M( 2^20 )
      3.内存地址的单元是 字节byte(简写为B),每个字节里面可以放8位(即bit),内存条的图示
image.png

数量单位和容量单位区分
数量单位: 1M=1024k,1k=1024
容量单位: 字节Byte(B) 1024B=1KB,1024KB=1MB,IBM银行的独立系统是以2字节为一个单位,常用的电脑是以1字节为一个单位
网络带宽100M=100Mbpt,传输速率单位(比特位,每秒传输12.5MB/s)

  • 数据总线:CPU与内存/其他部件之间的数据传送通道
    1. 它的宽度决定了CPU的单次数据传送量,也就是数据传送速度
    2. 8086的数据总线宽度是16,所以单次最大传递2个字节的数据
      我们常说的32位(4字节)、64位(8字节)CPU,这里的32、64指的就是数据吞吐量
  • 控制总线:CPU通过控制总线对外部器件进行控制
    1. 它的宽度决定了CPU对其他器件的控制能力、能有多少种控制
    2. 控制总线是控制线数量之和
image.png

CPU从内存的3号单元读取数据

  • CPU想操作内存中的数据,首先需要找到内存地址:CPU通过地址总线,将3这个地址传递给内存,即寻址到内存的3号单元
  • 需要操作3单元的数据,还需要确定是读还是写:CPU通过控制总线告诉内存需要进行的操作,假设是读
  • 内存知道了CPU想要进行的操作:内存将3号单元的数据通过数据线传递给CPU

内存

CPU是通过总线和硬件设备连接的
内存有RAM主存储器、RAM(主存储器)内存条

image.png

内存按照物理地址划分为主存储器、显存地址、显卡地址、网卡地址、系统地址

截屏2021-03-26 下午11.03.52.png

内存中的低地址是给用户用的,高地址是给系统用的,如下所示

image.png
  • 内存地址空间的大小受CPU地址总线宽度的限制。8086的地址总线宽度为20,可以定位220个不同的内存单元(内存地址范围0x00000~0xFFFFF),所以8086的内存空间大小为1MB
  • 0x00000~0x9FFFF:主存储器。可读可写
  • 0xA0000~0xBFFFF:向显存中写入数据,这些数据会被显卡输出到显示器。可读可写
  • 0xC0000~0xFFFFF:存储各种硬件\系统信息。只读

进制

进制的定义

  • 八进制由8个符号组成:0 1 2 3 4 5 6 7 逢八进一
  • 十进制由10个符号组成:0 1 2 3 4 5 6 7 8 9逢十进一
  • N进制就是由N个符号组成:逢N进一
做个练习
1 + 1 在____情况下等于 3 ?
十进制由10个符号组成: 0 1 3 2 8 A B E S 7 逢十进一
如果这样定义十进制: 1 + 1 = 3
这样的目的何在?
传统我们定义的十进制和自定义的十进制不一样.那么这10个符号如果我们不告诉别人这个符号表,别人是没办法拿到我们的具体数据的!用于加密!

进制的运算
八进制加法表

 0  1  2  3  4  5  6  7 
10 11 12 13 14 15 16 17
20 21 22 23 24 25 26 27
...

1+1 = 2                     
1+2 = 3   2+2 = 4               
1+3 = 4   2+3 = 5   3+3 = 6
1+4 = 5   2+4 = 6   3+4 = 7   4+4 = 10  
1+5 = 6   2+5 = 7   3+5 = 10  4+5 = 11  5+5 = 12
1+6 = 7   2+6 = 10  3+6 = 11  4+6 = 12  5+6 = 13  6+6 = 14
1+7 = 10  2+7 = 11  3+7 = 12  4+7 = 13  5+7 = 14  6+7 = 15  7+7 = 16

八进制乘法表

0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20 21 22 23 24 25 26 27...
1*1 = 1                     
1*2 = 2   2*2 = 4               
1*3 = 3   2*3 = 6   3*3 = 11    
1*4 = 4   2*4 = 10  3*4 = 14  4*4 = 20
1*5 = 5   2*5 = 12  3*5 = 17  4*5 = 24  5*5 = 31
1*6 = 6   2*6 = 14  3*6 = 22  4*6 = 30  5*6 = 36  6*6 = 44
1*7 = 7   2*7 = 16  3*7 = 25  4*7 = 34  5*7 = 43  6*7 = 52  7*7 = 61

数据的宽度

数学上的数字,是没有大小限制的,可以无限的大。
但在计算机中,由于受硬件的制约,数据都是有长度限制的(我们称为数据宽度),超过最大宽度的数据会被丢弃

以下例子都是真机调试

// 创建demo空工程
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int test(){
    int cTemp = 0x1FFFFFFFF;
    return cTemp;
}

int main(int argc, char * argv[]) {
    printf("%x\n",test());
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

打断点调试,可以发现1已经溢出

image.png

也可以通过获取地址,在Debug-Debug Workflow-ViewMemory中输入地址查看,下图Address 输入地址,Page 也可以翻页

image.png

计算机中常见的数据宽度

  • 位(Bit):1个位就是1个二进制位,即0或1
  • 字节(Byte):1个字节由8个Bit组成,内存中的最小单元Byte
  • 字(Word):1个字由两个字节组成(16位),第2个字节分别称为高字节和低字节
  • 双字(DoubleWord):1个双字由两个字组成(32位)

计算机存储数据它会分为有符号数和无符号数,看下图

image.png
无符号数: 直接换算!
有符号数: 符号放在第1位,第1位是0即正数,为1即负数:
正数:  0    1    2    3    4    5    6    7 
负数:  F    E    D    B    C    A    9    8
      -1   -2   -3   -4   -5   -6   -7   -8

CPU&寄存器

内部部件之间由总线连接

image.png
  • CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储。
  • CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
  • 对于arm64系的CPU来说, 64位寄存器以x开头,32位寄存器以w开头
  • 在系统中没有提供16位和8位的寄存器供访问和使用,其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的。
  • 对程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制
  • 不同的CPU,寄存器的个数、结构是不相同的

浮点和向量寄存器

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

  • 浮点寄存器 64位: D0 - D31 32位: S0 - S31
    现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.
  • 向量寄存器 128位:V0-V31

通用寄存器

  • 通用寄存器也称数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
  • ARM64拥有32个64位的通用寄存器 x0 到 x30,以及XZR(零寄存器)。这些通用寄存器有时也有特定用途。
    那么w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位.
    比如 w0 就是 x0的低32位!
    注意:了解过8086汇编就会知道,有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中,在ARM中并没有

通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间

image.png
  • CPU首先会将红色内存空间的值放到X0寄存器中:mov X0,红色内存空间
  • 然后让X0寄存器与1相加:add X0,1
  • 最后将值赋值给内存空间:mov 蓝色内存空间,X0

pc寄存器(program counter)

  • 为指令指针寄存器,它指示了CPU当前要读取指令的地址
  • 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息
  • CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义
    比如 1110 0000 0000 0011 0000 1000 1010 1010
    可以当做数据 0xE003008AA
    也可以当做指令 mov x0, x8
  • CPU根据什么将内存中的信息看做指令?
    CPU将pc指向的内存单元的内容看做指令
    如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过

寄存器查看

image.png

通用寄存器中fp对应x29,lr对应x30,fp与lr是对寄存器起了别名,如下图

image.png

Xcode查看汇编 Debug-Debug Workflow-Always Show Disassembly

image.png image.png

pc寄存器调试

  • 打印pc寄存器现在是c2138,可以看出来内存地址都相同
image.png
  • 按住control+Step into,继续打印
image.png
  • 除了读还可以写
    register write pc 0x104c31ecc
    register read pc 此时是读不出来的,因为断点断住了,如果step into,此时断点断在哪里?最终通过验证发现,会断在1ecc的下一行
截屏2021-03-27 上午11.16.06.png 截屏2021-03-27 上午11.16.26.png

此时pc指向1ecc,会将1ecc中的指令拿出来执行,执行完毕后走到1ecc的下一条指令,而此时d0的指令是还没有执行的

高速缓存

iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.
CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.

bl指令

  • CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
  • ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,比如mov x0,#10、mov x1,#20
  • 但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
  • ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令
  • bl是两个指令,b就是跳转指令
    bl指令 -- 练习
    现在有两段代码!假设程序先执行A,请写出指令执行顺序.最终寄存器x0的值是多少?
_A:
    mov x0,#0xa0
    mov x1,#0x00
    add x1, x0, #0x14
    mov x0,x1
    bl _B
    mov x0,#0x0
    ret

_B:
    add x0, x0, #0x10
    ret
    
<!--结果-->
流程:
    mov x0,#0xa0            -- x0:0xa0
    mov x1,#0x00            -- x1:0x00
    add x1, x0, #0x14       -- x1:0xa0+0x14=0xb4
    mov x0,x1               -- x0:0xb4
    add x0, x0, #0x10       -- x0:0xb4+0x10=0xc4
    ret                     -- 回到bl跳转的下一行
    mov x0,#0x0             -- x0:0x00

x0的值:0x00

下面编写汇编代码验证 Cmd+n -- empty (other下)-- asm.s(汇编代码文件,会编译成源码)

// asm.s文件内容
.text
.global _A,_B

_A:
    mov x0,#0xa0
    mov x1,#0x00
    add x1, x0, #0x14
    mov x0,x1
    bl _B
    mov x0,#0x0
    ret

_B:
    add x0, x0, #0x10
    ret

// ViewController.m 中调用
@implementation ViewController

// 汇编A函数声明
int A();

- (void)viewDidLoad {
    [super viewDidLoad];
    A();
    // Do any additional setup after loading the view.
}
@end

在A()执行处加断点,并执行程序,开启汇编调试
开始调用A函数

image.png

按住control+Step into,继续执行,跳入汇编A函数内

image.png

一步步执行,当执行到0x102925efc <+16>: bl 0x102925f08 ; B,按住control+Step into进入汇编代码B

image.png

继续一步步执行回到汇编代码A

image.png

此时我们发现继续执行的时候,一直在汇编代码A的最后两行执行,发生了死循环,退不回ViewController中

0x102925f00 <+20>: mov    x0, #0x0
->  0x102925f04 <+24>: ret    

分析原因: 汇编代码A中没有保存回去的路,原因我们下一篇讨论

相关文章

  • <安全攻防之汇编基础>

    &关于汇编基础请点击 <汇编一> <汇编二> <汇编三> <汇编四> <汇编五> <汇编六> <汇编七> <汇编八...

  • 逆向笔记(三)-汇编指令

    汇编指令 针对汇编来说,汇编指令是必须了解的,而且汇编中汇编指令比较多,但是每一个汇编指令对应的机器码是固定不变的...

  • 汇编一、初识汇编

    开发语言的发展 机器语言 由0和1组成的机器指令,如: 加:0100 0000 减:0100 1000 汇编语言 ...

  • 汇编(一) -- 初识汇编

    前言 最近准备学习汇编,然后在B站上看到叫iOS小贤的作者发的视频挺不错,打算跟着学,文章是看视频的笔记,最后有原...

  • 【汇编】一、初识汇编

    001--初识汇编 我们在学习逆向开发之前,我们要了解一个基本的逆向原理.首先我们是逆向iOS系统上面的APP.那...

  • iOS逆向学习(arm64汇编入门)

    iOS汇编 iOS汇编语音有很多钟。常见的有8086汇编、arm汇编、x86汇编等等。 arm汇编 iOS的架构从...

  • ARM64汇编入门 - 汇编基础

    ARM64汇编入门 - 汇编基础ARM64汇编入门 - 汇编基础

  • 汇编基础笔记一

    汇编指令 汇编代码,高级语言-》汇编代码-》二进制-》计算机执行 高级语言只能转换成一种汇编代码,汇编代码可能转换...

  • asm 汇编器及虚拟机

    汇编器是用于把汇编语言翻译为机器语言的程序 汇编 汇编器, 将汇编一比一翻译为机器码 虚拟机程序 将汇编翻译成机器...

  • ARM64汇编学习笔记一(初始汇编)

    ARM64汇编学习笔记一(初始汇编)

网友评论

      本文标题:汇编(一)

      本文链接:https://www.haomeiwen.com/subject/bjlacltx.html