字节存储顺序

字节序主要分为大端序(Big-endian)和小端序(Little-endian),区别如下:

  • 大端序高位字节 -> 低地址,低位字节 -> 高地址
  • 小端序低位字节 -> 低地址,高位字节 -> 高地址

图解

CTF - Reverse_大端序和小端序1.png

不管是大端法还是小端法存储,计算机在内存中存放数据的顺序都是从低地址到高地址

不同的是,取低字节的数据存放在低地址,还是取高字节数据存放在低地址

  • 若为常见的大小类型(如 int), 则是顺序(高位在左)
  • 若为其他的大小类型(如 int64),则是逆序(高位在右)

示例

例如,将 12345678h 写入 1000h 开始的内存中,以大端序和小端序模式存放结果如下:

CTF - Reverse_大端序和小端序2.png

  1. 在小端顺序里, v1 = 0x12345678 在内存的字节码中看起来是这样 78h 56h 34h 12h,这没有问题

如果这时有 v2 = 0x1234,在内存的字节码中看起来是 34h 12h

  1. 假如 v2 后面紧接着 v1 的话,字节码应该就是 34h 12h 78h 56h 34h 12h

再放一个 v3 = 0x12 的话,总的排序就是 12h 34h 12h 78h 56h 34h 12h

一般来说,x86 系列 CPU 都是 Little-endian 字节序,PowerPC 通常是 Big-endian 字节序

但是对于 [1, 2, 3, 4] 这么一个数组,并不会受到字节序的影响,它的排序无论大端序还是小端序都是一样的


二进制程序中的小端序

PE 程序

使用 DIE(Detect it easy)查看可执行程序:

CTF - Reverse_大端序和小端序3.png

字节序显示 LE 则是小端序,显示 BE 则是大端序


ELF 程序

使用 file 命令查看可执行程序:

file 可执行程序

CTF - Reverse_大端序和小端序4.png

显示 LSB 则是小端序,显示 MSB 则是大端序


IDA 中的小端序

_DWORD

在 IDA 中,F5 查看伪代码后,如果看到:

_DWORD v4[7];

v4[0] = 0xD6C0B67;
v4[1] = 0x175F4078;
v4[2] = 0x3302058;
v4[3] = 0x725D1244;
v4[4] = 0x2E1F3441;
v4[5] = 0x6847404D;
v4[6] = 0x1B;

注意最开始的数据类型,这个数组不仅要按照 4 字节左侧补零对齐,还要将每一组数据翻转拼接才能得到正确的字符串,IDA 并没有非常智能地捋顺字符串,所以初学的话非常有迷惑性

例如:

v4[2] = 0x3302058;

应为:0x03302058 --> 0x58,0x20,0x30,0x03


db、dw、dd、dq

伪指令 db、dw、dd、dq 都可以定义字符串,但最多的是用 db 来定义字符串

  1. 第一个原因是:dw、dd 定义的字符串到了内存中排序是相反的
    • 在字符串 "abcd" 中,元素按从高位向低位线性排序
    • 在内存中,数据由低位向高位线性排序
    • 因此,字符串 "abcd" 在内存中的顺序是从低位向高位排序的,所以相反,即:小端序
  2. 第二个原因是:不同版本编译器对 dw 与 dd 定义字符串的指令格式支持不一样
    • db 定义字节类型变量,一个字节数据占 1 个字节单元,读完一个,偏移量加 1 ( 1 个十六进制数)
    • dw 定义字类型变量,一个字数据占 2 个字节单元,读完一个,偏移量加 2 ( 2 个十六进制数)
    • dd 定义双字类型变量,一个双字数据占 4 个字节单元,读完一个,偏移量加 4 ( 4 个十六进制数)
    • dq 定义四字类型变量,一个四字数据占 8 个字节单元,读完一个,偏移量加 8 ( 8 个十六进制数)
  3. db 是一字节,按顺序一个一个存储
    • 用 db 定义字符串时,存储方式是顺着的,即:大端序