释放多层代码
最近在做2019看雪CTF总决赛第6题—三道八佛,很有意思。它采用内存位置无关代码编程并加壳,经过1401次对折叠代码层层随机释放。在前面的所有层中都没有解密程序的逻辑,只有单步到最后一层才会有真正的完整代码逻辑。
代码都是内存位置无关代码,在保存输入的用户名和序列号后便开始解密代码,将系统的栈克隆到全局区域(mov dword ptr fs:[4], eax和mov dword ptr fs:[8], ebx),最后通过call eax跳转到解密后的代码。
由于每次释放前都需要先复制迁移栈并设置挂靠,我们可以选择TEB中的StackBase作为硬件断点,然后在StackBase处设置硬件写断点(使用x32dbg调试)。
然后一直按着F9不动,在触发了1400次时停下来,再进行操作。因为最后一次修改fs:[8]后还有一次自解密,还需要跟踪才能到达真正的验证算法。如果直接运行1401次,关键代码都被清除了。
其实,我们也可以通过运行脚本的方式来获得核心代码。
最终获得的核心代码如下图所示,以mov ebp, esp, sub esp, 28开始。
做到此步我基本就没有多少思路了,该函数牵涉到的运算特别复杂,看着头大。抱着学习的态度参考了HHHso和KevinsBobo两位大佬的做法。
[原创] KCTF 2019 Q4 第六题 一个支点-CTF对抗-看雪论坛-安全社区|安全招聘|bbs.pediy.com
[原创]看雪CTF 2019总决赛 第六题 三道八佛-CTF对抗-看雪论坛-安全社区|安全招聘|bbs.pediy.com
HHHso将核心代码dump下来,长度为0x9e56,加载到0x4016AC处,然后用IDA F5出伪码。代码非常复杂,且和他列出的完全不一样,如下所示。
看着这么复杂的代码,完全没有逆出算法的信心。
转而,学习KevinsBobo的方法:仅把牵涉到key操作的函数代码dump出来,长度仅为0x3453。手动将dump的文件末尾一个字节改成c3,即ret,就可以用ida创建函数F5反编译了。
根据KevinsBobo逆向出的算法代码,照猫画虎,修改F5后源码中的变量名称,截图如下。
说明此法得到算法逻辑是可行的,F5出来的代码十分简洁。完整的算法还原代码如下
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <string.h>
unsigned char esiData[17] ={0};
#define __ROL2__(x, n) (((x) << (n)) | ((x) >> (16-(n))))
intcheck(unsigned char*esiData,WORD *calc_r)
{
WORD k_E_D; //ST5C_2
WORD k_A_9; //ST58_2
WORD k_C_B; //di
WORD k_0_F; //ST54_2
WORD k_6_5; //ST60_2
WORD k_2_1; //edx
WORD k_8_7; //ebx
WORD k_4_3; //ST48_4
WORD v14; //eax
WORD v17; //edx
WORD v18; //eax
WORD v19; //eax
WORD v20; //ST00_2
WORD v22; //ecx
WORD result; //eax
k_2_1 =esiData[0x1] +(esiData[0x2] << 8);
k_4_3 =esiData[0x3] +(esiData[0x4] << 8);
k_6_5 =esiData[0x5] +(esiData[0x6] << 8);
k_8_7 =esiData[0x7] +(esiData[0x8] << 8);
k_A_9 =esiData[0x9] +(esiData[0xA] << 8);
k_C_B =esiData[0xB] +(esiData[0xC] << 8);
k_E_D =esiData[0xD] +(esiData[0xE] << 8);
k_0_F =esiData[0xF] +(esiData[0x0] << 8);
WORD t0 =k_2_1 ^ k_4_3;
WORD t1 =k_6_5 ^ k_8_7;
WORD t2 =k_C_B -k_A_9;
WORD t3 =k_0_F +k_E_D;
WORD x1 =((((t1 & 0x5555) +((t1 >> 1) & 0x5555)) & 0x3333) +((((t1 & 0x5555) +((t1 >> 1) & 0x5555)) >> 2) & 0x3333));
x1 =((((x1 & 0xF0F) +((x1 >> 4) & 0xF0F)) >> 8) +((x1 & 0xF) +((x1 >> 4) & 0xF)));
v14 =(t2 & ~t0 | t3 & t0);
v17 =(t0 *v14 >> x1) +24;
v19 =t2 ^ v17;
v20 =v14 | v17;
v22 =v14 & v17 | (v19 & v20);
calc_r[0] =k_4_3 ^ v22; //0 4B 43 434B
calc_r[1] =k_2_1 ^ v22; //2 54 46 4654
calc_r[2] =k_0_F +v19; //4 00 1A 1A00
calc_r[3] =k_E_D -v19; //6 19 18 1819
calc_r[4] =k_C_B +v17; //8 17 16 1617
calc_r[5] =k_A_9 +v17; //A 15 14 1415
calc_r[6] =__ROL2__(v14 ^ k_8_7, x1 & 0xFF); //C 13 12 1213
calc_r[7] =__ROL2__(v14 ^ k_6_5, x1 & 0xFF); //E 11 10 1011
return1; //K C T F
//4B435446001A19181716151413121110
}
总结:根据HHHso和KevinsBobo的不同做法可以得出,给IDA分析的代码越小越好,利于还原算法。
然后运行KevinsBobo给出的C++爆破解法,得到
UserName:KCTF
Key:6CCDE9D2EC1D469DC67C647E66B4C565