2025-09-25
逆向工程
00

推荐学习资源

书籍

  • 《逆向工程权威指南》
  • 《C++反汇编与逆向分析》
  • 《恶意代码分析实战》

在线资源

  • Crackmes.one:逆向工程练习题库
  • OverTheWire:在线安全挑战
  • PicoCTF:适合初学者的CTF平台

工具学习

  • IDA Pro:业界标准反汇编器
  • Ghidra:NSA开源逆向工程工具
  • x64dbg:Windows平台调试器
2025-09-25
逆向工程
00

第一步:获取目标程序

我们拿到了一个反编译后的C代码文件(challenge.c),这是逆向工程中最常见的起点:

c
int __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:调用约定,表示函数参数如何传递

第二步:理解程序结构

2.1 找出程序的主要功能

通过阅读代码,我们发现:

c
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "========== Reverse Practice Challenge ==========\n"); std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Enter passphrase: ");

翻译成人话:程序在输出欢迎信息,要求用户输入密码。

2.2 识别关键逻辑

c
if ((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"); }

程序逻辑

  1. 验证用户输入的密码
  2. 如果密码正确,解密flag并显示
  3. 如果密码错误,显示提示信息

2.3 重要的提示信息

"Hint: check rotated-xor layers and index-dependent offset."

这个提示告诉我们加密算法可能包含:

  • rotated(旋转操作)
  • xor(异或运算)
  • index-dependent offset(索引相关的偏移)

第三步:分析复杂的判断逻辑

代码中有一行特别复杂的判断:

c
if ((*((_BYTE *)v4 + *(_QWORD *)(*v4 - 24LL) + 32) & 5) == 0)

** 这行代码在做什么?**

让我们一步步分解:

  1. v4:指向输入流对象(std::cin
  2. *v4 - 24LL:访问对象内部的虚函数表
  3. + 32:偏移到状态标志位的位置
  4. & 5:检查特定的状态位
  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}'

** 找到了正确的解密参数**:

  • 索引偏移乘数: 7 (即 i * 7)
  • 旋转模数: 5 (即 i % 5)
  • 异或密钥: 0x5A

第八步:获取密码并验证

使用相同的算法解密密码数据:

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}
2025-09-25
逆向工程
00

🛠️ IDA 动态调试口袋速查表

🔹 基本操作

快捷键功能口语化解释
F9Run(运行程序)放马跑,直到断点才停
Ctrl + F2Restart(重启调试)全部重来,调试从头开始
Shift + F9Run to user code跳过系统初始化,直接到主程序

🔹 单步调试

快捷键功能口语化解释
F7Step Into(单步进入)钻进函数内部看细节
F8Step Over(单步跳过)函数整体执行,直接看结果
Shift + F7Step Out(单步跳出)看腻了函数,直接跳回外层
Ctrl + F9Run to Cursor(运行到光标处)快速跳到你光标所在的地方

🔹 断点相关

快捷键功能口语化解释
F2Toggle Breakpoint(设置/取消断点)定闹钟,程序跑到这里停
Ctrl + F2Kill Process(杀掉调试进程)一键干掉当前调试进程

🔹 调试窗口

窗口功能看点
Registers寄存器窗口EAX/EBX/RCX 的实时值
Stack堆栈窗口参数、返回地址、局部变量
Memory dump内存窗口直接看内存字节内容
Disassembly反汇编视图当前执行的指令箭头

🔹 小技巧

  • 循环 → 在循环外面打个断点(F2),用 F9 直接跳过去。
  • API 调用 → 不想看里面,直接 F8 跳过。
  • 加密逻辑 → 在关键指令前下断点,观察寄存器/内存变化。
  • 调试保护 → 如果进程启动时被反调试,可以先 Attach to process,而不是直接 Start。

2025-09-25
杂项
00

图片隐写-实战

我每次做图片隐写都有一个固定的流程:

下载文件->拖进二进制工具->检查文件头和文件尾(如果有需要需要还原文件)->拖入stave查看通道->拖入binwalk检查是否有包含的文件->检查频率......(后面就是工具的使用)

本文末尾我会给出一些好用的图片分析网站,包含一些GIF还原,更强大的二维码识别等

1.0 admin.exe

  • 先下载到这个文件:

image.png

  • 直接拖进winhex(和010一个功能)里面看一下

image.png

  • 这里可以注意到,文件头为:
89 50 4E 47 0D 0A 1A 0A

标准的png格式,那么我们就需要把exe改为png

  • 更改后缀

image.png 这里出现了一个二维码,拿出手机扫一下就能获得flag

1.1 flag.jpg

  • 下载到文件:

image.png

  • 标准流程,先拖进winhex看看

image.png

刚开始没看到文件头,我本来寻思是什么txt之类的文件直接改的,但又想到文件是完好的不太可能,回去翻了一下别人写的wp,这里原来是文件数据倒转

可以看到文件末尾GNP,倒过来不就是PNG嘛,这里要还原的话需要写个小脚本:

python
a = 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图片里
  • 直接就获取到了flag图片

image.png

1.2 no_hex.png

  • 下载文件:

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

  • 拖进winhex继续看

image.png

后面没什么异常,但是文件开头全是0,很明显是被删掉了,根据png的文件头进行还原

89 50 4E 47 0D 0A 1A 0A

image.png 注意:尽量先备份文件再更改!!!

  • 然后图片就正常了:

1758776831845.png

1.3 shack.png

这题涉及图片宽高的改写,需要先知道以下知识:

长度(Length):指定数据块中数据区域的长度,长度不可超过(2^31-1)个字节 数据块类型码(Chunk Type Code):数据块类型码由ASCII字母(A-Z和a-z)组成的"数据块符号" 数据块数据(Chunk Data):存储数据块类型码指定的数据 循环冗余检测(CRC):存储用来检测是否文件传输有误的循环冗余码

PNG图片: image.png

JPG图片:

image.png

BMP图片:

f17dd04c61a99180455ea8626d2d1c4a.png

  • 打开图片:

image.png

  • 感觉很明显缺了一部分,拖进winhex:

image.png

  • 更改这一部分数据

image.png

  • 保存后再看,flag已经出现了

image.png

1.4 dog.jpg

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

image.png

  • 很明显里面有隐藏的文件,下面进行提取;

image.png

注意:有时候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/

2025-09-25
杂项
00

隐藏在二进制数据中的Flag

1. 常见图片文件的二进制头

PNG文件头

文件头以89 50 4E 47 0D 0A 1A 0A为标识符

JPEG文件头

文件头以FF D8 FF为标识符。结束以FF D9为标识符

BMP文件头

文件头以42 4D为标识符

2. 常见的图片隐写方法

2.1 LSB(最低有效位)隐写

在每个像素的二进制表示中,修改最后一个位来隐藏数据。例如,一个像素的颜色为11110000,可以修改最低有效位为11110001,从而隐藏一个比特。

2.2 DCT(离散余弯变换)隐写

DCT系数表示图像中的频率信息,隐写数据时会修改低频部分的系数,通常不影响图像质量。主要用于JPEG

2.3 调色板隐写

通过改变图像的调色板,将某些颜色替换成数据编码。颜色的变化很微小,难以察觉。

2.4 混合隐写

通过在不同的区域或多个方法上进行数据嵌入。

2.5 将文件直接嵌入图片

将目标文件的二进制数据直接附加到图片文件的结尾部分,或者使用特定工具将文件嵌入图片内。

2.6 将数据藏在二进制中的隐写术

通过修改图像文件的二进制数据,直接在文件流的非图像区域嵌入数据。这种方法并不直接依赖图像的视觉特性,而是改变文件的结构。例如,可以在文件的末尾添加自定义数据,或者修改文件头中的某些字段来存储隐藏的数据。

3. 常用的图片隐写分析工具

3.1 Steghide

Steghide是一个流行的隐写工具,支持将文本或文件嵌入图像中。

  • 使用方法:通过命令行操作,将文件或信息嵌入到图像中,或者从图像中提取隐藏数据。
  • 示例
    bash
    steghide embed -cf image.jpg -ef secret.txt steghide extract -sf image.jpg

3.2 OutGuess

OutGuess是一种广泛用于JPEG图片的隐写分析工具,支持提取隐藏的数据。

  • 使用方法:通过分析图片的频率信息,OutGuess可以提取出嵌入的数据。

  • 示例

    bash
    outguess -k password -d extracted_data.jpg secret.jpg

3.3 Zsteg

Zsteg是一个专门用于分析PNG和BMP图片的工具,可以检测并提取隐藏的数据。

  • 使用方法:通过Zsteg命令,分析图片的二进制结构,查找可能的隐写数据。

  • 示例

    bash
    zsteg image.png

3.4 Binwalk

Binwalk是一款常用的工具,用于分析二进制文件,可以识别嵌入在图像中的其他文件或数据。

  • 使用方法:Binwalk能够扫描图片文件,查找嵌入的文件系统或数据。

  • 示例

    bash
    binwalk image.jpg

3.5 StegoVeritas

StegoVeritas是一个专用的隐写检测工具,可以帮助分析和提取嵌入在图像中的数据。

  • 使用方法:这是一个图形化工具,拖进去就行。

3.6 010editer

010editer是一个专用于二进制分析的隐写工具,万金油工具,可以用来查看文件头,或者二进制数据。

  • 使用方法:这是一个图形化工具,拖进去就行。
  • 悄悄提一嘴,没有工具的可以用这个二进制分析的网站:https://hexed.it/
  • 实话说还不错。