我们拿到了一个反编译后的C代码文件(challenge.c),这是逆向工程中最常见的起点:
cint __fastcall main(int argc, const char **argv, const char **envp)
{
bool v3;
_QWORD *v4;
__int64 v6;
__int64 v7;
_QWORD v8[2];
char v9;
_QWORD v10[7];
_main(argc, argv, envp);
std::ios_base::sync_with_stdio(0LL, v3);
// ... 更多复杂的代码
}
** 困惑点**:
v3, v4, v6 这样?_QWORD 是什么意思?__fastcall 是什么?解答:
v1, v2 等占位_QWORD:表示8字节(64位)的数据类型,相当于 unsigned long long__fastcall:调用约定,表示函数参数如何传递通过阅读代码,我们发现:
cstd::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "========== Reverse Practice Challenge ==========\n");
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Enter passphrase: ");
翻译成人话:程序在输出欢迎信息,要求用户输入密码。
cif ((unsigned __int8)verify_passphrase(v8)) {
decrypt_blob[abi:cxx11](v10, &enc_flag);
// 显示解密结果
} else {
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Access denied.\n");
std::operator<<<std::char_traits<char>>(
refptr__ZSt4cout,
"Hint: check rotated-xor layers and index-dependent offset.\n");
}
程序逻辑:
"Hint: check rotated-xor layers and index-dependent offset."
这个提示告诉我们加密算法可能包含:
代码中有一行特别复杂的判断:
cif ((*((_BYTE *)v4 + *(_QWORD *)(*v4 - 24LL) + 32) & 5) == 0)
** 这行代码在做什么?**
让我们一步步分解:
v4:指向输入流对象(std::cin)*v4 - 24LL:访问对象内部的虚函数表+ 32:偏移到状态标志位的位置& 5:检查特定的状态位== 0:判断输入是否成功** 简化理解**:这相当于检查 std::cin.good() 和 !std::cin.fail()
程序中有两个重要函数没有实现:
verify_passphrase() - 验证密码decrypt_blob() - 解密flag由于我们没有源代码,需要通过逆向分析来推断这些函数的实现。
基于提示信息,我们创建一个暴力破解工具来测试不同的解密算法:
cpp#include <iostream>
#include <vector>
#include <string>
#include <cstdint>
using namespace std;
// 8位右旋转函数
inline uint8_t rotateRight8(uint8_t value, unsigned rotation) {
rotation &= 7; // 限制在0-7范围内
if (rotation == 0) return value;
return (uint8_t)((value >> rotation) | (value << (8 - rotation)));
}
// 尝试解密函数
string tryDecryption(const vector<uint8_t>& data, int multiplier, int rotation_mod, uint8_t xor_key) {
string result;
result.reserve(data.size());
for (size_t i = 0; i < data.size(); ++i) {
uint8_t byte = data[i];
// 步骤1:减去索引偏移
byte = (uint8_t)((byte - (uint8_t)((i * multiplier) & 0xFF)) & 0xFF);
// 步骤2:右旋转
if (rotation_mod > 0) {
byte = rotateRight8(byte, (unsigned)(i % rotation_mod));
}
// 步骤3:异或
byte = (uint8_t)(byte ^ xor_key);
result.push_back((char)byte);
}
return result;
}
我们需要测试不同的参数组合:
cpp// 加密数据
vector<uint8_t> sample_data = {
60, 115, 250, 254, 46, 75, 168, 225, 49, 193,
111, 203, 104, 20, 85, 110, 70, 24, 201, 247
};
// 尝试不同的参数组合
vector<tuple<int, int, uint8_t>> patterns = {
{7, 5, 0x5A}, // 乘数7, 旋转模5, 异或0x5A
{3, 4, 0x5A},
{5, 3, 0x5A},
// ... 更多组合
};
for (auto& pattern : patterns) {
int mult = get<0>(pattern);
int rot_mod = get<1>(pattern);
uint8_t xor_key = get<2>(pattern);
string result = tryDecryption(sample_data, mult, rot_mod, xor_key);
printf("乘数:%d, 旋转模:%d, 异或:0x%02x -> '%s'\n",
mult, rot_mod, xor_key, result.c_str());
// 检查是否包含flag模式
if (result.find("flag") != string::npos) {
cout << "*** 可能找到flag! ***\n";
}
}
运行我们的分析工具后,得到了结果:
乘数:7, 旋转模:5, 异或:0x5a -> 'flag{reverse_me_123}'
** 找到了正确的解密参数**:
i * 7)i % 5)使用相同的算法解密密码数据:
cpp// 密码数据
vector<uint8_t> enc_pass_data = {
53, 91, 10, 182, 147, 76, 168, 213, 17, 178, 133
};
string password = decryptData(enc_pass_data);
cout << "正确密码: " << password << endl; // 输出: open-sesame
最终验证:
bash$ echo "open-sesame" | ./challenge_working.exe
========== Reverse Practice Challenge ==========
Enter passphrase: Access granted.
Here is the secret: flag{reverse_me_123}
| 快捷键 | 功能 | 口语化解释 |
|---|---|---|
| F9 | Run(运行程序) | 放马跑,直到断点才停 |
| Ctrl + F2 | Restart(重启调试) | 全部重来,调试从头开始 |
| Shift + F9 | Run to user code | 跳过系统初始化,直接到主程序 |
| 快捷键 | 功能 | 口语化解释 |
|---|---|---|
| F7 | Step Into(单步进入) | 钻进函数内部看细节 |
| F8 | Step Over(单步跳过) | 函数整体执行,直接看结果 |
| Shift + F7 | Step Out(单步跳出) | 看腻了函数,直接跳回外层 |
| Ctrl + F9 | Run to Cursor(运行到光标处) | 快速跳到你光标所在的地方 |
| 快捷键 | 功能 | 口语化解释 |
|---|---|---|
| F2 | Toggle Breakpoint(设置/取消断点) | 定闹钟,程序跑到这里停 |
| Ctrl + F2 | Kill Process(杀掉调试进程) | 一键干掉当前调试进程 |
| 窗口 | 功能 | 看点 |
|---|---|---|
| Registers | 寄存器窗口 | EAX/EBX/RCX 的实时值 |
| Stack | 堆栈窗口 | 参数、返回地址、局部变量 |
| Memory dump | 内存窗口 | 直接看内存字节内容 |
| Disassembly | 反汇编视图 | 当前执行的指令箭头 |
我每次做图片隐写都有一个固定的流程:
下载文件->拖进二进制工具->检查文件头和文件尾(如果有需要需要还原文件)->拖入stave查看通道->拖入binwalk检查是否有包含的文件->检查频率......(后面就是工具的使用)
本文末尾我会给出一些好用的图片分析网站,包含一些GIF还原,更强大的二维码识别等


89 50 4E 47 0D 0A 1A 0A
标准的png格式,那么我们就需要把exe改为png
这里出现了一个二维码,拿出手机扫一下就能获得flag


刚开始没看到文件头,我本来寻思是什么txt之类的文件直接改的,但又想到文件是完好的不太可能,回去翻了一下别人写的wp,这里原来是文件数据倒转
可以看到文件末尾GNP,倒过来不就是PNG嘛,这里要还原的话需要写个小脚本:
pythona = open('flag.jpg','rb') #读取flag.jpg图片的byte数据
b = open('png.png','wb') #新建一个名为png.png的图片,写入byte数据
b = b.write(a.read()[::-1]) #将flag.jpg图片的byte数据,倒着写入png.png图片里

这里发现图片根本打不开,这也就是我上面提到的,其他文件直接改后缀会出现的情况---文件损坏

后面没什么异常,但是文件开头全是0,很明显是被删掉了,根据png的文件头进行还原
89 50 4E 47 0D 0A 1A 0A
注意:尽量先备份文件再更改!!!

这题涉及图片宽高的改写,需要先知道以下知识:
长度(Length):指定数据块中数据区域的长度,长度不可超过(2^31-1)个字节 数据块类型码(Chunk Type Code):数据块类型码由ASCII字母(A-Z和a-z)组成的"数据块符号" 数据块数据(Chunk Data):存储数据块类型码指定的数据 循环冗余检测(CRC):存储用来检测是否文件传输有误的循环冗余码
PNG图片:

JPG图片:

BMP图片:





前面流程就不展示了,直接快进到binwalk扫描:


注意:有时候binwalk提取出来的文件是损坏的,这时候你可以试试foremost工具,这个更准点,命令为:foremost dog.jpg -o dog //-o:指定输出的文件夹
更强大的二维码识别:https://online-barcode-reader.inliteresearch.com/ GIF分解:https://tu.sioe.cn/gj/fenjie/#google_vignette 在线多功能图片工具:https://tu.sioe.cn/gj/
文件头以89 50 4E 47 0D 0A 1A 0A为标识符
文件头以FF D8 FF为标识符。结束以FF D9为标识符
文件头以42 4D为标识符
在每个像素的二进制表示中,修改最后一个位来隐藏数据。例如,一个像素的颜色为11110000,可以修改最低有效位为11110001,从而隐藏一个比特。
DCT系数表示图像中的频率信息,隐写数据时会修改低频部分的系数,通常不影响图像质量。主要用于JPEG
通过改变图像的调色板,将某些颜色替换成数据编码。颜色的变化很微小,难以察觉。
通过在不同的区域或多个方法上进行数据嵌入。
将目标文件的二进制数据直接附加到图片文件的结尾部分,或者使用特定工具将文件嵌入图片内。
通过修改图像文件的二进制数据,直接在文件流的非图像区域嵌入数据。这种方法并不直接依赖图像的视觉特性,而是改变文件的结构。例如,可以在文件的末尾添加自定义数据,或者修改文件头中的某些字段来存储隐藏的数据。
Steghide是一个流行的隐写工具,支持将文本或文件嵌入图像中。
bashsteghide embed -cf image.jpg -ef secret.txt steghide extract -sf image.jpg
OutGuess是一种广泛用于JPEG图片的隐写分析工具,支持提取隐藏的数据。
使用方法:通过分析图片的频率信息,OutGuess可以提取出嵌入的数据。
示例:
bashoutguess -k password -d extracted_data.jpg secret.jpg
Zsteg是一个专门用于分析PNG和BMP图片的工具,可以检测并提取隐藏的数据。
使用方法:通过Zsteg命令,分析图片的二进制结构,查找可能的隐写数据。
示例:
bashzsteg image.png
Binwalk是一款常用的工具,用于分析二进制文件,可以识别嵌入在图像中的其他文件或数据。
使用方法:Binwalk能够扫描图片文件,查找嵌入的文件系统或数据。
示例:
bashbinwalk image.jpg
StegoVeritas是一个专用的隐写检测工具,可以帮助分析和提取嵌入在图像中的数据。
010editer是一个专用于二进制分析的隐写工具,万金油工具,可以用来查看文件头,或者二进制数据。