iOS攻防

汇编-基本概念

2021-03-23  本文已影响0人  HotPotCat

在逆向开发中,非常重要的一个环节就是静态分析。对于逆向iOS app来说,一个APP安装在手机上面的可执行文件本质上是二进制文件。因为iPhone手机本质上执行的指令是二进制。是由手机上的CPU执行的,静态分析是建立在分析二进制上面。

汇编语言的发展

机器语言

01组成的机器指令。0代表有电,1代表没电。

汇编语言(assembly language)

为了高效的写代码出现了助记符,使用助记符代替机器语言,如:

助记符就是汇编语言的前身,当有专门的编译器出现的时候就有了汇编语言。

高级语言(High-level programming language)

C\C++\Java\OC\Swift,更加接近人类的自然语言。
比如C语言:

代码在终端设备上的过程:


15193669666308.jpg

汇编语言的特点

汇编的用途

汇编语言的种类

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

iPhone里面用到的是ARM汇编,但是不同的设备也有差异(因CPU的架构不同)。

位数 架构 设备
32 armv6 iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch
32 armv7 iPhone3GS, iPhone4, iPhone4S,iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4
32 armv7s iPhone5, iPhone5C, iPad4(iPad with Retina Display)
64 arm64 iPhone5s,iPhone6、7、8,iPhone6、7、8 Plus,iPhone X,iPad Air,iPad mini2(iPad mini with Retina Display)
64 arm64e XS/XS Max/XR/ iPhone 11, iPhone 11 pro 及以后
64 x86_64 模拟器64位处理器 (intel)
32 i386 模拟器32位处理器(intel)

⚠️:苹果A7处理器支持两个不同的指令集:32ARM指令集(armv6|armv7|armv7s)和64ARM指令集(arm64

汇编相关的学习需要了解CPU等硬件结构,最为重要的是CPU/内存。在汇编中,大部分指令都是和CPU与内存相关的。
APP/程序的执行过程:


15193672391363.jpg

可执行文件(磁盘中)加载到内存中叫image(镜像文件),早期都是直接拷贝,后面为了安全有了加壳加密签名验证。在cpu眼中所有的数据都是01,那么怎么区分指令和数据呢?通过pc寄存器区分。

总线

15193693448725.jpg

比如:


image.png

执行过程:
1.地址总线先去内存地址。
2.控制读取发送读/写命令。
3.数据总线写数据->内存/ 内存发送数据->数据总线

地址总线

数据总线

我们现在常说的32位,64位cpu说的就是它的数据吞吐量。1次放电分别4字节,8字节数据。

控制总线

案例:
1.一个CPU 的寻址能力为8KB,那么它的地址总线的宽度为____
答案:8KB对应 8192, 213 = 8192 所以为13。

  1. 8080,8088,80286,80386 的地址总线宽度分别为16根,20根,24根,32根。那么他们的寻址能力分别为多少____KB, ____MB,____MB,____GB?
    答案:1kb = 210 = 1024
    1kb * 26 = 64kb
    1kb * 1kb = 1mb
    1mb * 24 = 16mb
    1kb * 1kb * 1kb * 22 = 4gb

  2. 8080,8088,8086,80286,80386 的数据总线宽度分别为8根,8根,16根,16根,32根.那么它们一次可以传输的数据为:____B,____B,____B,____B,____B
    答案:1 、1、2、2、4

4.从内存中读取1024字节的数据,8086至少要读____次,80386至少要读取____次.
答案:8086 数据总线宽度为16。8086一次读2个字节,那么需要512次,80286数据总线宽度为32,一次4个字节,需要256次。

内存

image.png image.png image.png

进制

想学好进制首先要忘掉十进制,也要忘掉进制间的转换。

进制的定义

⚠️:进制的本质是符号。

案例

  1. 1 + 1 在____情况下等于 3 ?
    除了算错的情况下。在十进制由10个符号组成,假如由: 0 1 3 2 8 A B E S 7组成逢十进一,那么在这种情况下1+1=3

传统定义的十进制和自定义的十进制不一样。那么这10个符号如果我们不告诉别人这个符号表,别人是没办法拿到我们的具体数据的,可以用于加密!
⚠️:十进制由十个符号组成,逢十进一,符号是可以自定义的!!!

  1. 八进制运算:

八进制加法表
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

二进制的简写形式

               二进制: 1 0 1 1 1 0 1 1 1 1 0 0
三个二进制一组: 101 110 111 100
                八进制:    5     6     7      4
四个二进制一组: 1011 1011 1100
            十六进制:     b        b       c

二进制:从 0 写到 1111
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
这种二进制使用起来太麻烦,改成更简单一点的符号:
0 1 2 3 4 5 6 7 8 9 A B C D E F 这就是十六进制了

数据的宽度

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

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

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%x",test());
}

输出:

ffffffff
image.png
数据溢出了。刚开始cTemp默认值1,溢出后变为-1第一位符号位,1代表负数,0代表正数。往后逐位取反,末尾加1。)。
(lldb) p cTemp
(int) $0 = 1
(lldb) p cTemp
(int) $1 = -1
(lldb) p &cTemp
(int *) $2 = 0x000000016b3a9b1c
(lldb) x 0x000000016b3a9b1c
0x16b3a9b1c: ff ff ff ff 10 00 00 00 00 00 00 00 ef 98 3a 6b  ..............:k
0x16b3a9b2c: 01 00 00 00 70 a9 f0 59 01 00 00 00 50 d4 a5 04  ....p..Y....P...
(lldb) p (uint)cTemp
(uint) $3 = 4294967295

Debug -> Debug Workflow -> View Memory中也可以查看(这里查看内容更新后需要翻页刷新然后切换回来才能显示新值):

image.png
再看下汇编代码(Debug -> Debug Workflow -> Always Show Disassembly):
TestDemo`test:
    0x104a59ec8 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x104a59ecc <+4>:  mov    w8, #-0x1
    0x104a59ed0 <+8>:  str    w8, [sp, #0xc]
->  0x104a59ed4 <+12>: ldr    w0, [sp, #0xc]
    0x104a59ed8 <+16>: add    sp, sp, #0x10             ; =0x10 
    0x104a59edc <+20>: ret    

可以看到直接将-1给力w8。指令在内存中占用4字节。

计算机中常见的数据宽度

计算机存储数据会分为有符号数和无符号数(对于数据本身内容没有变化,取决于你怎么看):


image.png

无符号数,直接换算!
有符号数:
正数: 0 1 2 3 4 5 6 7
负数: F E D B C A 9 8
-1 -2 -3 -4 -5 -6 -7 -8

自定义进制符号

案例:

十进制:
0 1 2 3 4 5 6 7 8 9
自定义:
2 9 1 7 6 5 4 8 3 A
92 99 91 97 96 95 94 98 93 9A
12 19 11 17 16 15 14 18 13 1A
72 79 71 77 76 75 74 78 73 7A
62 69 61 67 66 65 64 68 63 6A
52 59 51 57 56 55 54 58 53 5A
42 49 41 47 46 45 44 48 43 4A
82 89 81 87 86 85 84 88 83 8A
32 39 31 37 36 35 34 38 33 3A
922

转换后加法表:

9+9 = 1                 
9+1 = 7  1+1 = 6
9+7 = 6  1+7 = 5  7+7 = 4
9+6 = 5  1+6 = 4  7+6 = 8   6+6 = 3
9+5 = 4  1+5 = 8  7+5 = 3   6+5 = A   5+5 = 92
9+4 = 8  1+4 = 3  7+4 = a   6+4 = 92  5+4 = 99  4+4 = 91
9+8 = 3  1+8 = A  7+8 = 92  6+8 = 99  5+8 = 91  4+8 = 97  8+8 = 96
9+3 = A  1+3 = 92 7+3 = 99  6+3 = 91  5+3 = 97  4+3 = 96  8+3 = 95   3+3 = 94
9+A = 92 1+A = 99 7+A = 91  6+A = 97  5+A = 96  4+A = 95  8+A = 94   3+A = 98  A+A = 93

123 + 234 = 1A6

十进制:
0 1 2 3 4 5 6 7 8
自定义:
2 9 1 7 6 5 4 8 3
92 99 91 97 96 95 94 98 93
12 19 11 17 16 15 14 18 13
72 79 71 77 76 75 74 78 73
62 69 61 67 66 65 64 68 63
52 59 51 57 56 55 54 58 53
42 49 41 47 46 45 44 48 43
82 89 81 87 86 85 84 88 83
32 39 31 37 36 35 34 38 33
922

转换后加法表:

9+9 = 1                 
9+1 = 7   1+1 = 6
9+7 = 6   1+7 = 5  7+7 = 4
9+6 = 5   1+6 = 4  7+6 = 8   6+6 = 3
9+5 = 4   1+5 = 8  7+5 = 3   6+5 = 92  5+5 = 99
9+4 = 8   1+4 = 3  7+4 = 92  6+4 = 99  5+4 = 91  4+4 = 97
9+8 = 3   1+8 = 92 7+8 = 99  6+8 = 91  5+8 = 97  4+8 = 96  8+8 = 95
9+3 = 92  1+3 = 99 7+3 = 91  6+3 = 97  5+3 = 96  4+3 = 95  8+3 = 94   3+3 = 98

123 + 234 = 725

CPU&寄存器

内部部件之间由总线连接


image.png

CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储。

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

对于arm64系的CPU来说, 如果寄存器以x开头则表明的是一个64位的寄存器,如果以w开头则表明是一个32位的寄存器,在系统中没有提供16位和8位的寄存器供访问和使用。其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的

浮点寄存器

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

向量寄存器

现在的CPU支持向量运算。(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.

通用寄存器

⚠️:了解过8086汇编的都知道,有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中。在ARM中并没有。

在"Xcode"中我们可以查看具体寄存器的内容:


image.png image.png image.png
分别看一下x0w0的值:
x0  unsigned long   0x0000000159f0a970
w0  unsigned int    0x59f0a970

验证了w0x0的低32位。

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

image.png

pc寄存器

单步执行汇编代码(pc始终指向下一条指令):


image.png

修改pc的指向(跳过了中间的指令):


image.png

比如 1110 0000 0000 0011 0000 1000 1010 1010,
可以当做数据 0xE003008AA。
也可以当做指令 mov x0, x8

CPU将pc指向的内存单元的内容看做指令
如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被pc指向过。

高速缓存

iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M。

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

bl指令

bl分位bl:
b:跳转。
l:lr寄存器。

案例
现在有两段代码!假设程序先执行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

分析:
Xcode中创建Empty文件命名为asm.s.s汇编代码会被Xcode自动识别编译)。

//asm.s
.text   // 告诉是代码
.global _A, _B  //.global 是标号

_A:
mov x0,#0xa0             //a0 给 x0           x0 = 0xa0
mov x1,#0x00             //00 给x1            x1 = 0x00
add x1, x0, #0x14        //x0 +  0x14  给 x1  x1 = 0xb4
mov x0,x1                //x1 的值给 x0        x0 = 0xb4
bl _B                    //跳转B
mov x0,#0x0              //0x0 给 x0          x0 = 0x0
ret                      //return 上层调用的地方

_B:
add x0, x0, #0x10       //x0 + 0x10 给 x0     x0 = 0xc4
ret                     //return  A

oc调用汇编:

//ViewController.m
int A();

- (void)viewDidLoad {
    [super viewDidLoad];
    A();
}

swift调用汇编:

//声明方法A。Swift中C和汇编都可以这么暴露。
@_silgen_name("A")
func A()

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        A();
    }
    
}

答案:0x00

image.png
断点验证了x0最终值为0x00。这里有个问题是发生死循环了。(bl跳转指令导致的,lr寄存器在跳转后需要保护现场还原。)

总结

上一篇下一篇

猜你喜欢

热点阅读