radare2 中 ARM / AArch64 字节序(Endianness)处理原理与实践指南
radare2 中 ARM / AArch64 字节序Endianness处理原理与实践指南【免费下载链接】radare2UNIX-like reverse engineering framework and command-line toolset项目地址: https://gitcode.com/gh_mirrors/ra/radare2导读本文围绕 radare2 对 ARM / AArch64 架构的字节序处理展开核心结论是AArch64A64指令在内存中永远是 little-endian无论目标机的数据字节序如何这一规则由 ARMv8-A 架构固定。文章将以 doc/arm-endian.md 为主线结合 libr/arch/p/arm 下各个反汇编后端的真实源码说明cfg.bigendian配置项到底影响什么、不影响什么并对比 AArch64 与 AArch32BE32 / BE8的差异。读完你不仅能准确理解 r2 在 ARM 上指令小端、数据可大端的模型还能在实际逆向中正确解读pxw、pd等命令的输出。AArch64 指令永远是小端架构事实ARMv8-A 架构规范DDI 0487§E1.3明确规定A64 指令的内存表示始终为 little-endian。也就是说不存在 AArch64 的大端指令模式不存在SETEND之类的运行时切换指令一个 big-endian 的 AArch64 ELF 二进制EI_DATA ELFDATA2MSB实际采用的是BE8 模型数据是大端BE但每个 4 字节指令字内部仍是小端LE。因此当 radare2 加载这样的文件时cfg.bigendian由 ELF 头在加载时自动设置只影响数据的读取与显示如pxw、pxq、结构体字段解析而不影响 AArch64 的指令解码——解码始终按 LE 进行。cfg.bigendian 的自动设置与影响范围在 r2 中cfg.bigendian的默认值是false见 libr/core/cconfig.c 的注册代码SETCB (cfg.bigendian, false, cb_bigendian, use little (false) or big (true) endianness)。当打开一个二进制文件时r2 会根据 ELF 头的端序信息自动改写该配置// libr/core/cbin.c if (IS_MODE_SET (mode)) { r_config_set (core-config, file.type, info-rclass); r_config_set (core-config, cfg.bigendian, info-big_endian? true: false); ... }见 libr/core/cbin.c。这意味着你通常不需要手动设置但也可以随时用e cfg.bigendiantrue|false覆盖。其直接影响面包括受影响不受影响数据读取与显示pxw、pxq等十六进制转储AArch64 指令解码固定 LE结构体字段解析struct fields函数反汇编输出pdf、pd的指令语义数据引用与搜索ELF 头自身的解析r2 按文件自身端序读取AArch64 后端一览asm.archarmbits64doc/arm-endian.md将 r2 中支持 AArch64 解码的后端整理为下表结合 libr/arch/p/arm 目录结构可逐一对证插件文件BE8 处理方式armlibr/arch/p/arm/cs/arm64.c默认后端。Capstone 后端bits64时强制CS_MODE_LITTLE_ENDIANarm.gnulibr/arch/p/arm/plugin_gnu.cGNU binutils 后端。aarch64-dis.c硬编码endian_code BFD_ENDIAN_LITTLE指令流无论cfg.bigendian如何始终为 LEarm.v35libr/arch/p/arm/plugin_v35.cVector35 后端可选不在默认构建中。声明仅支持 LE无法干净接受 BE 配置arm.nzlibr/arch/p/arm/plugin.c自定义汇编器——仅有.encode没有.decode与反汇编无关其中arm.nz的插件元数据libr/arch/p/arm/plugin.c确实只声明了RArchPlugin的.encode回调而没有.decode说明它是纯汇编插件。Capstone 后端bits64 时强制 LElibr/arch/p/arm/cs/arm64.c 中的cs_mode_for_session()是理解整个机制的关键static inline int cs_mode_for_session(RArchSession *as) { int mode CS_MODE_ARM; if (as-config-bits 64) { // AArch64 instruction words are little-endian on disk even on BE8 // targets; cfg.bigendian only describes the data byte order. mode CS_MODE_LITTLE_ENDIAN; } else { mode | R_ARCH_CONFIG_IS_BIG_ENDIAN (as-config)? CS_MODE_BIG_ENDIAN: CS_MODE_LITTLE_ENDIAN; } ... return mode; }可以看到当bits 64时直接覆盖为CS_MODE_LITTLE_ENDIANcfg.bigendian完全不参与指令模式的判定只有 AArch32 分支else才会依据R_ARCH_CONFIG_IS_BIG_ENDIAN选择CS_MODE_BIG_ENDIAN。作为对照32 位 ARM 的 Capstone 后端 libr/arch/p/arm/cs/arm.c 没有这层保护——它始终按cfg.bigendian决定大端/小端模式mode | R_ARCH_CONFIG_IS_BIG_ENDIAN (as-config)? CS_MODE_BIG_ENDIAN: CS_MODE_LITTLE_ENDIAN这正是 AArch32 与 AArch64 行为差异的根源。GNU binutils 后端硬编码指令小端arm.gnu后端在反汇编时构造disassemble_info其中端序相关设置如下libr/arch/p/arm/plugin_gnu.cobj.endian !R_ARCH_CONFIG_IS_BIG_ENDIAN (as-config); ... if (bits 64) { obj.disassembler_options NULL; memcpy (bytes, buf, 4); op-size print_insn_aarch64 ((bfd_vma) addr, obj); } else { const char *options (bits 16)? force-thumb: no-force-thumb; obj.disassembler_options (char *)options; op-size (obj.endian BFD_ENDIAN_LITTLE) ? print_insn_little_arm ((bfd_vma) addr, obj) : print_insn_big_arm ((bfd_vma) addr, obj); }注意 64 位路径直接走print_insn_aarch64而 binutils 源码中libr/arch/p/arm/aarch64/aarch64-dis.c明确写有/* Aarch64 instructions are always little-endian */ info-endian_code BFD_ENDIAN_LITTLE;并且在取指打印时使用info-display_endian info-endian_code同文件约 L3358 处从而保证即使obj.endian反映的是 BE 数据端序指令解码仍按 LE 进行。32 位分支则区分print_insn_little_arm与print_insn_big_arm指令端序跟随cfg.bigendian。AArch32BE32 与 BE8 的关键差异与 AArch64 的铁板一块不同AArch32 情况更复杂BE32ARMv5 及更早legacy 模式指令字本身真的是大端由cfg.bigendian驱动BE8ARMv6行为类似 AArch64——指令 LE、数据 BE通过 ELFe_flags中的EF_ARM_BE8标志来标识。EF_ARM_BE8的取值定义可在 libr/arch/p/arm/gnu/elfarm.h 看到#define EF_ARM_BE8 0x00800000 #define EF_ARM_LE8 0x00400000r2 自带的 bin 格式头 libr/bin/format/elf/glibc_elf.h 中也有同样定义。当前现状AArch32 各后端目前把cfg.bigendian当作指令流端序来用而根据 ELF flags 区分 BE8 与 BE32 是另一项尚未完成的工作。其起点是 libr/arch/p/arm/gnu/arm-dis.c 中被#if 0禁用的桩代码int print_insn_big_arm (bfd_vma pc, struct disassemble_info *info) { /* Detect BE8-ness and record it in the disassembler info. */ #if 0 if (info-flavour bfd_target_elf_flavour info-section (elf_elfheader (info-section-owner)-e_flags EF_ARM_BE8)) info-endian_code BFD_ENDIAN_LITTLE; #endif return print_insn (pc, info, FALSE); }这段被禁用的代码展示了 binutils 上游本打算做的事当发现EF_ARM_BE8标志时将指令端序改为 LE即 BE8 语义但该逻辑在 r2 中尚未启用。实操验证如何亲手确认行为在 r2 中打开一个 AArch64 二进制例如r2 -a arm -b 64 你的be8_aarch64_binary可以按以下步骤验证指令 LE、数据 BE的行为查看文件头端序r2 -I 文件或加载后iI观察 ELF 的EI_DATA若为ELFDATA2MSB则文件声明为大端查看当前配置e cfg.bigendian加载大端 ELF 时应显示true由 libr/core/cbin.c 自动设置对比数据与指令在数据段执行pxq按 8 字节数据读取受cfg.bigendian影响在代码段执行pd 1指令解码AArch64 恒为 LE两者表现出的字节序差异正是 BE8 模型切换端序实验e cfg.bigendianfalse后再看pxq的输出会翻转而 AArch64 的pd反汇编结果保持稳定。另外注意arm.v35与arm.nz的边界前者声明 LE-only且不在默认构建中源码位于 libr/arch/p/arm/plugin_v35.c受WANT_V35宏控制后者仅用于汇编.encode不能用于反汇编验证。小结radare2 的 ARM 端序处理可以用一句话概括AArch64 指令恒定小端cfg.bigendian只管数据AArch32 则区分 BE32 与 BE8且 BE8 识别尚待完成。理解这一模型可以避免在逆向大端 AArch64 固件时误读pxw与pd的差异也能在遇到 AArch32 BE8 文件时正确预期当前 r2 的局限其区分 BE8/BE32 的补丁起点在 libr/arch/p/arm/gnu/arm-dis.c 的禁用桩处。相关实现可继续深入 libr/arch/p/arm 目录阅读各后端源码。【免费下载链接】radare2UNIX-like reverse engineering framework and command-line toolset项目地址: https://gitcode.com/gh_mirrors/ra/radare2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考