原生程序初次逆向之ARM与X86相关知识对比

本文对比了ARM与x86架构在寄存器使用、函数调用及Linux系统调用方面的区别,深入解析了两种架构在底层实现上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转自 看雪论坛 原文由Xbalien提供点击打开链接

小弟近来开始学习原生逆向,并对其中一些基础知识做下总结(大神勿喷,出错请指正)...


Android的原生程序运行于Linux内核上,并且处理器基于ARM,因此对于NDK的逆向,一些设计汇编知识的安全技术(注入,HOOK,劫持)等就需要了解有关知识。因此有必要学习一些与ARM相关的知识,下面主要介绍我们必须要了解的知识,具体指令等更加详细的知识参考相关文档,总结目录如下:
  1. 寄存器比较之ARM VS 80X86
  2. 函数调用比较之ARM VS 80X86
  3. Linux系统调用比较之ARM VS 80X86

寄存器比较之ARM VS 80X86
1.80X86

寄存器有以下规则:
(1)数据寄存器(EAX、EBX、ECX、EDX):数据寄存器主要用来保存操作数和运算结果等信息。但其也有一些约定俗成的特别作用。EAX用于存放函数返回值;EBX常用作计算存储器地址时的基址寄存器;ECX用作计数器使用,EDX一些计算中的默认参与运算寄存器。
(2)变址寄存器(ESI、EDI):它们主要用于存放存储单元在段内的偏移量。
(3)指针寄存器(EBP、ESP):ESP常用作堆栈指针,指向栈顶维护堆栈变化;EBP用作栈基地址,主要用于维护该函数栈帧,可直接存取堆栈中的数据。
(4)指令指针(EIP):存放下次将要执行的指令在代码段的偏移量。

2.ARM(用户模式)
寄存器的使用必须满足下面的规则:
(1)函数间通过寄存器R0~R3来传递参数,低于32位的函数返回值存于R0。 
(2)在函数中,使用寄存器R4~R11来保存局部变量。函数进入时必须保存所用到的局部变量寄存器的值,在返回前必须恢复这些寄存器的值;对于函数中没有用到的寄存器则不必进行这些操作。在Thumb中,通常只能使用寄存器 R4~R7来保存局部变量。
(3)寄存器R12 用作函数间的scratch 寄存器,记作IP
(4)寄存器R13 用作数据栈指针,记作SP。函数调用前后必须保持堆栈平衡。
(5)寄存器R14 称为连接寄存器,记作LR。它用作保存函数的返回地址。如果在函数中保存了返回地址,寄存器R14 则可以用作其他用途。 
(6)寄存器R15是程序计数器,记作PC。

   名称:  ARM jicunqi.jpg查看次数: 2文件大小:  88.5 KB

函数调用比较之ARM VS 80X86
函数调用样例:
代码:
#include <stdio.h>
int call(int a,int b,int c,int d,int e,int f){
  int sum =0;
  int v1 =1,v2 =2,v3 =3,v4 =4;
  sum = a+b+c+d+e+f+v1+v2+v3+v4;
  return sum;

}
int main(void){
    int sum =0;
    sum =call(1,2,3,4,5,6);
    printf("sum = %d\n",sum);
    return 0;
}
1.80X86
函数调用主要是使用CALL指令触发,函数结束并由RET/RETN返回到调用该函数时的下一条指令。在函数调用执行过程中,大部分通过栈来完成,包括参数的传递、局部变量存储、函数返回地址等。函数调用函数栈变化如下(常见调用方式):

|……    |<-------- [ESP](分配完局部变量后)
|……    |
|局部变量2  |
|局部变量1 |
|上层[EBP]  |<-------- [EBP](栈帧)
|返回地址  |
|参数1  |
|参数2  |
|参数3  |
+|栈底方向,高地址|+<-------- [ESP](调用前)

整个函数执行过程中,有[EBP]来操作栈中的数据,函数调用结束后,根据栈中的返回地址返回上级函数。

IDA载入,call函数即将被调用的代码如下:
代码:
.text:004010BF                 push    6
.text:004010C1                 push    5
.text:004010C3                 push    4
.text:004010C5                 push    3
.text:004010C7                 push    2
.text:004010C9                 push    1
.text:004010CB                 call    j_?call@@YAHHHHHHH@Z ; 
函数参数压栈,并调用call函数

call函数里边的代码:
代码:
var_14  = dword ptr -14h
var_10  = dword ptr -10h
var_C   = dword ptr -0Ch
var_8   = dword ptr -8
var_4   = dword ptr -4

.text:00401020                 push    ebp
.text:00401021                 mov     ebp, esp
.text:00401023                 sub     esp, 54h
.text:00401026                 push    ebx
.text:00401027                 push    esi
.text:00401028                 push    edi
.text:00401029                 lea     edi, [ebp+var_54]
.text:0040102C                 mov     ecx, 15h
.text:00401031                 mov     eax, 0CCCCCCCCh
.text:00401036                 rep      stosd
.text:00401038                 mov     [ebp+var_4], 0
.text:0040103F                 mov     [ebp+var_8], 1
.text:00401046                 mov     [ebp+var_C], 2
.text:0040104D                 mov     [ebp+var_10], 3
.text:00401054                 mov     [ebp+var_14], 4
.text:0040105B                 mov     eax, [ebp+arg_0]
.text:0040105E                 add     eax, [ebp+arg_4]
.text:00401061                 add     eax, [ebp+arg_8]
.text:00401064                 add     eax, [ebp+arg_C]
.text:00401067                 add     eax, [ebp+arg_10]
.text:0040106A                 add     eax, [ebp+arg_14]
.text:0040106D                 add     eax, [ebp+var_8]
.text:00401070                 add     eax, [ebp+var_C]
.text:00401073                 add     eax, [ebp+var_10]
.text:00401076                 add     eax, [ebp+var_14]
.text:00401079                 mov     [ebp+var_4], eax
.text:0040107C                 mov     eax, [ebp+var_4]
.text:0040107F                 pop     edi
.text:00401080                 pop     esi
.text:00401081                 pop     ebx
.text:00401082                 mov     esp, ebp
.text:00401084                 pop     ebp
.text:00401085                 retn
进入函数后,
push ebp       ;先保存上层函数栈帧
sub esp,54h      ;栈中开辟空间用于函数过程使用(局部变量等),
mov eax, 0CCCCCCCCh 
rep stosd       ;将预留空间填充为CC,以便越界中断
之后的mov操作即可看出是在给局部变量赋值。
最后运算结果存储于eax 。
完成函数功能后,函数恢复之前寄存器值,retn返回。

2.ARM
一般采用B系列指令执行函数的跳转执行,其中BL指令将返回地址存储于LR寄存器,BLX指令除了地址存储于LR寄存器,还对要执行的指令进行判断(ARM or Thumb)。
在参数传递时,将所有参数看作是存放在连续的内存单元中的字数据。然后,依次将各字数据传送到寄存器R0~R3中,如果参数多于4个,则通过栈进行存储,入栈的顺序与参数顺序相反,即最后一个字数据先入栈。
局部变量可以存储在寄存器中,也可以有栈来分配。
函数执行完后,若结果32位的整数时,可以通过寄存器返回(一般使用R0)。 64位整数时,可以通过寄存器R0和R1返回,依次类推(不含浮点运算结果)。最后将之前保存的函数地址赋予到PC(可以是LR,也可以是其他寄存器)。函数调用结构图可如下(采用BL跳转):

寄存器:
R0 = 参数1                        
R1 = 参数2              
R2 = 参数3              
R3 = 参数4             
LR = 返回地址  
栈:
|参数5      |
|参数6      |
|参数7      |
+|栈底方向,高地址|+
IDA载入,call函数即将被调用的代码如下:
代码:
var_14          = -0x14
var_10          = -0x10
var_8           = -8

.text:0000851C                 STMFD   SP!, {R11,LR}
.text:00008520                 ADD     R11, SP, #4
.text:00008524                 SUB     SP, SP, #0x10
.text:00008528                 MOV     R3, #0
.text:0000852C                 STR     R3, [R11,#var_8]
.text:00008530                 MOV     R3, #5
.text:00008534                 STR     R3, [SP,#0x14+var_14]
.text:00008538                 MOV     R3, #6
.text:0000853C                 STR     R3, [SP,#0x14+var_10]
.text:00008540                 MOV     R0, #1
.text:00008544                 MOV     R1, #2
.text:00008548                 MOV     R2, #3
.text:0000854C                 MOV     R3, #4
.text:00008550                 BL       call
通过BL进行函数调用,参数分别存放在R0~R3寄存器中。

call函数里边的代码:
代码:
……
.text:0000850C                 MOV     R0, R3
.text:00008510                 MOV     SP, R11
.text:00008514                 LDMFD   SP!, {R11}
.text:00008518                 BX      LR
结果存在R0中,并通过BX LR跳转会函数被调用的下一条指令,并判断指令类型。  

Linux系统调用比较之ARM VS 80X86
1.80X86
在该系列体系中,系统调用号将放入%eax,它的参数则依次放入%ebx,%ecx,%edx,%esi和%edi。然后调用软中断0x80,程序将参数和系统调用号交给内核,内核来完成系统调用的执行比如,在以下的调用:
Write(2,“Hello”,5)的汇编形式大概是这样的
movl $4, %eax
movl $2, %ebx
movl $hello, %ecx
movl $5, %edx
int  $0×80
在进行linux进行注入过程中,常采用ptrace函数,对于系统调用的查看,只需要根据函数调用时刻寄存器的值,即可判断当前程序执行的函数功能以及所附带的参数。

2.ARM
在ARM架构上,所有的系统调用都是通过SWI软中断来实现的,指令如下:
SWI{cond} immed_24,具体的调用号存放在寄存器R7中,调用函数的参数和之前提到的传参方式相同。例如:exit(0)
MOV R0,#0
MOV R7,#7
SWI #0
<think>嗯,用户问的是群晖的镜像文件能不能直接移植到ARM架构中。首先,我需要理解群晖系统的基本情况。群晖的DSM系统通常是针对特定硬件设计的,特别是他们自家的设备,这些设备可能使用不同的处理器架构,比如x86或者ARM。 然后,用户提到的是“镜像文件”移植到ARM架构。这里需要区分不同的镜像类型,比如系统镜像还是软件镜像。不过根据群晖的情况,可能指的是系统镜像。DSM系统镜像通常是和硬件绑定的,尤其是引导程序和驱动程序部分。不同的架构需要不同的内核和驱动支持,所以直接移植可能不行。 接下来,要考虑群晖官方的支持情况。群晖官方一般不会提供跨架构的镜像,他们的设备有明确的型号和对应的架构。如果用户尝试将x86的镜像安装到ARM设备上,可能会遇到兼容性问题,比如驱动不匹配、引导失败等。 不过,有没有可能通过第三方工具或者修改镜像来实现呢?比如有人尝试在非群晖硬件上安装DSM,但通常需要特定的引导程序和硬件兼容性。ARM架构的移植可能需要重新编译内核,适配驱动,这对于普通用户来说难度很大,而且可能涉及法律问题,因为群晖的系统是闭源的。 另外,用户可能有特定的需求,比如想在ARM设备上运行群晖系统,可能需要考虑官方是否有对应的ARM版本设备。比如群晖的某些型号如DS218j就是ARM架构的,对应的镜像自然是ARM的。如果用户是想在不同型号之间迁移,可能需要使用群晖的备份和迁移工具,而不是直接移植镜像。 最后,用户可能需要替代方案,比如使用Docker容器或其他轻量级系统来在ARM设备上实现类似的功能,这可能更可行。总结来说,直接移植镜像不可行,但存在其他方法或需要注意官方支持的型号。</think>关于群晖(Synology)镜像文件能否直接移植到ARM架构的问题,需要从以下几个层面逐步分析: 1. **群晖系统架构特性** - DSM系统高度依赖硬件兼容性,官方镜像包含: $$内核模块(.ko文件)$$ $$硬件驱动(如SATA控制器、网卡芯片驱动)$$ $$引导加载程序(Synoboot)$$ - 不同架构x86/ARM)的二进制文件完全不兼容,ARM架构需要专门的指令集编译 2. **移植可行性分析** | 移植方式 | 可行性 | 技术难点 | |----------------|--------|------------------------------| | 直接移植原始镜像 | ❌ | 引导程序/内核架构不匹配 | | 修改引导程序 | △ | 需逆向工程Synoboot源码 | | 重新编译内核 | △ | 需获得DSM内核配置和补丁 | 3. **实际操作限制** - 硬件签名验证:群晖启动时校验硬件PID/VID - 驱动适配问题:ARM开发板的GPIO/I2C等外设驱动缺失 - 系统更新障碍:官方更新包会覆盖非官方修改 4. **替代解决方案建议** - 使用官方ARM机型(如DS218play/DS420j系列) - 通过Docker容器化部署(需ARM兼容镜像): ```bash docker run --platform linux/arm64 -d synology-package ``` - 考虑开源方案:OpenMediaVault(原生支持ARM架构) 建议优先选择群晖官方ARM机型,或通过容器化方案实现功能迁移。直接移植系统镜像在技术层面存在根本性架构障碍,需要深度硬件逆向工程支持,普通用户难以实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值