ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

BUUCTF-reverse2

BUUCTF-reverse2

记录一次来自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}

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进