吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1647|回复: 1
收起左侧

[Android 原创] <luckfollowme> arm 学习篇06 - 调用约定

[复制链接]
2016976438 发表于 2023-4-28 16:33

luckfollowme arm 学习篇06 - 调用约定

在 arm64 的调用约定中

前八个整数参数依次存储在 x0-x7 中。

其余的参数会入栈

调用完毕后 返回结果会放入在 x0 中

所以您目前需要留意几个寄存器

x0~x7 储存参数的

x29(Frame Pointer)  帧指针

x30(LR) 链接寄存器用于跳回去的地址

x31(SP) 堆栈指针

调整项目

为了更清晰 的理解 arm64 的调用约定,我们把 sum 的传参改成 10位。并把剩下两个参数 改成

uint64 这样更能体验 64位 的数据储存

  #include "stdio.h"
  #include "stdint.h"

  uint64_t _global_sum;

  int sum(int a1, int a2,int a3,int a4,int a5,int a6,int a7,int a8,uint64_t a9,uint64_t a10)
  {

      //4. 测试加法 和 局部变量
      int sum = a1 + a2 + a3 + a4 + a5 + a6 + a7+ a8;
      uint64_t sum2 = a9 + a10;
      //5. 测试状态寄存器 和 条件跳转
      if (sum > 10)
      {
          //6. 测试全局变量
          _global_sum = sum + sum2;
      }
      // 7. 测试返回值
      return _global_sum;
  }

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

          getchar();
      }
      return 0;
  }

指令集分析

下面是 main 方法最终生成的 指令集:

loc_788                                 ; CODE XREF: main+10↑j
                                        ; main+74↓j
MOV             X8, #0xA
STUR            X8, [X29,#-0x10]
MOV             X8, #0x14
STR             X8, [SP,#0x18]
LDUR            X10, [X29,#-0x10]
LDR             X8, [SP,#0x18]
MOV             X9, SP
STR             X10, [X9]               ; a9
STR             X8, [X9,#8]             ; a10
MOV             W0, #1                  ; a1
MOV             W1, #2                  ; a2
MOV             W2, #3                  ; a3
MOV             W3, #4                  ; a4
MOV             W4, #5                  ; a5
MOV             W5, #6                  ; a6
MOV             W6, #7                  ; a7
MOV             W7, #8                  ; a8
BL              _Z3sumiiiiiiiimm        ; sum(int,int,int,int,int,int,int,int,ulong,ulong)

STR             W0, [SP,#0x14]
LDR             W1, [SP,#0x14]
ADRL            X0, aSumD               ; "sum:%d\n"
BL              .printf

BL              .getchar

B               loc_788

由于我把之前 extern C  方法去掉后 ,它就以 C++ 的形式生成了 _Z3sumiiiiiiiimm 方法,因为 标准C 的方法时没有方法重载的 ,所以以 C++ 生成的方法会带上

参数类型 其中 i 是 int  m是uint64

我们摘取关键一段传参调用的 ,按照上一章讲解的知识进行分析:

# 1. 复制栈顶指针
MOV             X9, SP
# 2.储存 a9 a10 变量到 栈
STR             X10, [X9]               ; a9
STR             X8, [X9,#8]             ; a10
# 3.剩余的 a1~a8 储存在 x0~x7 寄存器
MOV             W0, #1                  ; a1
MOV             W1, #2                  ; a2
MOV             W2, #3                  ; a3
MOV             W3, #4                  ; a4
MOV             W4, #5                  ; a5
MOV             W5, #6                  ; a6
MOV             W6, #7                  ; a7
MOV             W7, #8                  ; a8
# 4. 调用sum方法 
BL              _Z3sumiiiiiiiimm

这很符合我们所说的调用约定。 x0~x7 传参 多余的参数放在栈中

我们将栈结构画个结构,记住下面现在的栈的储存值,后续我们将分析 sum 方法如何取出调用的

-34 |
-30 |
-2C |
-28 |
-24 |
-20 |
-1C |
-18 |
-14 |
-10 |
-C  |
-8  |
-4  |           
0   |   a9      <--SP指针
4   |
8   |   a10

sum 分析

我们看看 sum 方法生成的指令

SUB             SP, SP, #0x40
LDR             X9, [SP,#0x40]
LDR             X8, [SP,#0x48]
STR             W0, [SP,#0x3C]
STR             W1, [SP,#0x38]
STR             W2, [SP,#0x34]
STR             W3, [SP,#0x30]
STR             W4, [SP,#0x2C]
STR             W5, [SP,#0x28]
STR             W6, [SP,#0x24]
STR             W7, [SP,#0x20]
STR             X9, [SP,#0x18]
STR             X8, [SP,#0x10]
LDR             W8, [SP,#0x3C]
LDR             W9, [SP,#0x38]
ADD             W8, W8, W9
LDR             W9, [SP,#0x34]
ADD             W8, W8, W9
LDR             W9, [SP,#0x30]
ADD             W8, W8, W9
LDR             W9, [SP,#0x2C]
ADD             W8, W8, W9
LDR             W9, [SP,#0x28]
ADD             W8, W8, W9
LDR             W9, [SP,#0x24]
ADD             W8, W8, W9
LDR             W9, [SP,#0x20]
ADD             W8, W8, W9
STR             W8, [SP,#0xC]
LDR             X8, [SP,#0x18]
LDR             X9, [SP,#0x10]
ADD             X8, X8, X9
STR             X8, [SP]
LDR             W8, [SP,#0xC]
SUBS            W8, W8, #0xA
B.LE            loc_760

B               loc_748

; ---------------------------------------------------------------------------

loc_748                                
LDRSW           X8, [SP,#0xC]
LDR             X9, [SP]
ADD             X8, X8, X9
ADRP            X9, #0x2000
STR             X8, [X9,#0xAC0]
B               loc_760

; ---------------------------------------------------------------------------

loc_760                                 

ADRP            X8, #0x2000
LDR             X8, [X8,#0xAC0]
MOV             W0, W8
ADD             SP, SP, #0x40
RET

分配局部变量空间

在第一行 SUB 命令就是分配栈空间

SUB             SP, SP, #0x40

sub(Subtraction) 意思是相减。

上面实际就是 sp = sp - 0x40

按照之前的栈分析的话,此时的栈顶SP指针位置在 -40 的地方

-40 |   <--SP指针 
-3c |
-38 |
-34 |
-30 |
-2C |
-28 |
-24 |
-20 |
-1C |
-18 |
-14 |
-10 |
-C  |
-8  |
-4  |           
0   |   a9  
4   |
8   |   a10

读取栈空间

下面两个 LDR 就是读取之前 通过栈传参的 a9 和 a10

# 此时的 [sp + 40] 就是 a9 [sp+48] 就是a10
# x9 = [sp+40] = a9
# x8 = [sp+48] = a10
LDR             X9, [SP,#0x40]
LDR             X8, [SP,#0x48]

储存到栈空间

下面一些列的 STR 全部是储存到栈空间

# 将 x0~x7 参数放入 到栈空间中
# 也就是 a1 ~ a8
STR             W0, [SP,#0x3C]
STR             W1, [SP,#0x38]
STR             W2, [SP,#0x34]
STR             W3, [SP,#0x30]
STR             W4, [SP,#0x2C]
STR             W5, [SP,#0x28]
STR             W6, [SP,#0x24]
STR             W7, [SP,#0x20]
# 将 a9 ~ a10 也放入栈空间中
STR             X9, [SP,#0x18]
STR             X8, [SP,#0x10]

此时的栈空间应该是这种样子

-40 |   <--SP指针 
-3c |   
-38 |
-34 |
-30 |   a10
-2C |   
-28 |   a9
-24 |   
-20 |   a8
-1C |   a7
-18 |   a6
-14 |   a5
-10 |   a4
-C  |   a3
-8  |   a2
-4  |   a1              
0   |   a9  
4   |
8   |   a10

很明显在 a9 和 a10 之间 空了4 字节。 因为它们的类型是 uint64 占了 8字节

个人觉得 a1 - a10 入堆栈是下面方法的参数,它们应该也算做局部变量

int sum(int a1, int a2,int a3,int a4,int a5,int a6,int a7,int a8,uint64_t a9,uint64_t a10)

所以  a9 a10 出现了两次。 一个在 main 进行 sum的传参中

一个是 sum 的方法参数 (算作局部变量)

储存相加结果 sum

接下里就枯燥的取值相加了,我就直接写上分析的结果

# x8 = a1 + a2
LDR             W8, [SP,#0x3C]
LDR             W9, [SP,#0x38]
ADD             W8, W8, W9
# x8 = x8 + a3
LDR             W9, [SP,#0x34]
ADD             W8, W8, W9
# x8 = x8 + a4
LDR             W9, [SP,#0x30]
ADD             W8, W8, W9
# x8 = x8 + a5
LDR             W9, [SP,#0x2C]
ADD             W8, W8, W9
# x8 = x8 + a6
LDR             W9, [SP,#0x28]
ADD             W8, W8, W9
# x8 = x8 + a7
LDR             W9, [SP,#0x24]
ADD             W8, W8, W9
# x8 = x8 + a8
LDR             W9, [SP,#0x20]
ADD             W8, W8, W9
# [sp + 0xC] = x8
STR             W8, [SP,#0xC]

这些代码应该对应 C代码中的

int sum = a1 + a2 + a3 + a4 + a5 + a6 + a7+ a8;

sum 应该在 -40 + C = -34 的位置

-40 |   <--SP指针 
-3c |   
-38 |
-34 |   sum
-30 |   a10
-2C |   
-28 |   a9
-24 |   
-20 |   a8
-1C |   a7
-18 |   a6
-14 |   a5
-10 |   a4
-C  |   a3
-8  |   a2
-4  |   a1              
0   |   a9  
4   |
8   |   a10

储存相加结果 sum2

下面就对应着 sum2的:

# x8 = a9 + a10
LDR             X8, [SP,#0x18]
LDR             X9, [SP,#0x10]
ADD             X8, X8, X9
# 储存在 sp 上
STR             X8, [SP]

对应 c代码的

uint64_t sum2 = a9 + a10;

栈中储存的地方为:

-40 |   sum2  <--SP指针 
-3c |   
-38 |
-34 |   sum
-30 |   a10
-2C |   
-28 |   a9
-24 |   
-20 |   a8
-1C |   a7
-18 |   a6
-14 |   a5
-10 |   a4
-C  |   a3
-8  |   a2
-4  |   a1              
0   |   a9  
4   |
8   |   a10

PSR 和 条件助记符

PSR 之前谈到过 。它表示着状态寄存器。

在 ARM 中 ,常见的状态标记为:

N (Negative) 计算结果是否为负数

C (Carry) 结果是否进位

V (oVerflow) 结果是否溢出

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

它们一般用作于 条件分支指令

EQ:等于,当零标志位(Z)被设置时为真。
NE:不等于,当零标志位(Z)未被设置时为真。
CS(或HS):带进位(或有符号数大于或等于),当进位标志位(C)被设置时为真。
CC(或LO):无进位(或有符号数小于),当进位标志位(C)未被设置时为真。
MI:负数,当负数标志位(N)被设置时为真。
PL:正数或零,当负数标志位(N)未被设置时为真。
VS:溢出,当溢出标志位(V)被设置时为真。
VC:未溢出,当溢出标志位(V)未被设置时为真。
HI:无符号数大于,当进位标志位(C)被设置且零标志位(Z)未被设置时为真。
LS:无符号数小于或等于,当进位标志位(C)未被设置或零标志位(Z)被设置时为真。
GE:有符号数大于或等于,当负数标志位(N)与溢出标志位(V)的值相同(都是0或都是1)时为真。
LT:有符号数小于,当负数标志位(N)与溢出标志位(V)的值不同时为真。
GT:有符号数大于,当零标志位(Z)未被设置且负数标志位(N)与溢出标志位(V)的值相同且都是0时为真。
LE:有符号数小于或等于,当零标志位(Z)被设置或负数标志位(N)与溢出标志位(V)的值不同时为真。

举几个例子:

1.BEQ 代表着 相等跳转 而 Z = 1 代表 结果是 0 才执行,什么意思呢?

# x0 - x1 == 0   标志位 Z = 1 那么它们相等
subs x0,x0,x1   # sub 代表相减 s 代表影响 PSR 

2.BLT 代表着 less than 也就是小于跳转 标记符判断是 N!=V 这是什么意思呢?

首先 N 是作为判断小于的标准,下面有一个例子,其中 x0 是 3 x1 是 5 那么 N状态是 1

# 3 -5 = -2  此时 N = 1
subs x0,x0,x1
BLE address

明明 N 就足够判断 x0 是否小于 x1 , 为什么 还需要 V 标志位

您首先先了解 V 标志位的意思

V 标志表示 结果的符号位是否跟 两个寄存器不同,我举一个例子:

# 假设寄存器的大小只有 1 字节 
# r0 r1 的二进制数都是 b1000 0000 = -128
ADD r0, r0 , r1
# r0 结果按道理是 1 0000 0000  可是我说假设是由 1个字节
# r0 被截取后 变成了 0
# 此时代表了溢出 V = 1

那这跟我们 LE 有什么关系呢?

首先 N = 1 一般来说 肯定是 x0 小于 x1

但由于符号位问题可能会溢出成正数。

如下:

# 假设 寄存器还是只有 1个 字节
# r0 是 b1000 0000 = -128 && r1 = b0000 0001 = 1
subs r0, r0 , r1 
# 由于寄存器最大是 1字节 负数最大只能是 -128
# b1000 0000 -  b0000 0001 = b0111 1111 = 127
# 此时变成了负数且溢出 此时 N = 0 V = 1
# 但 r0 实际 比 r1 小

所以 N!=V 用于 有符号位的 大小判断。

3.BGT (greater than ) 大于跳转  判断标志位 N = V

正常不溢出 且 r0 - r1  不会成负数 ,那么就代表 r0 > r1

条件跳转

下面是最后剩余片段,我们只看它是如何进行条件跳转的

# 1. 从栈中取出 sum
LDR             W8, [SP,#0xC]
# 2. 跟 10 做比较 并影响 psr 状态寄存器
SUBS            W8, W8, #0xA
# 3. 如果 N!=V 也就是说 sum < 10 跳到 loc_7770038760 地址上
B.LE            loc_7770038760

# 4. 反之  sum >=0 直接跳到 loc_7770038748 上
B               loc_7770038748

; ---------------------------------------------------------------------------
# 5. 跳到 loc_7770038748 标记
loc_7770038748                         
LDRSW           X8, [SP,#0xC]
LDR             X9, [SP]
ADD             X8, X8, X9
ADRP            X9, #0x777003A000
STR             X8, [X9,#0xAC0]
B               loc_7770038760

; ---------------------------------------------------------------------------
# 6. 跳到 loc_7770038760 标记
loc_7770038760                        

ADRP            X8, #0x777003A000
LDR             X8, [X8,#0xAC0]
MOV             W0, W8
ADD             SP, SP, #0x40 ; '@'
RET

对应着 c++ 的代码:

if (sum > 10)
{
    //6. 测试全局变量
    _global_sum = sum + sum2;
}
    // 7. 测试返回值
    return _global_sum;

全局变量

在回顾一下 目前栈储存的值:

-40 |   sum2  <--SP指针 
-3c |   
-38 |
-34 |   sum
-30 |   a10
-2C |   
-28 |   a9
-24 |   
-20 |   a8
-1C |   a7
-18 |   a6
-14 |   a5
-10 |   a4
-C  |   a3
-8  |   a2
-4  |   a1              
0   |   a9  
4   |
8   |   a10

结果值肯定 是 sum > 10 的,我们只看摘取的一部分:

# 反之  sum >=0 直接跳到 loc_7770038748 上
B               loc_7770038748

; ---------------------------------------------------------------------------
# 跳到 loc_7770038748 标记
loc_7770038748                         
LDRSW           X8, [SP,#0xC]
LDR             X9, [SP]
ADD             X8, X8, X9
ADRP            X9, #0x777003A000
STR             X8, [X9,#0xAC0]
B               loc_7770038760

1.LDRSW

LDRSW 是 LDR (Load register) 的扩充指令,也是从内存中取出数据到寄存器中。

后面的SW  代表着 signed word (with optional Extend , 意思是 带符号扩充到 64 位。 也就是说 32位的数据扩充到 64位 最高位符号不变。

# 获取 sum 并转换 64 位 放入 x8 中
LDRSW           X8, [SP,#0xC]

2.ADD

# 获取 sum2 放入 x9 中
LDR             X9, [SP]
# sum + sum2
ADD             X8, X8, X9

3.ADRPPCSTR

adrp 获取 基于pc 和 目标偏移 的 4kb 对齐地址

pc 是当前指令执行的地址

str 是储存寄存器到内存地址

整合起来的意思是:

# 将当前 pc 内存对齐的地址 到 x9 中
ADRP            X9, #0x777003A000
# [x9 + 0xAC0] = sum + sum2
# [x9 + 0xAC0] 就是全局变量的指针
STR             X8, [X9,#0xAC0]

我们看看 0x777003A000 + 0xac0 在内存中是什么样子的

</p>

01.png

</p>

由于是小端排序,且是 64 位的 uint64:

     42 00 00 00 00 00 00 00
转换  00 00 00 00 00 00 00 42     

0x42 的结果是 66  就是我们 sum + sum2 的结果。

它们换算成代码就是:

_global_sum = sum + sum2;

RET

最后就讲解下返回值了

RET 实际类似与我们使用 return 命令

return _global_sum;

来看看最后的汇编做了什么:

# 1. 获取全局变量 _global_sum
ADRP            X8, #0x777003A000
LDR             X8, [X8,#0xAC0]
# 2. 将全局变量放在 x0 中用于返回
MOV             W0, W8
# 3. 释放局部变量空间
ADD             SP, SP, #0x40 ; '@'
# 4. 返回到 x30 寄存器指向的地址
RET

此时的栈空间就又变成原始的:

.....(这些被释放掉了)  
0   |   a9      <--SP指针
4   |
8   |   a10

RET 指令的话依赖于 X30 寄存器。 也就是 linker 地址。

一般在使用 BL 会计算下一条指令的位置,用图展示方便大伙分析:

</p>

02.png

</p>

用鼠标点击下 x30 寄存器跳过去

</p>

03.png

</p>

你会发现ida pro 给你翻译成数据了

此时你按 C 翻译成代码

</p>

04.png

</p>

到此你就会发现 它就是我们 BL sum 指令下的位置

下一章我们就讲解 dobby inline Hook

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
debug_cat + 2 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

debug_cat 发表于 2023-4-28 22:18
感谢分享,支持支持
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-2 22:29

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表