BUUCTF-reverse1
记录一次来自BUUCTF平台reverse系列进入平台点击该题目,首先下载题目中给的zip压缩包并打开,然后解压之后拖到IDA中寻找main_0函数,发现有类似的flag值但是这次可不是明面上获得的答案,不信你可以提交以下试试?这个时候需要按f5来查看程序内容我们来做个分析。
·
记录一次来自BUUCTF平台reverse系列

进入平台点击该题目,首先下载题目中给的zip压缩包并打开,然后解压

之后拖到IDA中寻找main_0函数,发现有类似的flag值

但是这次可不是明面上获得的答案,不信你可以提交以下试试?
这个时候需要按f5来查看程序内容

我们来做个分析
函数基础信息:
int __fastcall main_0(int argc, const char **argv, const char **envp) /**函数签名**/
__fastcall:调用约定,前两个参数(argc、argv)会通过寄存器(RCX、RDX)传递,第三个参数(envp)通过 R8 传递(x64 架构);这是程序的主逻辑函数,负责 flag 验证
分析1.局部变量定义
char *v3; // rdi /***字符指针,关联rdi寄存器(x64函数调用的第一个参数寄存器)
__int64 i; // rcx /***64位整数,关联rcx寄存器(循环计数器)
size_t v5; // rax /***无符号整数,关联rax寄存器(存储字符串长度)
char v7; // [rsp+0h] [rbp-20h] BYREF /***栈上的字符变量,作为内存初始化的起始地址
int j; // [rsp+24h] [rbp+4h] /***32位整数,Str2替换循环的计数器
char Str1[224]; // [rsp+48h] [rbp+28h] BYREF /***存储用户输入的flag(224字节缓冲区)
__int64 v10; // [rsp+128h] [rbp+108h] /***临时变量,存储j的值(无实际作用)
开头先定义了一系列局部变量,这些变量会分配在栈上(RBP 为栈基址)
- 关键:
BYREF表示 “按引用”,是逆向工具(如 IDA)的标注,实际就是栈上的普通变量; - 栈偏移(如
[rsp+0h] [rbp-20h]):表示变量在栈上的位置,无需深究,知道是栈内存即可。
分析 2:第一个循环(栈内存初始化,无实际业务意义)
这是编译器自动生成的 “栈清零” 代码,和 flag 验证无关,逐行拆解:
v3 = &v7; // 步骤2.1:初始化指针v3,指向栈变量v7的地址(内存初始化的起始位置)
for ( i = 82; i; --i ) // 步骤2.2:循环初始化
{
*(_DWORD *)v3 = -858993460; // 步骤2.3:给v3指向的内存赋值
v3 += 4; // 步骤2.4:v3指针向后移动4字节(因为赋值的是4字节DWORD)
}
- 循环次数:
i = 82→ 循环 82 次; - 赋值数值:
-858993460的十六进制是0xCCCCCCCC,这是 MSVC 编译器对未初始化栈变量的默认填充值(表示 “未使用的栈内存”); - 执行效果:从 v7 的地址开始,连续填充 82×4=328 字节的
0xCCCCCCCC,仅为了内存安全,和 flag 无关。
分析3:第二个循环(核心部分,Str2 的字符替换)
这是决定 flag 的关键逻辑,逐行拆解执行流程:
for ( j = 0; ; ++j ) // 步骤3.1:初始化计数器j=0,无终止条件(靠内部break退出)
{
v10 = j; // 步骤3.2:临时存储j的值(逆向残留代码,无实际作用)
if ( j > j_strlen(Str2) ) // 步骤3.3:判断是否遍历完Str2
break; // 满足则退出循环
if ( Str2[j] == 111 ) // 步骤3.4:判断Str2第j个字符是否是'o'
Str2[j] = 48; // 步骤3.5:若是,替换为数字'0'
}
j_strlen(Str2):自定义的字符串长度函数(和标准strlen功能完全一致),返回Str2的字符数(不含结束符\0);- 循环终止条件:
j > j_strlen(Str2)→ 比如Str2长度是 10,j 从 0 到 10 时触发 break(j=10 时Str2[10]是结束符\0,不会触发替换); - ASCII 码对应
1.111 → 字符 'o'(小写字母 o);
2.48 → 字符 '0'(数字零);
- 核心效果:遍历预设字符串
Str2的每一个字符,把所有的'o'替换成'0'(比如Str2是"abc_o123o",替换后变成"abc_01230")。 - 循环次数:
i = 82→ 循环 82 次; - 赋值数值:
-858993460的十六进制是0xCCCCCCCC,这是 MSVC 编译器对未初始化栈变量的默认填充值(表示 “未使用的栈内存”); - 执行效果:从 v7 的地址开始,连续填充 82×4=328 字节的
0xCCCCCCCC,仅为了内存安全,和 flag 无关。
分析4:提示输入 + 读取用户输入
调用两个自定义函数,实现 “打印提示 + 读取输入”:
sub_1400111D1("input the flag:"); // 步骤4.1:打印提示语
sub_14001128F("%20s", Str1); // 步骤4.2:读取用户输入到Str1
sub_1400111D1:对应标准库的printf/puts,作用是打印字符串;sub_14001128F:对应标准库的scanf,作用是读取用户输入;- 格式符
%20s:限制输入最多 20 个字符(超出会截断),输入存储到Str1缓冲区。
分析5:对比输入与替换后的 Str2,输出结果
这是验证 flag 的核心判断逻辑:
v5 = j_strlen(Str2); // 步骤5.1:获取替换后Str2的长度(记为v5)
if ( !strncmp(Str1, Str2, v5) ) // 步骤5.2:对比两个字符串
sub_1400111D1("this is the right flag!\n"); // 匹配则输出正确
else
sub_1400111D1("wrong flag\n"); // 不匹配则输出错误
关键细节:
strncmp(Str1, Str2, v5):
- 功能:对比
Str1和Str2的前v5个字符; - 返回值:完全一致返回 0,不一致返回非 0;
!strncmp(...):即判断返回值是否为 0(是否完全一致);
- 验证规则:用户输入的 Str1,必须和替换后的 Str2 的前 v5 个字符完全一致,才是正确 flag。
分析6:函数返回
return 0; // 程序正常退出,返回值0
答案结果为flag{hell0_w0rld}
小知识:点击数字之后按R键可以转换后的字符
![]()
![]()
更多推荐


所有评论(0)