看雪2022 KCTF 秋季赛 | 第十一题设计思路及解析
2022-12-14 18:2:10 Author: 看雪学苑(查看原文) 阅读量:6 收藏

2022 KCTF 秋季赛 看雪学苑

看雪 2022 KCTF秋季赛 已于11月15日中午12点正式开始!比赛延续上一届的模式并进行优化,对每道题设置了难度值、火力值、精致度等多类积分,用规则引导题目的难度和趣味度。大家请注意:签到题(https://ctf.pediy.com/game-season_fight-216.htm)将持续开放,整个比赛期间均可提交答案,获得积分哦~
本赛季最后一题《衣锦昼行》于今日中午正式截止答题!该题共持续3天,共有4支战队最终提交了正确答案。

下面一起看看该赛题的设计思路和相关解析吧~

出题团队简介

出题方:Archaia
战队介绍:武汉科锐学员队
战队成员id:橘喵Cat、ariesGinn、GloryRef

赛题设计思路

公开的用户名及序列号:
用户名 : D1BD4E3EC707C7CA
序列号 : 84616AC140435FEE

题目答案
用户名 : KCTF
序列号 : 6FB1CE80914403C4

题目设计:此题使用了X86的代码混淆。

1、题目介绍

这次提交的题目是windows32位的应用程序,作战思路是将算法高级代码和汇编进行混淆、膨胀

2、整体设计
(1)用户名加密部分
使用修改过的DES算法,对用户名进行多次加密,将结果进行hash、拼接,再进行hash,得到一个值。

(2)序列号加密部分
使用修改过的DES算法,对序列号进行多次解密,密钥来自于(1)的中间结果,最后得到一个值,与(1)最后生成的值进行比较

3、混淆
(1)32位混淆:对常见汇编指令进行替换、膨胀,增加垃圾代码,乱序

4、算法
使用修改过的DES算法;
使用RSHash算法保护算法部分的内存完整性;

技术要点
(1)X86的代码混淆
(2)算法设计

5、破解时可能会遇到的问题

总体思路:
通过调试器的trace功能或者模拟执行的方式拿到完整的解密算法,最后分析解密算法得到加密算法。

6、需要解决的问题
(1)拿到完整的代码之后,需要写脚本将垃圾指令除去,以便最后的分析
(2)通过分析解密算法得到加密的算法。
(3)需要正面对抗大量冗余代码

7、预期的破解思路
(1)找到混淆规律,提升分析速度

(2)根据关键算法代码写逆算法

赛题解析

本题解析由看雪论坛会员【mb_mgodlfyn】提供:

个人主页:https://bbs.pediy.com/user-home-925585.htm

ida打开,main函数是sub_5B4270,没加混淆。但是main函数调用的其他计算函数都加了混淆(大概看了下,与Archaia战队在前几次比赛中用的差不多)
main函数没有混淆,验证逻辑很容易看出来,大概是对name和serial各做一些计算分别得到两个8字节的值,然后比较两个值是否相等。
比较好的一点是程序没有加壳与反调试,直接动态调试main函数,发现修改中间值的一个bit也会对结果产生巨大的影响,所以似乎只能分析算法逆推。
看了一个小时题目,先去睡觉了(实在不想硬刚代码混淆,再加上昨晚写第十题的wp没怎么睡)晚上起来发现一血只用了一个半小时,果断开始找非预期。
main函数调用的其他函数由于被混淆了,导致ida识别的函数签名很乱,main函数的F5伪代码观感很差。
先修正所有函数的类型,根据汇编里调用函数前的push个数确定参数个数。
修正之后的main函数看上去清爽多了,摘抄如下:【"..."中间的代码与前后相同(除了数组偏移),在此略去】
// bad sp value at call has been detected, the output may be wrong!int __cdecl main(int argc, const char **argv, const char **envp){  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]   memset(v70, 0, 0x1C2u);  memset(v69, 0, sizeof(v69));  qmemcpy(v116, "abcdefgh", sizeof(v116));  memset(name, 0, 0x32u);  *(_DWORD *)Buffer = 0;  v406 = 0;  v207 = 0;  v208 = 0;  v209 = 0;  v210 = 0;  v326 = 0;  v327 = 0;  v328 = 0;  printf(&Format);                              // "请输入用户名:\n"  gets((int)name);  v390 = name;  v293 = &name[1];  v390 += strlen(v390);  v289 = ++v390 - &name[1];  Value = sub_5AE2D3(name, v390 - &name[1]);  itoa(Value, Buffer, 16);    sub_5AD9F9(v116, Buffer, &v207, &v326);  *(_DWORD *)v70 = v207;  *(_DWORD *)&v70[4] = v208;  *(_DWORD *)v69 = v326;  *(_DWORD *)&v69[4] = v327;  qmemcpy(v115, "ijklmnop", sizeof(v115));  v231 = 0;  v232 = 0;  v233 = 0;  v234 = 0;  v323 = 0;  v324 = 0;  v325 = 0;  ...  sub_593D03(v126, Buffer, v77, v277);  qmemcpy(v127, "abcdefgh", sizeof(v127));  memset(v76, 0, sizeof(v76));    v275[0] = 0;  v275[1] = 0;  v276 = 0;  sub_5937B3(v127, Buffer, v76, v275);  memset(v365, 0, sizeof(v365));  v366 = 0;  v3 = sub_5AE2D3(v69, 192);  sprintf(v365, "%08x", v3);    // v365   v401[0] = 0;  v401[1] = 0;  v402 = 0;  v403 = 0;  v404 = 0;  v391[0] = 0;  v391[1] = 0;  v392 = 0;  v393 = 0;  v394 = 0;  sub_592CB9(v70, v365, v401);  v368 = malloc(8u);  v4 = v368;  v5 = v403;  *v368 = v402;  v4[1] = v5;  sub_592CB9(&v70[9], v401, v391);  ...  v44 = malloc(8u);  v387 = v44;  v45 = v403;  *v44 = v402;  v44[1] = v45;  sub_592CB9(&v70[189], v401, v391);            // here, v391 is the correct serial    v44 = malloc(8u);  v387 = v44;  v45 = v403;  *v44 = v402;  v44[1] = v45;  sub_592CB9(&v70[189], v401, v391);  memset(serial, 0, sizeof(serial));  v288 = 0;  memset(v399, 0, sizeof(v399));  v400 = 0;  memset(v395, 0, sizeof(v395));  v396 = 0;  v397 = 0;  v398 = 0;  printf(&byte_5D0848);                         // "请输入序列号:\n"  gets((int)serial);  v389 = serial;  v291 = &serial[1];  v389 += strlen(v389);  v292 = ++v389 - &serial[1];  if ( v389 - &serial[1] == 16 )  {    sub_592C10(&v70[189], v399, serial);    v47 = v387[1];    *(_DWORD *)&v399[8] = *v387;    *(_DWORD *)&v399[12] = v47;    ...    sub_592C10(&v70[9], v399, v395);    v67 = v368[1];    *(_DWORD *)&v399[8] = *v368;    *(_DWORD *)&v399[12] = v67;    sub_592C10(v70, v395, v399);     for ( i = 0; i < 8; ++i )    {      if ( v395[i] != v365[i] )      {        printf(&fail_1);                        // "登录失败!\n"        system(aPause_0);        return 0;      }    }    printf(&success);                           // "登录成功!\n"    system(aPause_1);    return 0;  }  else  {    printf(&fail_2);                            // "登录失败!\n"    system(Command);    return 0;  }}
修正函数签名后,交叉引用的查找也会变得更准。“登录成功”的条件是v395和v365相等,对v365查找交叉引用可以定位到上面的sprintf,是从name计算出来的一个值;v395则是从serial计算出来的一个值。
观察代码,发现v70变量贯穿了大部分代码。

在输入serial之前以v365为初始值正向遍历v70进行了22次迭代计算得到v391;在输入serial之后以serial为初始值反向遍历v70进行了22次迭代计算得到v395。

这两块计算给人感觉很像互逆的。输入公开的name,尝试在0x5B6364下断点查看正向迭代22次后的v391,发现此处的v391正好就是公开的serial。
所以最终获得答案的方法很简单,输入KCTF作为name,提取正向迭代22次后的v391,即为对应的serial。
验证通过
C:\>CrackMe.exe请输入用户名:KCTF请输入序列号:6FB1CE80914403C4登录成功!请按任意键继续. . .
投票评选中
WINTER IS COMING

1、2022 KCTF 秋季赛【新思路奖】,快为你支持的战队投票吧!

https://bbs.pediy.com/thread-275535.htm

2、2022 KCTF 秋季赛【最佳人气奖】,快为你喜欢的战队投票吧!

https://bbs.pediy.com/thread-275534.htm


- End -

球分享

球点赞

球在看

“阅读原文查看详情!

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