看雪2022 KCTF 春季赛 | 第五题设计思路及解析
2022-5-20 18:16:49 Author: mp.weixin.qq.com(查看原文) 阅读量:15 收藏

2022 KCTF 看雪学苑

看雪 2022 KCTF春季赛 于5月10日中午12点正式开赛!
第五题《危机四伏》已于今日中午12点截止答题,这是一道Windows 逆向题。经统计,此题围观人数2036人,仅4支战队成功破解。
Lixinist.战队】成员【上学困难户】用时6小时40分11秒拿下本题“一血”.
截至发文第五题《危机四伏》攻破战队数仅有4个
接下来和我一起来看看该赛题的设计思路和相关解析吧!

出题团队简介

第五题《危机四伏》出题方 Archaia 战队:

赛题设计思路

此题为分别使用了X86和X64的代码混淆,并使用了天堂之门技术对调试进行干扰。
 

题目介绍

这次提交的题目是windows32位的应用程序,作战思路是将算法代码分成三部分,分别对三部分算法代码进行不同运行模式(32位运行模式和64位运行模式)的混淆,然后使用天堂之门进行运行模式的切换。
 
整体设计

(1)核心算法部分:
本程序将解密算法分割成3部分。
第一部分是用64位指令实现;
第二部分是用32位指令实现;
第三部分是用64位指令实现;
使用天堂之门技术进行三个部分的连接;
(2)除算法外的其他部分:
本程序的其他部分由cpp编写,在项目中使用cpp的异常处理干扰调试者。
 
(3)天堂之门:天堂之门的运行模式切换,能使该程序可以对抗市面上大部分的调试器,使其调试工作不顺利的进行。
 
混淆

(1)64位混淆:64位混淆主要有指令等价替换,流程干扰,花指令,产生垃圾代码、栈地址修改等干扰功能,为了影响攻击者,指令等价替换、产生垃圾代码等功能具有一定随机性。
 
(2)32位混淆:32位的混淆在64位的功能基础上,加入了seh异常处理干扰程序执行流程,主要目的是影响调试器的trace功能。同时将部分代码做了简单的加密处理(将某些指令做了异或加密),也是通过异常的方式获取代码控制权,然后在异常中使用天堂之门切换到64位之后再解密后续的代码,解密完成之后切换到32位继续执行。
 

算法

  1. 使用AES算法和修改的Base64算法(以下简称Base64)验证用户名和序列号是否正确。
    (1) 算法参考KCTF2021春季赛-千里寻根实现。
    (2) 验证算法部分是一段完整的shellcode代码,将其分片成3部分后,做了混淆和校验。
    (3) 如果对输入的序列号解密失败,或者解密结果与用户名不匹配,则提示失败。

  2. 使用CRC32算法保护算法部分的内存完整性
    (1) 在解密shellcode中,随机插入5份CRC32检查,计算出当前EIP前后一段内存范围内的校验值。
    (2) 使用5份校验值分别对Base64映射表的一部分做异或,得到加密后的Base64映射表。
    (3) CrackMe一开始使用该加密后的Base64映射表,只有运行时得到正确的CRC32值,才可以恢复正确的Base64映射表。
    (4) 该校验主要用于反调试,在校验范围内的内存如果有被修改(比如下了int3断点),那么Base64映射表的结果将是错误的。

技术要点

1、天堂之门
2、X86和X64的代码混淆
3、SEH异常
4、算法设计
 
破解时可能会遇到的问题

1.总体思路:
通过调试器的trace功能或者模拟执行的方式拿到完整的解密算法,最后分析解密算法得到加密算法。
2.需要解决的问题
(1)想办法突破天堂之门的限制
(2)在32位算法部分,对抗异常处理和天堂之门
(3)拿到完整的代码之后,需要写脚本将垃圾指令除去,以便最后的分析
(4)通过分析解密算法得到加密的算法。
 
预期的破解思路
1.想办法处理32位到64位天堂之门的模式转换;
 
2.调试第一层,找出第一层64位代码混淆的规律,编写脚本获取到关键信息;
 
3.想办法处理64位到32位天堂之门的模式转换;
 
4.调试第二层,找出第二层32位代码混淆的规律,编写脚本获取到关键信息;
 
5.想办法处理32位到64位天堂之门的模式转换;
 
6.调试第三层,找出第三层64位代码混淆的规律,编写脚本获取到关键信息;
 
7.想办法处理64位到32位天堂之门的模式转换,然后找到结果校验处;
 
8.经过上面的步骤找到的信息,根据特征分离出关键算法代码;
 
9.根据关键算法代码写逆算法。

赛题解析

本赛题解析由看雪论坛专家 ThTsOd 给出:

搜索字符串,发现有一些Failed和一个Succeed,在开头下断点。

发现41ADCC是个比较长度0x10,得到加密数据的位置在0x8995f1
对0x8995f1下硬件断点,发现断在奇怪的地方。

同时偶然发现上方有一串base64,下个硬件断点。

输入64个0,观察,发现加密结果有点熟悉,这个和KCTF2021春季赛第7题差不多。

一血很快就出来了(6小时40分钟),在代码如此混淆加密严重的情况下,觉得是遇到了非预期。

尝试把当时的flag输进去看下,
发现有base64,然后有KCTF出现。


不过要先确定需要比较的加密内容是什么。

CheatEngine搜索KCTF得到答案:


下载出题人的keygen算法对比,修改下代码:

然后还发现xor数值不对,将得到的结果再次xor。
最后代码(仅能注册KCTF)
// KenGen.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。// #include <iostream>#include <windows.h> //#pragma pack(1)//typedef struct tagShareData//{//    uint64_t g_lpCtf;//    uint8_t g_szName[17];//    uint8_t g_szSerial[32];//    uint8_t g_szBase64Table[0x100];//    uint64_t g_qwDecSuccess;//    uint64_t g_qwStackSize;//    uint8_t g_Stack[0x6008];//    uint8_t g_szDec[16];//    uint8_t tmp[24];//}SHAREDATA, * PSHAREDATA;//////SHAREDATA g_sd; uint8_t g_sd[0x6179] = { 0 }; #define g_sd_g_lpCtf               (*(uint64_t*)(g_sd))#define g_sd_g_szName              ((uint8_t*)(g_sd+8))#define g_sd_g_szSerial            ((uint8_t*)(g_sd+25))#define g_sd_g_szBase64Table       ((uint8_t*)(g_sd+57))#define g_sd_g_qwDecSuccess        (*(uint64_t*)(g_sd+313))#define g_sd_g_qwStackSize         (*(uint64_t*)(g_sd+321))#define g_sd_g_Stack               ((uint8_t*)(g_sd+329))#define g_sd_g_szDec               ((uint8_t*)(g_sd+24913))#define g_sd_tmp                   ((uint8_t*)(g_sd+24929))  #define LEFTMOVE(X,D) (( (X) >> (16 - ((D) ) )) | ( (X) << ( (D)  )))  #define RIGHTMOVE(X,D) (( (X) << (16 - ((D) ) )) | ( (X) >> ( (D)  )))  #define XOR_NUMBER 0x8160C68FF6C4875E^0x090A0B004654434Bint hex2string(unsigned char* hex, int size, char* buf){    int i = 0, j = 0;    unsigned int val = 0;     for (; i < size; ++i)    {        val = hex[i] >> 4;        if (val < 10)            buf[j + 0] = '0' + val;        else            buf[j + 0] = 'A' + val - 10;         val = hex[i] & 0x0f;        if (val < 10)            buf[j + 1] = '0' + val;        else            buf[j + 1] = 'A' + val - 10;         j += 2;    }     buf[j] = 0;    return j;} void DecM(LPBYTE g_uhSerial, LPBYTE dec, BYTE x1, BYTE x2){    unsigned short int d[8] = { 0 };    for (int i = 0; i <= 7; i++)    {        d[i] = (((unsigned char*)(g_uhSerial))[(x2 ^ (x1 + 2 * i)) % 16] << 8) + ((unsigned char*)(g_uhSerial))[(x2 ^ (x1 + 2 * i + 1)) % 16];    }     unsigned short int A = d[0] ^ d[1];    unsigned short int B = d[2] + d[3];    unsigned short int C = d[4] - d[5];     //这里面的操作是取d[6] ^ d[7]二进制中1的个数赋值给D    unsigned short int D = d[6] ^ d[7];    D = (D & 0x55555555) + ((D >> 1) & 0x55555555);    D = (D & 0x33333333) + ((D >> 2) & 0x33333333);    D = (D & 0x0F0F0F0F) + ((D >> 4) & 0x0F0F0F0F);    D = (D & 0x00FF00FF) + ((D >> 8) & 0x00FF00FF);    D = (D & 0x0000FFFF) + ((D >> 16) & 0x0000FFFF);     unsigned short int S = (A & B) | ((~A) & C);     d[6] ^= S;    d[7] ^= S;     d[6] = LEFTMOVE(d[6], D);    d[7] = LEFTMOVE(d[7], D);     unsigned short int R = (unsigned short int)(((unsigned int)A * S) >> D) + 24;    d[4] += R; d[5] += R;     unsigned short int U = R ^ C;    d[2] += U; d[3] -= U;      unsigned short int V = (R & S) | (S & U) | (U & R);    d[0] ^= V; d[1] ^= V;     for (int i = 0; i <= 7; i++)    {        *(unsigned short int*)((char*)(dec) + 2 * i) = d[i];    }}  void EncM(LPBYTE g_uhSerial, LPBYTE enc, BYTE x1, BYTE x2){    unsigned short int X[8] = { 0 };    for (int i = 0; i <= 7; i++)    {        X[i] = *(unsigned short int*)((char*)(g_uhSerial) + 2 * i);    }     unsigned short int d[8] = { 0 };    unsigned short int A, B, C, D, S, R, U, V;    C = X[4] - X[5];    B = X[2] + X[3];    A = X[0] ^ X[1];     //这里面的操作是取X[6] ^ X[7]二进制中1的个数赋值给D    D = X[6] ^ X[7];    D = (D & 0x55555555) + ((D >> 1) & 0x55555555);    D = (D & 0x33333333) + ((D >> 2) & 0x33333333);    D = (D & 0x0F0F0F0F) + ((D >> 4) & 0x0F0F0F0F);    D = (D & 0x00FF00FF) + ((D >> 8) & 0x00FF00FF);    D = (D & 0x0000FFFF) + ((D >> 16) & 0x0000FFFF);     S = (A & B) | ((~A) & C);    R = (unsigned short int)(((unsigned int)A * S) >> D) + 24;    U = R ^ C;    V = (R & S) | (S & U) | (U & R);    d[0] = X[0] ^ V; d[1] = X[1] ^ V;    d[2] = X[2] - U; d[3] = X[3] + U;    d[4] = X[4] - R; d[5] = X[5] - R;    d[6] = RIGHTMOVE(X[6], D) ^ S; d[7] = RIGHTMOVE(X[7], D) ^ S;    for (int i = 0; i <= 7; i++)    {        ((unsigned char*)(enc))[(x2 ^ (x1 + 2 * i)) % 16] = (d[i] >> 8);        ((unsigned char*)(enc))[(x2 ^ (x1 + 2 * i + 1)) % 16] = (d[i]);    }}   bool KC_base64_encode(uint8_t* pIn, uint8_t* pOut){    //定义base64编码表    //0O/+KuChaNiUB1FAckL4Hot9zlWEMpm2TG5Sb6Ixdeq8YJPjygXQsRvwVZr73Dnf     unsigned char base64_table[64] = {        0x30, 0x4F, 0x2F, 0x2B, 0x4B, 0x75, 0x43, 0x68, 0x61, 0x4E, 0x69, 0x55, 0x42, 0x31, 0x46, 0x41,        0x63, 0x6B, 0x4C, 0x34, 0x48, 0x6F, 0x74, 0x39, 0x7A, 0x6C, 0x57, 0x45, 0x4D, 0x70, 0x6D, 0x32,        0x54, 0x47, 0x35, 0x53, 0x62, 0x36, 0x49, 0x78, 0x64, 0x65, 0x71, 0x38, 0x59, 0x4A, 0x50, 0x6A,        0x79, 0x67, 0x58, 0x51, 0x73, 0x52, 0x76, 0x77, 0x56, 0x5A, 0x72, 0x37, 0x33, 0x44, 0x6E, 0x66    };      uint32_t nRound = 24 / 3;    uint32_t nEnc = 0;    uint32_t nDec = 0;     //以3个8位字符为一组进行编码     while (nRound-- > 0)    {        uint32_t idx;         idx = ((pIn[nDec + 2] & 0xf) << 2) | (pIn[nDec] & 0x3);        pOut[nEnc] = base64_table[idx];         idx = (pIn[nDec + 1] & 0x3c) | ((pIn[nDec] & 0xc) >> 2);        pOut[nEnc + 1] = base64_table[idx];         idx = ((pIn[nDec + 1] & 0x3) << 4) | (pIn[nDec] >> 4);        pOut[nEnc + 2] = base64_table[idx];         idx = ((pIn[nDec + 1] & 0xc0) >> 2) | (pIn[nDec + 2] >> 4);        pOut[nEnc + 3] = base64_table[idx];         nDec += 3;        nEnc += 4;    }     return true;} void KC_base64_decode(uint8_t* pIn, uint8_t* pOut){    uint32_t nRound = 32 / 4;    uint32_t nEnc = 0;    uint32_t nDec = 0;     //以4个字符为一位进行解码     while (nRound-- > 0)    {        uint8_t tmp0 = g_sd_g_szBase64Table[pIn[nEnc]];        uint8_t tmp1 = g_sd_g_szBase64Table[pIn[nEnc + 1]];        uint8_t tmp2 = g_sd_g_szBase64Table[pIn[nEnc + 2]];        uint8_t tmp3 = g_sd_g_szBase64Table[pIn[nEnc + 3]];         if ((tmp0 & 0xC0) != 0 || (tmp1 & 0xC0) != 0 || (tmp2 & 0xC0) != 0 || (tmp3 & 0xC0) != 0)        {            //走这里就是解密出错了            return;        }        g_sd_g_qwDecSuccess = g_sd_g_qwDecSuccess | (tmp0 & 0xC0);        g_sd_g_qwDecSuccess = g_sd_g_qwDecSuccess | (tmp1 & 0xC0);        g_sd_g_qwDecSuccess = g_sd_g_qwDecSuccess | (tmp2 & 0xC0);        g_sd_g_qwDecSuccess = g_sd_g_qwDecSuccess | (tmp3 & 0xC0);          pOut[nDec] =            (((uint8_t)tmp2) << 4)            | ((((uint8_t)tmp1) & 0x3) << 2)            | (((uint8_t)tmp0) & 0x3);        pOut[nDec + 1] =            ((((uint8_t)tmp3) & 0x30) << 2)            | (((uint8_t)tmp1) & 0x3c)            | (((uint8_t)tmp2) >> 4);        pOut[nDec + 2] =            (((uint8_t)tmp3) << 4)            | (((uint8_t)tmp0) >> 2);         nEnc += 4;        nDec += 3;    }      return;} uint64_t EncQWORD(uint64_t input){    uint64_t result = 0;     uint64_t xor_val = input ^ XOR_NUMBER;     PBYTE pResult = (PBYTE)&result;    PBYTE pXorVal = (PBYTE)&xor_val;     pResult[0] = pXorVal[0] ^ pXorVal[1] ^ pXorVal[2] ^ pXorVal[3];    pResult[1] = pXorVal[0] ^ pXorVal[1] ^ pXorVal[2];    pResult[2] = pXorVal[0] ^ pXorVal[2];    pResult[3] = pXorVal[0];     pResult[4] = pXorVal[4] ^ pXorVal[5] ^ pXorVal[6] ^ pXorVal[7];    pResult[5] = pXorVal[4] ^ pXorVal[5] ^ pXorVal[6];    pResult[6] = pXorVal[4] ^ pXorVal[6];    pResult[7] = pXorVal[4];     return result;} uint64_t DecQWORD(uint64_t input){    uint64_t result = 0;     PBYTE pResult = (PBYTE)&result;    PBYTE pInputVal = (PBYTE)&input;      pResult[2] = pInputVal[2] ^ pInputVal[3];    pResult[1] = pInputVal[1] ^ pInputVal[3] ^ pResult[2];    pResult[3] = pInputVal[0] ^ pInputVal[3] ^ pResult[1] ^ pResult[2];    pResult[0] = pInputVal[3];     pResult[6] = pInputVal[6] ^ pInputVal[7];    pResult[5] = pInputVal[5] ^ pInputVal[7] ^ pResult[6];    pResult[7] = pInputVal[4] ^ pInputVal[7] ^ pResult[5] ^ pResult[6];    pResult[4] = pInputVal[7];     result ^= XOR_NUMBER;    return result;}  void Enc(LPBYTE Serial, LPBYTE enc){    BYTE tmp[24] = { 0 };    BYTE tmp2[32] = { 0 };     EncM(Serial, tmp+8, 3, 8);     *(uint64_t*)tmp = EncQWORD(*(uint64_t*)Serial);     KC_base64_encode(tmp, tmp2);      EncM(tmp2, enc, 11, 7);     EncM(tmp2+16, enc+16, 5, 14);} void Dec(){    BYTE tmp[24] = { 0 };     DecM(g_sd_g_szSerial, g_sd_g_szSerial, 11, 7);    DecM(g_sd_g_szSerial + 16, g_sd_g_szSerial + 16, 5, 14);     KC_base64_decode(g_sd_g_szSerial, tmp);     *(uint64_t*)tmp = DecQWORD(*(uint64_t*)tmp);     memcpy(g_sd_g_szDec, tmp + 8, 16);     DecM(g_sd_g_szDec, g_sd_g_szDec, 3, 8);     g_sd_g_qwDecSuccess = g_sd_g_qwDecSuccess | (*(uint64_t*)g_sd_g_szDec ^ *(uint64_t*)tmp); }   int main(){      BYTE szInput[20] = { 0 };// { 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10 };    BYTE szEnc[32] = { 0 };     char szShow[200] = { 0 };     for (int i = 0; i < 16; i++)    {        szInput[i] = 0x10 - i;    }     printf("input name:");    scanf("%16s", &szInput);       hex2string(szInput, 16, szShow);    printf("\r\n\r\ng_userName : \r\n%s\r\n\r\n", szShow);      Enc(szInput, szEnc);     hex2string(szEnc, 32, szShow);    printf("\r\n\r\nEnc : \r\n%s\r\n\r\n", szShow);      memcpy(g_sd_g_szName, szInput, 16);    memcpy(g_sd_g_szSerial, szEnc, 32);     uint8_t table[256] = {        0x95, 0xE2, 0x80, 0xC6, 0xEA, 0xC3, 0xD5, 0x8D, 0x9E, 0xC5, 0xB3, 0x62, 0x64, 0x4D, 0x76, 0xBA,   //0x00-0x0F        0x92, 0xFD, 0xDE, 0x7F, 0x42, 0x72, 0x81, 0xAD, 0x79, 0x54, 0x73, 0x85, 0x86, 0x5E, 0xF1, 0x84,   //0x10-0x1F        0x6A, 0xF5, 0x63, 0xD8, 0xFE, 0xA8, 0xC0, 0xC8, 0x4F, 0xC9, 0xC7, 0x03, 0x7B, 0xE5, 0xDF, 0x02,   //0x20-0x2F        0x00, 0x0D, 0x1F, 0x3C, 0x13, 0x22, 0x25, 0x3B, 0x2B, 0x17, 0xAA, 0xA0, 0xF6, 0x97, 0x59, 0x58,   //0x30-0x3F        0x6D, 0x0F, 0x0C, 0x06, 0x3D, 0x1B, 0x0E, 0x21, 0x14, 0x26, 0x2D, 0x04, 0x12, 0x1C, 0x09, 0x01,   //0x40-0x4F        0x2E, 0x33, 0x35, 0x23, 0x20, 0x0B, 0x38, 0x1A, 0x32, 0x2C, 0x39, 0x7C, 0xD1, 0xF2, 0x5C, 0x75,   //0x50-0x5F        0xA1, 0x08, 0x24, 0x10, 0x28, 0x29, 0x3F, 0x31, 0x07, 0x0A, 0x2F, 0x11, 0x19, 0x1E, 0x3E, 0x15,   //0x60-0x6F        0x1D, 0x2A, 0x3A, 0x34, 0x16, 0x05, 0x36, 0x37, 0x27, 0x30, 0x18, 0x6C, 0x4A, 0x7A, 0x44, 0x98,   //0x70-0x7F        0x96, 0x69, 0xC4, 0xEB, 0xCC, 0x49, 0xBE, 0xB5, 0x48, 0x71, 0x94, 0xE1, 0xA3, 0xB1, 0x78, 0xFA,   //0x80-0x8F        0x53, 0x46, 0x40, 0xCB, 0xBC, 0x47, 0x83, 0xC1, 0xEE, 0xF9, 0xE8, 0x61, 0xA9, 0xFB, 0xC2, 0xD2,   //0x90-0x9F        0x4C, 0x55, 0xDA, 0xF7, 0x7E, 0xD9, 0x8F, 0xAC, 0xE3, 0x52, 0x60, 0x9B, 0xE9, 0x56, 0x9C, 0x89,   //0xA0-0xAF        0x57, 0xB4, 0x51, 0x7D, 0xB0, 0x74, 0x8E, 0xA2, 0x9D, 0xED, 0xB6, 0xE0, 0x5F, 0xFC, 0x4B, 0x6E,   //0xB0-0xBF        0xA5, 0x41, 0xD0, 0xA7, 0xBB, 0xF0, 0x8C, 0x91, 0x65, 0xB9, 0xD4, 0xE6, 0x87, 0xB8, 0xBF, 0xF8,   //0xC0-0xCF        0xEC, 0x9F, 0x9A, 0xD3, 0x6F, 0x93, 0x5D, 0x66, 0x88, 0x43, 0x5B, 0x50, 0xF3, 0x82, 0xB7, 0xCE,   //0xD0-0xDF        0x67, 0xE7, 0xF4, 0xFF, 0xAF, 0xCD, 0xD6, 0xDC, 0xAB, 0x68, 0x5A, 0x8A, 0xDD, 0xEF, 0xE4, 0xBD,   //0xE0-0xEF        0x4E, 0xA4, 0x77, 0xB2, 0x45, 0xA6, 0x99, 0x70, 0xDB, 0x6B, 0xD7, 0x90, 0xCF, 0xAE, 0x8B, 0xCA    //0xF0-0xFF    };        memcpy(g_sd_g_szBase64Table, table, sizeof(table));      Dec();     hex2string(g_sd_g_szDec, 16, szShow);    printf("\r\n\r\nDec : %lld (<-- 必须要为0)\r\n%s\r\n\r\n",g_sd_g_qwDecSuccess, szShow);      system("pause"); }
得到正确答案:39ED62B341BC560217EAB3BF90265D101067856B36495264144A5B487264CB4B

第六题《废土末世》正在进行中,

👆还在等什么,快来参赛吧!
如何成为一名出色的CTF选手?
*点击图片查看详情
入门-基础-进阶-强化,只需四个阶段!摇身一变成为主力、中坚力量

*点击图片查看大图


- End -

球分享

球点赞

球在看

“阅读原文展开第六题的战斗!

文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458446726&idx=1&sn=8165fe70974e4521afa289b060255a4b&chksm=b18fdb0c86f8521adb3583d94be0bec36b711ab981878ab4530ab8d4ea350fcb214382bc25f5#rd
如有侵权请联系:admin#unsafe.sh