释放多层代码

2021-08-23  本文已影响0人  静析机言

最近在做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

上一篇下一篇

猜你喜欢

热点阅读