Programs that parse evolving file formats must be able to tell what
version of the format it must parse. This is, often, stored right near
the magic number.
在现代 x86 架构下,内存和二进制文件通常使用 Little-Endian
(小端序) 。简单来说,就是低位字节存放在低地址(前面) 。
十进制的 1337,转换成十六进制是
0x0539。
如果是 2-byte (Short, <H)
存储,它不是 \x05\x39,而是反过来的
\x39\x05 。
如果是 4-byte (Integer, <I)
存储,高位要补零,所以变成了
\x39\x05\x00\x00 。
Python 的 struct
模块就是用来做这种转换的完美工具。struct.pack("<I", 1337)
里的 < 代表 Little-Endian,I 代表 4
字节的无符号整数。
前缀
含义 (Byte Order)
适用场景
<
Little-endian (小端序)
最常用的格式。现代 x86/x64 架构的标准。低位字节在低地址。
>
Big-endian (大端序)
高位字节在低地址。常见于一些非主流或老旧的 RISC 架构。
!
Network byte order (网络字节序)
实际上就是大端序。 所有网络包头部都用这个。
@
Native (本机原生)
默认值,使用本机的字节序和 C 编译器的内存对齐方式(会产生 padding
填充)。
Format (字符)
C 语言对应类型
Python 对应类型
标准大小 (Bytes)
x
pad byte
(无)
1
用于手动填充空字节以实现内存对齐。
b
signed char
integer
1
8-bit 有符号整数 (-128 到 127)。
B
unsigned char
integer
1
8-bit 无符号整数 (0 到 255)。处理单字节 flags 时最常用。
h
short
integer
2
16-bit 有符号整数。
H
unsigned short
integer
2
16-bit 无符号整数。比如网络端口号就用这个 (!H)。
i
int
integer
4
32-bit 有符号整数。
I
unsigned int
integer
4
32-bit 无符号整数。CTF
中最常用的内存地址偏移量大小(32位系统)。
q
long long
integer
8
64-bit 有符号整数。
Q
unsigned long long
integer
8
64-bit 无符号整数。现代 64 位系统。
f
float
float
4
IEEE 754 单精度浮点数。
d
double
float
8
IEEE 754 双精度浮点数。
s
char[]
bytes
变长
字符串/字节数组。需要在前面加数字,比如 4s 代表 4
个字节的 bytes。
假设你需要给 /challenge/cimg
一个文件头,格式要求如下:
Magic Number: "CIMG" (4 个字节)
Version: 1 (16-bit 无符号整数,小端序)
Payload Size: 1024 (64-bit 无符号整数,小端序)
Flag: 0xFF (单字节无符号整数)
1 2 3 4 5 6 7 8 9 10 11 import structfile_header = struct.pack("<4sHQB" , b"CIMG" , 1 , 1024 , 0xFF ) with open ("arch_crafted_payload.bin" , "wb" ) as f: f.write(file_header)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 Pixel = namedtuple("Pixel" , ["ascii" ]) def main (): if len (sys.argv) >= 2 : path = sys.argv[1 ] assert path.endswith(".cimg" ), "ERROR: file has incorrect extension" file = open (path, "rb" ) else : file = sys.stdin.buffer header = file.read1(6 ) assert len (header) == 6 , "ERROR: Failed to read header!" assert header[:4 ] == b"c:MG" , "ERROR: Invalid magic number!" assert int .from_bytes(header[4 :6 ], "little" ) == 51 , "ERROR: Invalid version!" with open ("/flag" , "r" ) as f: flag = f.read() print (flag) if __name__ == "__main__" : try : main() except AssertionError as e: print (e, file=sys.stderr) sys.exit(-1 )
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *from pwn import processfile_header = struct.pack("<4sH" , b"c:MG" , 51 ) file = open ("payload.cimg" , "wb" ) file.write(file_header) file.close() p = process(["/challenge/cimg" , "payload.cimg" ], stdin=process.PTY, stdout=process.PTY) print (p.recvall())
b’pwn.college{**********************************************}’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int main (int argc, char **argv, char **envp) { struct cimg cimg = { 0 }; int won = 1 ; if (argc > 1 ) { if (strcmp (argv[1 ]+strlen (argv[1 ])-5 , ".cimg" )) { printf ("ERROR: Invalid file extension!" ); exit (-1 ); } dup2(open(argv[1 ], O_RDONLY), 0 ); } read_exact(0 , &cimg.header, sizeof (cimg.header), "ERROR: Failed to read header!" , -1 ); if (cimg.header.magic_number[0 ] != '{' || cimg.header.magic_number[1 ] != 'M' || cimg.header.magic_number[2 ] != 'A' || cimg.header.magic_number[3 ] != 'G' ) { puts ("ERROR: Invalid magic number!" ); exit (-1 ); } if (cimg.header.version != 168 ) { puts ("ERROR: Unsupported version!" ); exit (-1 ); } if (won) win(); }
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *from pwn import processfile_header = struct.pack("<4sQ" , b"{MAG" , 168 ) file = open ("payload.cimg" , "wb" ) file.write(file_header) file.close() p = process(["/challenge/cimg" , "payload.cimg" ], stdin=process.PTY, stdout=process.PTY) print (p.recvall())
b’pwn.college{**********************************************}’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ;-- main: # ... │ 0x004012d0 488d0d210e.. lea rcx, str.ERROR:_Failed_to_read_header_ ; 0x4020f8 ; "ERROR: Failed to read header!" │ 0x004012d7 e83f020000 call sym.read_exact ;[5] │ 0x004012dc 807c240343 cmp byte [rsp + 3], 0x43 ; 'C' │ ┌─< 0x004012e1 7515 jne 0x4012f8 │ │ 0x004012e3 807c24044d cmp byte [rsp + 4], 0x4d ; 'M' │┌──< 0x004012e8 750e jne 0x4012f8 │││ 0x004012ea 807c240561 cmp byte [rsp + 5], 0x61 ; 'a' ┌────< 0x004012ef 7507 jne 0x4012f8 ││││ 0x004012f1 807c240667 cmp byte [rsp + 6], 0x67 ; 'g' ┌─────< 0x004012f6 7414 je 0x40130c │└─└└─> 0x004012f8 488d3d170e.. lea rdi, str.ERROR:_Invalid_magic_number_ ; 0x402116 ; "ERROR: Invalid magic number!" │ │ ┌─> 0x004012ff e82cfeffff call sym.imp.puts ;[6] │ └───> 0x00401304 83cfff or edi, 0xffffffff ; -1 │ ╎ 0x00401307 e8d4feffff call sym.imp.exit ;[7] └─────> 0x0040130c 807c240764 cmp byte [rsp + 7], 0x64 ; 'd' ╎ 0x00401311 488d3d1b0e.. lea rdi, str.ERROR:_Unsupported_version_ ; 0x402133 ; "ERROR: Unsupported version!" #...
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *from pwn import processfile_header = struct.pack("<4sB" , b"CMag" , 0x64 ) file = open ("payload.cimg" , "wb" ) file.write(file_header) file.close() p = process(["/challenge/cimg" , "payload.cimg" ], stdin=process.PTY, stdout=process.PTY) print (p.recvall())
b’pwn.college{**********************************************}’