记录一次来自BUUCTF平台reverse系列
进入平台点击该题目,首先下载题目中给的压缩包并打开,然后解压
拖到IDA中寻找main函数,发现有类似的flag值
但结果不是此flag值,于是按f5查看
先做个分析
一、变量定义部分
int stat_loc; // [rsp+4h] [rbp-3Ch] BYREF int i; // [rsp+8h] [rbp-38h] __pid_t pid; // [rsp+Ch] [rbp-34h] char s2[24]; // [rsp+10h] [rbp-30h] BYREF unsigned __int64 v8; // [rsp+28h] [rbp-18h]stat_loc:用于存储子进程的退出状态,BYREF表示该变量会按引用传递给waitpid函数。i:循环计数器,用于遍历flag字符串的每个字符。7天pid:存储fork()函数返回的进程 ID(PID),__pid_t是进程 ID 的专用类型。s2[24]:存储用户输入的 flag,数组长度 24 是为了预留足够空间(后续scanf限制最多读 20 个字符)。v8:栈保护相关变量(栈金丝雀 / Stack Canary),__readfsqword(0x28u)是读取操作系统fs段的栈保护值,防止栈溢出攻击,属于编译器自动插入的安全代码。
二.栈保护初始化
v8 = __readfsqword(0x28u);- 读取
fs段偏移0x28处的栈保护值并赋值给v8,函数结束时会校验该值是否被篡改,以此检测栈溢出。
三. 创建子进程
pid = fork();- fork()是 Linux/Unix 系统调用,作用是创建一个新的子进程。
- 核心特性:调用 1 次,返回 2 次:
1.父进程中:返回子进程的 PID(非 0 值);
2.子进程中:返回0。
四. 父进程分支逻辑
if ( pid ) { waitpid(pid, &stat_loc, 0); }- 因为父进程中
pid是非 0 值,所以进入该分支。 waitpid(pid, &stat_loc, 0):阻塞父进程,等待pid对应的子进程执行完毕,stat_loc会接收子进程的退出状态(比如正常退出 / 异常终止)。
五. 子进程分支逻辑
else { for ( i = 0; i <= strlen(flag); ++i ) { if ( flag[i] == 105 || flag[i] == 114 ) flag[i] = 49; } }- 子进程中
pid=0,进入该分支,核心是修改flag字符串:
1.strlen(flag):计算原始flag字符串的长度(不包含结束符'\0');
2.循环范围i=0到i<=strlen(flag):遍历flag的每个字符(包含结束符'\0');
3.ASCII 码对应关系:105 = 'i',114 = 'r',49 = '1';
4.逻辑:遍历flag的每个字符,只要是字符'i'或'r',就替换成'1'。
六. 核心漏洞点
子进程是父进程的内存副本(写时复制机制),子进程对flag的修改仅作用于自身的内存空间,不会影响父进程的flag变量。也就是说,子进程的修改完全是 “无效操作”。
七. 读取用户输入并验证 flag
printf("input the flag:"); __isoc99_scanf("%20s", s2); if ( !strcmp(flag, s2) ) return puts("this is the right flag!"); else return puts("wrong flag!");printf("input the flag:"):提示用户输入 flag__isoc99_scanf("%20s", s2):读取用户输入的字符串,%20s限制最多读 20 个字符,防止数组越界;strcmp(flag, s2):
比较flag和用户输入的s2:
1.相等时返回 0,!0为真,输出 “this is the right flag!”
2.不相等时返回非 0,!非0为假,输出 “wrong flag!”
- 由于子进程的修改不影响父进程,这里对比的是原始的
flag,而非修改后的版本。
答案为:flag{hack1ng_fo1_fun}