1. 题目背景与核心思路解析
这道逆向工程题目来自HNCTF 2022第一周赛题,考察的是小端序(Little Endian)存储原理在实际逆向分析中的应用。题目要求我们通过分析给定的可执行文件,找到正确的输入序列以获取flag。
核心解题逻辑可以概括为:
- 程序会将用户输入的6个32位整数与硬编码的6个32位整数(0x51670536, 0x5E4F102C等)进行异或运算
- 只有每个输入值经过异或0x12345678后与对应位置的值相等时,才会输出"you are right!"
- 这些硬编码值实际上是以小端序存储的ASCII字符序列
2. 逆向分析详细过程
2.1 初始静态分析
使用IDA Pro加载目标文件后,我们首先定位到main函数,F5查看伪代码:
c复制int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+2Ch] [rbp-24h]
int v5[6]; // [rsp+30h] [rbp-20h] BYREF
for ( i = 0; i <= 5; ++i )
{
scanf("%x", &v5[i]);
if ( (v5[i] ^ 0x12345678) != enc[i] )
{
puts("wrong!");
return 0;
}
}
puts("you are right!");
return 0;
}
关键点解析:
- 程序读取6个十六进制格式的输入
- 每个输入会与0x12345678进行异或
- 结果必须与enc数组中的对应元素相等
2.2 加密数组分析
双击查看enc数组,可以看到6个32位整数值:
code复制.rdata:0000000140003000 enc dd 51670536h, 5E4F102Ch, 7E402211h, 7C71094Bh, 7C553F1Ch, 6F5A3816h
2.3 直接异或解法
最直观的解法是直接将这6个值与0x12345678异或:
cpp复制#include <iostream>
using namespace std;
int main() {
int arr[6] = {0x51670536, 0x5E4F102C, 0x7E402211, 0x7C71094B, 0x7C553F1C, 0x6F5A3816};
for(int i=0; i<6; i++) {
cout << hex << (arr[i]^0x12345678) << ' ';
}
return 0;
}
输出结果:
code复制4353534e 4c7b4654 6c747469 6e455f33 6e616964 7d6e6e6e
将这些十六进制值转换为ASCII字符:
code复制CSSN L{FT ltti nE_3 naid}nnn
观察发现每组4字节字符倒序后能组成有意义的字符串:
code复制NSSCTF{Littl3_Endiannnn}
3. 小端序原理深入解析
3.1 字节序基本概念
字节序(Endianness)指多字节数据在内存中的存储顺序,主要分为两种:
- 大端序(Big Endian):最高有效字节存储在最低内存地址
- 小端序(Little Endian):最低有效字节存储在最低内存地址
以32位整数0x12345678为例:
- 大端序存储:12 34 56 78
- 小端序存储:78 56 34 12
3.2 题目中的小端序应用
在本题中,flag字符串"NSSCTF{Littl3_Endiannnn}"被分成6个32位整数存储,每个整数对应4个字符。由于采用小端序存储,实际内存中的字节顺序与人类可读顺序相反。
例如:
- "NSS"的ASCII码为0x4E 0x53 0x53 0x43
- 小端序存储为0x4353534E
- 与0x12345678异或后得到0x51670536
3.3 正确的逆向过程
- 提取enc数组中的6个32位整数值
- 每个值与0x12345678异或得到原始的小端序存储值
- 将每个32位值拆分为4个字节并反转顺序
- 将全部字节转换为ASCII字符组合成flag
完整Python解法:
python复制enc = [0x51670536, 0x5E4F102C, 0x7E402211, 0x7C71094B, 0x7C553F1C, 0x6F5A3816]
xor_key = 0x12345678
flag_bytes = bytearray()
for num in enc:
xored = num ^ xor_key
# 将32位整数拆分为4个字节(小端序)
for i in range(4):
flag_bytes.append((xored >> (8*i)) & 0xFF)
# 由于是小端序存储,直接转换为字符串即可
flag = flag_bytes.decode('latin-1')
print(flag)
4. 常见问题与调试技巧
4.1 调试过程中可能遇到的问题
-
字节顺序混淆:
- 症状:得到的flag部分正确但顺序混乱
- 解决:确认是小端序存储,需要反转每组4字节的顺序
-
数据类型不匹配:
- 症状:异或结果不正确
- 解决:确保使用32位无符号整数进行计算
-
字符编码问题:
- 症状:转换后的字符显示异常
- 解决:使用正确的编码方式(如latin-1)处理字节数据
4.2 逆向分析实用技巧
-
IDA Pro使用技巧:
- 使用快捷键G快速跳转到指定地址
- 按X查看交叉引用,了解数据被使用的位置
- 使用Hex-Rays反编译器时,可以重命名变量提高可读性
-
动态调试技巧:
- 在scanf调用后设置断点,观察输入如何被处理
- 检查内存中enc数组的实际存储形式
- 使用调试器修改寄存器值测试不同输入
-
Python辅助分析:
- 使用struct模块处理字节序转换:
python复制import struct # 将小端序字节转换为整数 num = struct.unpack('<I', bytes.fromhex('36056751'))[0]
- 使用struct模块处理字节序转换:
5. 扩展思考与变种题目
5.1 题目可能的变种形式
-
动态密钥:
- 异或密钥不是固定值,而是根据输入或其他条件计算得出
- 解法:需要分析密钥生成算法
-
多层加密:
- 在异或基础上增加其他加密操作
- 解法:逐步逆向每一层加密
-
自定义字节序:
- 使用非标准字节序(如混合大小端)
- 解法:仔细分析内存存储模式
5.2 实际应用中的小端序
小端序在现代x86/x64架构中广泛使用,理解这一概念对于以下场景尤为重要:
- 网络协议分析(需要注意网络序通常是大端序)
- 二进制文件格式解析
- 跨平台数据传输
- 内存取证分析
6. 完整解题脚本
以下是集成了所有步骤的Python 3解题脚本:
python复制def solve():
# 题目给定的加密数组
enc = [0x51670536, 0x5E4F102C, 0x7E402211, 0x7C71094B, 0x7C553F1C, 0x6F5A3816]
xor_key = 0x12345678
flag = bytearray()
for num in enc:
# 异或得到原始值
original = num ^ xor_key
# 提取4个字节(小端序)
bytes_part = [
(original >> 0) & 0xFF,
(original >> 8) & 0xFF,
(original >> 16) & 0xFF,
(original >> 24) & 0xFF
]
# 添加到flag字节数组
flag.extend(bytes_part)
# 转换为字符串
flag_str = flag.decode('latin-1')
print(f"Flag: {flag_str}")
if __name__ == "__main__":
solve()
运行结果:
code复制Flag: NSSCTF{Littl3_Endiannnn}
7. 逆向工程学习建议
对于想要深入学习逆向工程的初学者,建议从以下方面入手:
-
基础理论:
- 掌握计算机体系结构,特别是内存管理和数据表示
- 理解常见的汇编指令集(x86/x64, ARM等)
- 学习可执行文件格式(PE, ELF等)
-
工具链:
- 静态分析工具:IDA Pro, Ghidra, Binary Ninja
- 动态调试工具:x64dbg, OllyDbg, GDB
- 辅助工具:Process Monitor, PE Explorer
-
实践方法:
- 从简单的CrackMe题目开始
- 参与CTF比赛中的逆向类题目
- 分析真实的恶意软件样本(需在安全环境中)
-
持续学习:
- 关注安全社区的最新研究
- 学习现代软件保护技术(加壳、混淆等)
- 了解反逆向工程技术
提示:在实际逆向分析中,遇到加密或编码数据时,首先要识别算法特征。像本题中的固定异或密钥是相对简单的保护方式,现代软件通常会使用更复杂的加密方案。