BUUCTF reverse3-学习笔记

发布时间:2026/7/28 13:26:19
BUUCTF reverse3-学习笔记 前置知识正文一、UPX脱壳二、IDA静态分析三、编写逆向脚本获取flag前置知识BUUCTF 新年快乐-学习笔记正文一、UPX脱壳使用exeinfope检查文件是否有壳没有壳。二、IDA静态分析用IDA软件打开shiftF12CTRLF搜索fl点击进去看看CTRLX查看谁调用了这个地址F5反编译一下分析伪代码用AI 分析一下// 主函数 main_0// __cdeclC 标准调用约定参数从右往左入栈调用者负责清理栈int__cdeclmain_0(intargc,constchar**argv,constchar**envp){size_tv3;// eax保存字符串长度constchar*Source;// eax保存处理后的字符串指针size_tMaxCount;// eax保存最终比较的字符串长度charv7;// [esp0h] [ebp-188h]临时字符变量实际用于函数调用传参charv8;// [esp0h] [ebp-188h]另一个临时字符变量用于函数调用传参signedinti;// 循环变量 iintn0x64;// 循环变量 n0x64用于初始化 Destination 数组signedintv11;// 保存 Destination 字符串长度charDestination[108];// 目标数组用于存放变换后的用户输入charStr[28];// 用户输入缓冲区最多读取 20 个字符_BYTE v14[8];// 8 字节临时数组可能用于加密/编码函数的输出或上下文// // 第一部分清空 Destination 数组// 将 Destination 的前 100 个字节置 0// for(n0x640;n0x64100;n0x64){// 边界检查如果 n0x64 100报错if((unsignedint)n0x640x64)j____report_rangecheckfailure();// Destination[n0x64] 0// 清空数组避免栈上垃圾数据影响后续逻辑Destination[n0x64]0;}// // 第二部分打印提示信息// sub_41132F(please enter the flag:, v7);// 这个函数大概率是 printf / puts 封装// 作用输出 please enter the flag:// sub_41132F(please enter the flag:,v7);// // 第三部分读取用户输入// sub_411375(%20s, (char)Str);// 这个函数大概率是 scanf / fgets 封装// %20s最多读取 20 个字符防止溢出// 用户输入会存入 Str 数组// sub_411375(%20s,(char)Str);/// 第四部分计算用户输入长度// v3j_strlen(Str);// // 第五部分调用 sub_4110BE 处理用户输入// Source sub_4110BE(Str, v3, v14)// 参数// Str用户输入原始字符串// v3字符串长度// v148 字节临时数组// 返回值处理后的字符串指针// 这里 sub_4110BE 很可能是一个加密/编码/变换函数// Source(constchar*)sub_4110BE(Str,v3,v14);// // 第六部分将处理后的字符串拷贝到 Destination// strncpy(Destination, Source, 0x28u)// 0x28 40// 最多拷贝 40 个字符// strncpy(Destination,Source,0x28u);// // 第七部分计算 Destination 的长度// v11j_strlen(Destination)// // 第八部分对 Destination 做逐字符变换// 第 0 个字符 0// 第 1 个字符 1// 第 2 个字符 2// ...// 第 i 个字符 i// for(i0;iv11;i)Destination[i]i;// // 第九部分再次计算 Destination 长度// MaxCount strlen(Destination)// MaxCountj_strlen(Destination);// // 第十部分与目标字符串 Str2 比较// if (!strncmp(Destination, Str2, MaxCount))// Str2 e3nifIH9b_CndH// 如果变换后的 Destination 等于 Str2输出正确// if(!strncmp(Destination,Str2,MaxCount))sub_41132F(rigth flag!\n,v8);elsesub_41132F(wrong flag!\n,v8);return0;}所以这个程序的逻辑是用户输入 Str -sub_4110BE 处理传给Source -拷贝到 Destination -Destination[i]i -与e3nifIH9b_CndH比较所以这道题的逆向流程就是目标密文e3nifIH9b_CndH-每个字符 - i -得到 Source -再逆向 sub_4110BE -得到原始 flag所以重点看sub_4110BE函数 双击一下进去看看进去发现sub_4110BE只是个跳板函数因为第一行注释// attributes: thunk是 IDA 自动标记代表这是跳板中转函数本身没有任何加密运算逻辑不处理数据功能只有一件事把收到的所有参数原封不动转发给sub_411AB0并且把sub_411AB0的返回值直接向上返回。等价通俗理解sub_4110BE 传话员自己不干活直接把全部参数交给真正干活的sub_411AB0在之前的代码里之前我们猜测sub_4110BE是加密函数现在证实✅真正的加密 / 字符串变换逻辑全部在sub_411AB0内部整条调用链简化main_0 → sub_4110BE(跳板) → sub_411AB0(真实算法)双击进入sub_411AB0函数复行数十步豁然开朗扔给AI分析告诉你这是base64加密函数核心// Base64编码核心函数// p_Str原始输入字符串指针// a2原始字符串长度// a3外部传入的8字节数组函数内部把输出长度写入*a3void*__cdeclsub_411AB0(char*p_Str,size_ta2,_BYTE*a3){intv4;// [espD4h] [ebp-38h] 输出缓冲区下标记录当前写到第几个字节intv5;// [espD4h] [ebp-38h] case1临时下标副本intv6;// [espD4h] [ebp-38h] case2临时下标副本intv7;// [espD4h] [ebp-38h] case3临时下标副本intn3;// [espE0h] [ebp-2Ch] 本轮循环读取到的原始字节数量最多3字节size_tv9;// [espECh] [ebp-20h] 原始字节分组数每3字节一组intv10;// [espECh] [ebp-20h] Base64编码后的输出长度4字节每组intv11;// [espECh] [ebp-20h] 剩余待处理原始字节数量void*v12;// [espF8h] [ebp-14h] malloc分配的Base64输出缓冲区char*p_Str_1;// [esp104h] [ebp-8h] 原始字符串遍历指针// 合法性判断空指针 或 输入长度为0直接返回NULLif(!p_Str||!a2)return0;// 计算Base64输出需要多大内存 v9a2/3;// 完整3字节组数量if((int)(a2/3)%3)// 存在不足3字节的剩余数据v9;v104*v9;// Base64规则每组3字节原始数据编码为4字节*(_DWORD*)a3v10;// 将最终编码长度写入外部传入的a3缓冲区v12malloc(v101);// 分配内存1预留末尾\0字符串结束符if(!v12)// 内存分配失败返回0return0;j_memset(v12,0,v101);// 将缓冲区全部清零p_Str_1p_Str;// 遍历指针指向原始字符串起始v11a2;// 剩余字节总数初始化为输入长度v40;// 输出缓冲区写入位置从0开始// 循环处理所有原始字节每次最多取出3字节while(v110){// algn_41A145、byte_41A144 是IDA全局数组// byte_41A144[0/1/2]存放本轮取出最多3个原始字节// algn_41A145 是字节移位临时缓存IDA反编译变量命名优化缺陷algn_41A145[1]0;algn_41A145[0]0;byte_41A144[0]0;// 循环读取最多3字节原始数据for(n30;n33v111;n3){byte_41A144[n3]*p_Str_1;// 读取1字节存入缓存--v11;// 剩余字节-1p_Str_1;// 原始指针向后移动}if(!n3)// 没有读到任何字节退出循环break;// 根据本轮读到字节数量n3分3种Base64编码情况switch(n3){// 场景1只读到1个原始字节case1:// 第1个Base64字符取原始字节高6bit*((_BYTE*)v12v4)aAbcdefghijklmn[(int)(unsigned__int8)byte_41A144[0]2];v5v41;// 第2个Base64字符原始字节低2bit 填充0bit*((_BYTE*)v12v5)aAbcdefghijklmn[((algn_41A145[0]0xF0)4)|(16*(byte_41A144[0]3))];// 后两个字符填充 (下标64)*((_BYTE*)v12v5)aAbcdefghijklmn[64];*((_BYTE*)v12v5)aAbcdefghijklmn[64];v4v51;// 更新输出下标break;// 场景2读到2个原始字节case2:*((_BYTE*)v12v4)aAbcdefghijklmn[(int)(unsigned__int8)byte_41A144[0]2];v6v41;*((_BYTE*)v12v6)aAbcdefghijklmn[((algn_41A145[0]0xF0)4)|(16*(byte_41A144[0]3))];*((_BYTE*)v12v6)aAbcdefghijklmn[((algn_41A145[1]0xC0)6)|(4*(algn_41A145[0]0xF))];// 末尾1个填充 *((_BYTE*)v12v6)aAbcdefghijklmn[64];v4v61;break;// 场景3完整读到3个原始字节标准分组无填充符case3:*((_BYTE*)v12v4)aAbcdefghijklmn[(int)(unsigned__int8)byte_41A144[0]2];v7v41;*((_BYTE*)v12v7)aAbcdefghijklmn[((algn_41A145[0]0xF0)4)|(16*(byte_41A144[0]3))];*((_BYTE*)v12v7)aAbcdefghijklmn[((algn_41A145[1]0xC0)6)|(4*(algn_41A145[0]0xF))];*((_BYTE*)v12v7)aAbcdefghijklmn[algn_41A145[1]0x3F];v4v71;break;}}*((_BYTE*)v12v4)0;// 在编码结果末尾写入字符串结束符\0returnv12;// 返回Base64编码后的字符串指针即main中的Source}三、编写逆向脚本获取flag设密文 Cipher “e3nifIH9b_CndH”第一步逆运算抵消 Destination[i] i第二步逆运算Base64 解码 Source解码结果就是最终 flagimportbase64 cipherbe3nifIH9b_CndHsourcebytearray()foridx,chinenumerate(cipher):source.append(ch-idx)b64_strsource.decode(ascii)print(正确Base64字符串,b64_str)flagbase64.b64decode(b64_str)print(flag ,flag.decode())