2016976438 发表于 2023-4-26 22:39

luckfollowme arm学习篇5 - 认识arm

本帖最后由 2016976438 于 2023-4-26 22:44 编辑

# luckfollowme arm学习篇5 - 认识arm

上一章各位都运行起来了吧

这一章让我们一起分析分析 当中的 arm 指令



## 简介

我只能勉强带你入门,因为我也只是刚入门的水准,arm 那么多指令 记不住 根本记不住,只能按照例子来,见到啥说啥。

我只说 arm64 ,剩余 arm32 如果大伙有需要的话我在写一章

貌似 arm 也叫做 aarch 遇到的时候注意点就是的了

## 寄存器

在讲解 指令之前 ,先了解些基本概念。

首先 **arm64** 中有 **x0~x31** 的寄存器。

这 **32** 个寄存器都是**64位**的。

有时候我们只需要存储**32 位**的。还有 **w0~w31** 的写法,他们会存储在**64位中 低32位**的位置

当然您还会看到**cpsr** 这种状态寄存器。

了解过 **x86** 的人来说会比较熟悉。

最常见的就是

**N(Nagative) 结果是否是负数**

**Z(Zero) 结果是否为 0**

**C(Carry) 结果是否进位**

**V(Overflow) 在x86 是 o 标志位**

一般计算结果的标志位可用于 条件判断 。 如 Z = 1 代表 两边结果相等



当然每个寄存器您也不用特意记,后面用到的时候我在提出来,这样记得更清楚一点。



目前您只需要了解最基本的:

x30 链接寄存器

x31 堆栈寄存器

**还有个程序计数器 PC 用于记录当前指令执行的地址**



## 示例

我们将之前的代码改成 如下的:

```c
#include "stdio.h"

int _global_sum;
// 使用 标准 C 约束 生成的 方法名不会变得很奇怪
extern "C"
{
    int sum(int a, int b)
    {

      //4. 测试加法 和 局部变量
      int sum = a + b;
      //5. 测试状态寄存器 和 条件跳转
      if (sum > 10)
      {
            //6. 测试全局变量
            _global_sum = sum;
      }
      // 7. 测试返回值
      return _global_sum;
    }
}

int main()
{
    //1. 测试b 指令 死循环
    while (true)
    {
      //2. 测试局部变量赋值指令
      int a = 10;
      int b = 20;
      //3. 测试传参 和 调用方法
      int ret = sum(a, b);
      printf("sum:%d\n", ret);

      getchar();
    }
    return 0;
}

```



然后使用 **ndk_builder.py adb_execute_arm64.bat** 执行起来

**强调一点。 如果您通过 netstat 没有看到 23946 端口 。 那么请重新启动 ida pro android_server64 并执行端口转发 adb forward tcp:23946 tcp:23946**

**如果您的android 模拟器出现问题 。 它的 avd 应该在 Android\sdk\system-images\android-apiLevel 目录下,删掉重新创建即可**



### Ida Pro Debugger

启动完砸门的程序后 您 俺任意键他会重复执行 方便我们调试分析

**然后 用 ida pro 进行 debuger 附加动态分析**

ida pro 当然也支持静态分析(拖入 elf 文件进去即可)



但是我们要看栈的情况来分析接下来的指令,所以用 debugger附加的形式

话不多说,先来分析下ida pro 一些基本界面



1.首先您进来注意右边的寄存器。 注意观察 **x30**还有 **SP(x31) 和 PC**

其中 **X30** 是 linker 地址 可以理解为程序的返回后地址

**SP 是当前的栈顶指针**

**PC 是当前执行指令的地址**

在寄存器的右边 就是 状态寄存器 对应着 **PSR**

在最下面是 **stack view** 栈视图 后面会说

</p>



</p>



2.其次您需要做些准备,进入到main方法后,将我图中被 ida pro 自动分析符号 的 **按Q** 转换成偏移数字。

并在图中所示的地方下个断点。

</p>





</p>

下的断点的地方刚刚是在**死循环**的内部。

为什么不在上面的的代码上:

```asm
SUB             SP, SP, #0x20
STP             X29, X30,
ADD             X29, SP, #0x10
STUR            WZR,
B               loc_74C717E714
```

因为我们直接启动程序后 直接跑到我们的死循环内部了,上面的代码是不会在执行的了。

其实我们可以通过 ida pro 创建进程来直接调试,这样可以在程序一开始的地方就断下来,但那种有点麻烦。

不过我们可以通过分析砸门另一个 **sum 方法** 来分析上面的指令。



因为 上面的指令如

```asm
SUB             SP, SP, #0x20
```

明显是给 sp 栈空间分配大小的,按道理标准的调用约定 都会完成这种指令。

**对于 常见调用约定一般都是部分参数入寄存器 其余参数从右到左入栈 被调用的方法实现栈平衡 随后返回值放入第一个寄存器 如 eax| r0 | x0**



下面是我们将要分析的完整汇编指令,我们将逐步分析:

```asm
MOV             W8, #0xA
STR             W8,
MOV             W8, #0x14
STR             W8,
LDR             W0,
LDR             W1,
BL            sum

STR             W0,
LDR             W1,
ADRL            X0, aSumD               ; "sum:%d\n"
BL            unk_74C717E790

BL            unk_74C717E7A0

B               loc_74C717E714
```



### MOV

接下来您按 **F9** 运行程序,然后在程序输入任意键重新进入断点

接下来您会断第一条语句上:

```asm
MOV             W8, #0xA
```

这段指令的意思是 向 **x8 寄存器**赋值 **0xA** 也就是 16

**注意 w8 意思是低32位**

此时我的 **x8** 寄存器是 **0x000000000000003F** 按 **F8** 步过后 会变成 **0x000000000000000A**



### STR

接下来看 STR 指令:

```asm
STR             W8,
```



STR 您也可以读成 store register 意思是储存寄存器。

它可以将 寄存器的值 储存在内存中。

上面意思您可以理解成 = w8

也就是 **栈地址 + 8** 赋值 **w8** 的值



点击 **stack view** 后 在点击 寄存器上的 **SP(x31)** 跳到当前的栈顶:

```
0000007FC2455540000000140000001E
0000007FC2455548000000000000000A
```

**0000007FC2455540** 是当前的栈顶

执行完 **STR             W8, ** 指令后 堆栈下面的**0000007FC2455548** 会变成 A,由于上一次循环设置过了,所以我这里本身就是 A



经过上面的说明,您应该也能分析,接下的指令:

```asm
MOV             W8, #0x14 # 将 0x14 放到 x8
STR             W8,       # 将 由于 w8 是低 32 位 所以 sp 只加 4
```

注意一点的是 **sp + 4** 的位置 还是看之前栈的数值:

```
0000007FC2455540000000140000001E
分开32 位是 00000014 0000001E
```

您可能会有疑惑,为什么不是 **0000001E 00000014** 而是反过来的?

这就要设计到小端排序的问题了。

小端排序是什么意思呢?

我们通过 **hex view** 进行分析。

先点击 **hex view** 在点击 寄存器的 **SP** 然后您应该就可以看到如下所示:



</p>



</p>



正确的数值是 : **1E 00 00 00 14 00 00 00**

此时您更懵逼了,但是您可以先抽出32 位的来看。

```
14 00 00 00
```

由于小端排序是低地址储存高位字节。

所以我们取出数据 得从最右边开始取,转换过来就是:

```
00 00 00 14
```

这种结果就是对的。

那如果是 **64** 位呢?

```
               1E 00 00 00 14 00 00 00
转换成          00 00 00 14 00 00 00 1E      
```

此时就对了。



然后我们整合下面的指令的意思就是 **[ sp + 8 ] = 10**   ** = 20**

```asm
MOV             W8, #0xA
STR             W8,
MOV             W8, #0x14
STR             W8,
```

对应着 c 命令

```c
//2. 测试局部变量赋值指令
int a = 10;
int b = 20;
```



而且您应该也能分析出来 **局部变量都是放在栈空间的**



### LDR

接下来在分析一下: **LDR** 指令

```asm
MOV             W8, #0xA
STR             W8,
MOV             W8, #0x14
STR             W8,
LDR             W0,
LDR             W1,
BL            sum

STR             W0,
LDR             W1,
ADRL            X0, aSumD               ; "sum:%d\n"
BL            unk_74C717E790

BL            unk_74C717E7A0

B               loc_74C717E714
```



STR 是从 寄存器 到 内存。

LDR (Store Register) 则是从 内存到寄存器

那么下面您可以直接翻译成:

```asm
LDR             W0,                 # w0 = = 10
LDR             W1,                 # w1 = = 20
```

很明显这是从 栈取出局部变量 给 寄存器



**B**

B (branch) 用于跳转某个地址上,一般用于跳到子方法上

而下面指令 BL 带 L 的可以理解为记录 **linker X30** 寄存器,也就是返回地址,因为调用完返回后我们得返回到之前执行的地方。



接下来我将带我的地址的汇编和寄存器赋值一下让你们参考下:

```
hello2:00000074C717E728 LDR             W1,
hello2:00000074C717E72C BL            sum

hello2:00000074C717E730 STR             W0,
```

```
x29         0000007FC2455550
x30         00000074C717E748
sp               0000007FC2455540
pc               00000074C717E72C
```



然后按 F8 执行完 下面的指令后:

```
hello2:00000074C717E72C BL            sum
```

注意看我的寄存器,很明显 x30 也就是 linker 变成 bl 指令下面的地址了:

```
x29         0000007FC2455550
x30         00000074C717E730
sp               0000007FC2455540
pc               00000074C717E730

hello2:00000074C717E730 STR             W0,
```



根据调用约定 部分参数放在寄存器中

结合 BL 跳转指令

下面我们将 指令整合起来看

```asm
# 1. 局部变量放在栈中
# = 10
MOV             W8, #0xA
STR             W8,
# = 20
MOV             W8, #0x14
STR             W8,
# 2. 取出局部变量 然后传参到 sum 方法
LDR             W0,
LDR             W1,
BL            sum

# 3. 根据调用约定 sum执行结束后会还原栈 随后把返回值放在 x0
# 把返回值放在栈中 也就是局部变量中
STR             W0,
```

这些应该对应 c 代码中的

```c
   //2. 测试局部变量赋值指令
      int a = 10;
      int b = 20;
      //3. 测试传参 和 调用方法
      int ret = sum(a, b);
```



### 伪指令 ADRL

下面一条指令是

```asm
ADRL            X0, aSumD
```



这是一条计算地址的指令。 但实际**arm64** 中并没有这条指令

这条指令就是伪指令。

一般来说 **arm** 指令都是 4字节

如何看指令字节码呢?

点击 **Options->General**

在 **opcode bytes** 输入 10

</p>



</p>

</p>



</p>

可以看到它实际指令的字节码是 **00 00 00 90 00 20 15 91**

我们把他放入在线网站转换一下**https://armconverter.com/**

```
ADRL            X0, 0x7B13862548
转换成
adrp x0, #0x7b13862000
add x0, x0, #0x548
```

可以看到 **ADRL** 实际是 **adrp 和 add**两个指令而来



**adrp 实际也是个伪指令**

个人感觉

adrp 应该是取当前 pc 内存对齐后的数值,然后行为类似 mov

可能mov 因为字节码长度限制的原因 不能把这么大的数直接放入 x0,所以用 **adrp 伪指令**

整理以下意思应该是这样的:

```
adrp x0, #0x7b13862000      # 获取pc内存对齐后的地址 0x7b13862000 复制给 x0
add x0, x0, #0x548                # x0 = x0 + 0x548
```

这种行为很像获取 **基址 + 属性地址偏移**

我们双击这条指令的 **0x7B13862548**的值进行跳转

```
ADRL            X0, 0x7B13862548
```



</p>



</p>

这应该就是我们 **printf** 里面字符串的参数了

```c
printf("sum:%d\n", ret);
```

也可以分析出里面 **DCB 伪指令**就是分配储存数据的

那么 ADRL 伪指令就是将储存 **"sum:%d\n"** 字符串的 指针给放到 X0 寄存器中

```
ADRL            X0, 0x7B13862548
```



### 整合分析

```
# 0. 死循环记录地址
loc_7B13862714

#1.设置局部变量 到栈中
MOV             W8, #0xA
STR             W8,
MOV             W8, #0x14
STR             W8,

# 2. 取出局部变量 作为参数调用 sum方法
LDR             W0,
LDR             W1,
BL            sum
# 3. 取出调用 sum 的返回值 给 栈中
STR             W0,

# 4. 取出sum 返回值 又放到 x1 寄存器
# 取出储存 "sum:%d\n" 数据的 指针放到 x0 寄存器
# 调用 unk_74C717E790(printf) 方法传递 x0 x1 参数
LDR             W1,
ADRL            X0, aSumD               ; "sum:%d\n"
BL            unk_74C717E790

# 5. getchar
BL            unk_74C717E7A0

# 6. 跳到上面的死循环
B               loc_74C717E714
```





下一章我会给大家分析下 sum 内部方法 的 调用约定 和 栈平衡

xixicoco 发表于 2023-4-27 01:19

好的,欢迎科普文章

Dreadnought 发表于 2023-4-27 09:44

感谢分享

han109014 发表于 2023-4-27 09:56

学到老,活到老,

laustar 发表于 2023-4-27 10:06

        用心讨论,共获提升!

howe1 发表于 2023-4-27 10:50

感谢分享

xcyzh1992 发表于 2023-4-27 10:51

谢谢分享,学习中

aonima 发表于 2023-4-27 13:07

感谢分享

kerwenfly 发表于 2023-4-29 08:21

收藏了,有空慢慢没研究~
页: [1]
查看完整版本: luckfollowme arm学习篇5 - 认识arm