![[网鼎杯 2020 青龙组] jocker-学习笔记](http://pic.xiahunao.cn/yaotu/[网鼎杯 2020 青龙组] jocker-学习笔记)
前置知识正文一、UPX脱壳二、ida静态分析三、脚本手动解密encrypt 函数前 19 字符1、 Shift F2召唤脚本2、CCode转成代码)**3、PProcedure创建函数**4、按 F5 验证5、shifitE提取核心数据6、python跑两个16进制的xor运算四、二次解密finally 函数后 5 字符完整解题脚本前置知识XOR 性质a ^ k ^ k a用同一个值异或两次恢复原值SMC程序运行时动态修改自身代码区增加静态分析难度VirtualProtectWindows API修改内存保护属性使代码段可写知识点说明SMC 自修改代码运行时用 XOR 解密代码段绕过静态分析。需用 IDAPython 或动态调试修复VirtualProtect修改内存保护属性使代码段可写烟雾弹omg() 即使失败也不 exit只打印信息迷惑分析者双路径校验wrongomg 是假的encrypt 才是真校验finally 是最后一步已知明文攻击利用 flag 末尾必为}的特性反推 XOR keyIDA 修复 SMC选中区域 U(ndefine) → C(ode) → P(rocedure) 三步修复Q: 为什么 encrypt 和 finally 的函数体在 IDA 中看不到A: 因为它们被 SMC 保护了。程序运行时先用VirtualProtect取消写保护再用 XOR 0x41 解密。需要在 IDA 中执行 IDAPython 脚本手动解密patch。正文一、UPX脱壳使用exeinfope检查文件无壳。二、ida静态分析在 IDA 中打开jocker.exe32 位定位到_main0x401726按 F5 反编译int__cdeclmain(){charStr[50],Destination[80];puts(please input you flag:);VirtualProtect(encrypt,0xC8,4,flOldProtect);// 取消写保护scanf(%40s,Str);if(strlen(Str)!24){puts(Wrong!);exit(0);}strcpy(Destination,Str);// 备份原始输入wrong(Str);// 变换 Str不影响 Destinationomg(Str);// 检查变换结果仅打印不退出// ★ SMC: 解密 encrypt 函数XOR 0x41for(inti0;i186;i)*((BYTE*)encrypti)^0x41;if(encrypt(Destination))// ★ 真正的 flag 检查finally(Destination);// ★ 最后的校验}可以进入wrong()里面看for(i0;i23;i){if(i1)Str[i]-i;// 奇数减 ielseStr[i]^i;// 偶数 XOR i}omg()里取src_0x4030c0的 24 个 dword 与 wrong 变换后的结果比较。src_ 数据提取0x66 0x6B 0x63 0x64 0x7F 0x61 0x67 0x64 0x3B 0x56 0x6B 0x61 0x7B 0x26 0x3B 0x50 0x63 0x5F 0x4D 0x5A 0x71 0x0C 0x37 0x66逆向 wrong() 得到假 flagflag{fak3_alw35_sp_me!!}可以发现以下信息函数性质说明wrongomg烟雾弹只打印不 exit可产生假 flagencryptSMC 加密被 XOR 0x41 加密需解密finallySMC 加密也被 XOR 0x41 加密需解密三、脚本手动解密encrypt 函数前 19 字符1、 Shift F2召唤脚本静态打开 IDA 时可以看到0x14 0xC8 0xA4 0x16 ...← 乱码没法反编译在 IDA 中shiftF2执行以下 Python 脚本将0x401500起的 187 字节 XOR 0x41importidc addr0x401500foriinrange(187):bidc.get_bytes(addri,1)idc.patch_byte(addri,ord(b)^0x41)运行时真实代码0x55 0x89 0xE5 0x57 ...← 正常 x86 指令2、CCode转成代码)**保持选中状态按 C Make Code / Force code效果IDA 把这一片字节重新解码为 x86 汇编指令此时你应该能看到正常的指令 push ebp / mov ebp, esp / …3、PProcedure创建函数**按 P Create Function效果IDA 自动分析函数边界、参数、局部变量建立反编译所需的函数框架此时函数窗口里会出现完整的 encrypt 函数函数体不再是 6 字节的残缺4、按 F5 验证在函数开头按 F5 能正常弹出 C 伪代码就成功了。int__cdeclencrypt(char*a1){intv2[19];qmemcpy(v2,unk_403040,sizeof(v2));// 复制 19 个 dwordfor(inti0;i18;i){if((char)(a1[i]^Buffer[i])!v2[i]){// Buffer hahahaha_do_you_find_me?puts(wrong ~);exit(0);}}return1;}核心判断a1[i] ^ hahahaha_do_you_find_me?[i] expected[i]所以flag[i] expected[i] ^ hahahaha_do_you_find_me?[i]5、shifitE提取核心数据已知expected19 dword取低字节0x0E 0x0D 0x09 0x06 0x13 0x05 0x58 0x56 0x3E 0x06 0x0C 0x3C 0x1F 0x57 0x14 0x6B 0x57 0x59 0x0DXOR keyhahahaha_do_you_find_me?前 19 字节h a h a h a h a _ d o _ y o u _ f i n计算过程flag[i] expected[i] ^ key[i]iexpected^ key flag00x0E‘h’f10x0D‘a’l20x09‘h’a30x06‘a’g40x13‘h’{50x05‘a’d60x58‘h’070x56‘a’780x3E‘_’a90x06‘d’b100x0C‘o’c110x3C‘_’c120x1F‘y’f130x57‘o’8140x14‘u’a150x6B‘_’4160x57‘f’1170x59‘i’0180x0D‘n’c6、python跑两个16进制的xor运算用python脚本可以跑一下key_strhahahaha_do_you_find_me?# ① 密钥字符串key[ord(c)forcinkey_str]# ② 字符→ASCII数字: ord(h)0x68expected[0x0E,0x0D,0x09,0x06,0x13,0x05,0x58,0x56,0x3E,0x06,0x0C,0x3C,0x1F,0x57,0x14,0x6B,0x57,0x59,0x0D]# ③ 预期值数组flag[]foriinrange(19):# ④ 循环 19 次eexpected[i]# 取 expected 第 i 字节kkey[i]# 取 key 第 i 字节fe^k# XOR 运算flag.append(f)# 收集结果print(.join(chr(c)forcinflag))# ⑤ 数字→字符→拼接代码作用新手疑问ord(h)字符转 ASCII 数字ord(h)1040x68因为内存里存的是数字不是字母chr(0x66)数字转回字符chr(102)f最后输出时要转回字符0x5F十六进制写法0x5F 95 _的 ASCII^Python 里的异或运算符不是数学上的幂运算前 19 字符flag{d07abccf8a410c四、二次解密finally 函数后 5 字符finally函数同样被 SMC 加密了。解密 finally 不需要单独的脚本 因为 main 里那个 for (i0; i186; i) 循环是从0x401500开始 连续解密 187 字节 而 finally 在0x40159A偏移 0x9A 154 186 正好落在同一个范围内 。所以同一个脚本就同时解密了两个函数。用相同的方法解密XOR 0x41 后重新分析int__cdeclfinally(char*a1){charv3[9];strcpy(v3,%tp:);// 栈上构造字符串srand(time(0));v4rand()%100;// 随机化比较无法直接逆向if((v3[v3[5]]!a1[v3[5]])v4)returnputs(Really???);elsereturnputs(I hide the last part...);}finally函数做了随机比较无法直接逆向。但我们可以通过社工技巧得到后 5 位finally中有硬编码字符串%tp:假设它对字符串的操作与encrypt相同XOR 固定 key已知 flag 以}结尾key : ^ }0x3A ^ 0x7D0x47 71计算后 5 字符原字符ASCII^ 0x47结果%0x250x62bt0x740x333p0x700x3770x260x61a:0x3A0x7D}后 5 字符b37a}最终 Flagflag{d07abccf8a410cb37a}完整解题脚本# -*- coding: utf-8 -*-# encrypt 部分前 19 字符key[ord(c)forcinhahahaha_do_you_find_me?]exp[0x0e,0x0d,0x09,0x06,0x13,0x05,0x58,0x56,0x3e,0x06,0x0c,0x3c,0x1f,0x57,0x14,0x6b,0x57,0x59,0x0d]flag[exp[i]^key[i]foriinrange(19)]# finally 部分后 5 字符s%tp:xor_keyord(})^ord(s[-1])# 0x47forcins:flag.append(ord(c)^xor_key)print(.join(chr(c)forcinflag))# 输出: flag{d07abccf8a410cb37a}