下载附件die查壳64位IDA打开f5伪码进行分析int __fastcall main(int argc, const char **argv, const char **envp)这是main函数的标准参数第三个属于主流编译器通用扩展int argc全称 argument count命令行参数的个数。至少为 1程序自身的路径会算作第 0 个参数。const char **argv全称 argument vector命令行参数数组。是字符串指针的指针可以理解为字符串数组argv[0]是程序名argv[1]及之后是用户传入的参数const表示这些字符串内容不可修改。const char **envp全称 environment pointer环境变量数组。每个元素是一条环境变量字符串格式类似PATHC:\Windows程序可以读取当前系统的环境变量。_main(argc, argv, envp);在执行后面我们真正的业务代码也就是复制密文、打印提示、读取输入、校验 flag 这些逻辑之前先调用它完成程序运行的前置准备工作初始化全局变量、静态变量的初始值初始化 C 标准库的运行环境让printf、fgets这类库函数能正常工作处理命令行参数、环境变量的初始化如果是 C 程序还会执行所有全局对象的构造函数调用完成后它会返回然后程序才会顺序执行后面的 flag 校验逻辑。这是一行内存拷贝语句作用是给密文数组v6填入前半部分的初始数据。qmemcpy(v6, “350$/2#2/,608*282,”, 24);qmemcpy这是编译器优化后的快速内存拷贝函数功能和 C 标准库的memcpy完全一致按字节把源内存里的数据原样复制到目标内存中。q是quick的缩写是编译器内置的高效实现版本IDA 反编译时会识别并标注为qmemcpy。第一个参数v6拷贝的目标地址。数据会被复制到我们之前说的v6[30]字节数组里也就是程序用来存放正确密文的缓冲区。第二个参数350$/2#2/,608*282,拷贝的源数据。这是程序硬编码在数据段里的字符串常量每个字符对应 1 字节的 ASCII 码是密文的前半部分内容。第三个参数24拷贝的字节长度。表示从源字符串里连续复制 24 个字节到v6数组的起始位置。这行代码的作用是给密文数组v6从第 22 号下标开始的连续 8 字节写入一个 64 位常量补全密文的后半部分。*(_QWORD *)v6[22] 0xDA626F696F38262Cui64;v6[22]取数组v6中下标为 22 的元素的内存地址。数组下标从 0 开始也就是从数组开头往后数第 23 个字节的位置作为本次写入的起始地址。(_QWORD *)这是强制类型转换把原本指向 1 字节的字符指针转换成指向8 字节QWORD64 位的指针。_QWORD是 IDA 里的类型名对应 C 语言的unsigned long long占 8 个字节。*(_QWORD *)...最前面的*是解引用操作表示直接操作这个指针指向的 8 字节内存空间也就是一次性修改连续 8 个字节的内容。0xDA626F696F38262Cui64要写入的 64 位无符号常量0x开头表示十六进制数值ui64是 IDA 的标注说明这是unsigned int 6464 位无符号整数类型。结合前面的qmemcpy这两步共同完成 30 字节密文数组v6的初始化前半部分用字符串常量快速填充后半部分用一次 64 位赋值补全并覆盖尾部 2 字节最终得到完整的 30 字节密文用于后续和用户输入处理后的结果逐位对比。这是编译器的常见优化写法用字符串批量填充 64 位直接赋值比逐字节赋值执行效率更高。v3 __acrt_iob_func(0);这行代码的作用是获取标准输入流的文件指针为后续读取你在控制台输入的 flag 做准备。逐部分拆解__acrt_iob_func这是微软 VC 运行时库UCRT的内部函数用来获取标准 I/O 流的FILE*指针。新版本的微软 C 运行库把stdin/stdout/stderr从全局变量改成了通过函数返回因此 IDA 反编译后会显示成这个函数调用。参数0表示要获取第 0 号标准流也就是标准输入stdin对应键盘输入。参数 1 对应标准输出stdout控制台打印参数 2 对应标准错误stderr错误信息输出v3 ...将获取到的标准输入流指针赋值给前面定义的FILE *类型变量v3。拿到这个输入流指针后程序就会通过fgets(Buffer, 128, v3)读取你在控制台输入的字符串存到Buffer数组里用于后续校验。逆向分析时这行属于系统库逻辑不用深究内部实现知道它对应键盘输入即可。if ( !fgets(Buffer, 128, v3) )return 0;这行代码的作用是读取用户输入的字符串并做读取失败的异常处理。fgets(Buffer, 128, v3)这是 C 语言标准库的按行读取字符串函数三个参数含义它会一直读到换行符或者达到长度上限为止读到的内容会包含末尾的换行符。Buffer目标缓冲区读到的内容会存到这个 128 字节的字符数组里128最大读取长度实际最多读取 127 个字符最后会自动补一个字符串结束符\0v3输入源这里就是前面拿到的标准输入流stdin对应你在控制台键盘输入的内容。!fgets(...)这是对函数返回值做判断读取成功时fgets返回Buffer的首地址非空逻辑上为 “真”读取失败 / 遇到文件结束EOF时返回NULL空指针逻辑上为 “假”前面的!是逻辑取反也就是**“如果读取失败”**的条件。return 0;如果读取失败程序直接返回 0正常退出不再执行后面的长度校验、flag 对比等逻辑。执行逻辑总结正常输入内容并回车时fgets读取成功条件不成立程序继续往下执行如果输入异常比如直接按 CtrlZ 结束输入程序就直接退出。v10 strlen(Buffer);if ( v10 ){if ( Buffer[v10 - 1] 10 )Buffer[–v10] 0;}这几行是清理用户输入、去掉末尾换行符的标准处理逻辑因为fgets读取输入时会把你按的回车换行符也存进缓冲区需要提前去掉才能正确校验 flag。逐行拆解v10 strlen(Buffer);计算输入字符串的长度把结果存到变量v10里。strlen会统计从字符串开头到结束符\0之间的字符个数不包含结束符本身。if ( v10 )判断字符串长度不为 0。如果用户直接回车导致空串就跳过后续处理避免出现v10 - 1 -1的数组下标越界错误。if ( Buffer[v10 - 1] 10 )判断字符串的最后一个字符是不是 ASCII 码为 10 的字符也就是换行符\n。这是fgets的特性读到换行符就停止读取并且会把这个换行符原样存入缓冲区所以正常输入后字符串末尾都会带一个\n。Buffer[--v10] 0;这一步完成两个操作--v10先将长度v10减 1下标指向末尾的换行符位置 0把这个位置的字符改成字符串结束符\0。效果就是删掉了末尾的换行符同时字符串长度也对应减少了 1。整体作用清理掉输入自带的换行符保证后续的长度校验必须等于 30和逐位加密对比是基于纯净的输入内容来计算的。if ( v8 (_DWORD)v10 ){for ( i 0; i v8; i ){v7 Buffer[i];v7 3;v7 ^ 0x5Au;if ( v7 ! v6[i] ){printf(“Incorrect.\n”);return 0;}}printf(“Correct! Here is your flag: %s\n”, Buffer);return 0;}这是整个程序的核心校验逻辑负责验证你输入的内容是不是正确的 flag。1. 外层长度校验if ( v8 (_DWORD)v10 )v8是程序预设的正确长度在这里值为 30和密文数组v6的长度一致v10是你输入的字符串去掉换行后的实际长度(_DWORD)是强制类型转换把 64 位的v10转成 32 位整数再比较不影响逻辑本身。这一步的作用是先卡长度如果输入长度不等于 30直接跳过校验逻辑走到后面的else分支输出Wrong length.并退出。2. 循环遍历每个字符for ( i 0; i v8; i )循环变量i从 0 开始每次加 1直到i v8不成立为止一共执行 30 次逐个处理你输入的每一个字符和密文逐位对比。3. 单字符加密变换核心算法v7 Buffer[i]; v7 3; v7 ^ 0x5Au;这三行是对输入字符的加密步骤依次执行取出你输入的第i个字符暂存到变量v7字符的 ASCII 码数值加 3把加 3 后的结果和十六进制常量0x5A十进制 90做按位异或运算。异或运算的特点是可逆同一个值异或两次就会还原成原值这也是我们能反向算出 flag 的基础。4. 逐位对比校验if ( v7 ! v6[i] ) { printf(Incorrect.\n); return 0; }把加密后的字符和内置密文数组v6的第i位做对比只要有任意一位不相等就立刻输出Incorrect.错误然后直接退出程序后面的字符不再检查这是逆向题里很常见的 “逐位校验” 写法。5. 校验成功分支如果整个循环跑完30 个字符全部匹配成功就会执行printf(Correct! Here is your flag: %s\n, Buffer); return 0;输出Correct!提示并且把你输入的内容当作 flag 打印出来程序正常结束。整体逻辑总结程序的验证流程可以概括为输入长度必须等于 30对每个输入字符执行「ASCII 码 3 → 异或 0x5A」的变换变换结果和内置密文逐位完全一致才算正确。我们解题就是把这个过程反过来拿密文先异或0x5A再减 3就能反向算出正确的 flag 明文接下来写解密脚本# 1. 构造完整的30字节密文# 前22字节来自硬编码字符串第22位之后会被后面的64位常量覆盖cipher_str350$/2#2/,608*282,cipherlist(cipher_str.encode())[:22]# 补充后8字节64位常量 0xDA626F696F38262C 按小端序拆分cipher[0x2C,0x26,0x38,0x6F,0x69,0x6F,0x62,0xDA]# 2. 逐字节解密先异或 0x5A再减 3加密的逆运算flagforbyte_valincipher:plain_byte(byte_val^0x5A)-3flagchr(plain_byte)# 3. 输出结果print(解密得到的flag,flag)