看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

WriteUp 2年前 (2022) admin
538 0 0
看雪 2022 KCTF秋季赛 已于11月15日中午12点正式开始!比赛延续上一届的模式并进行优化,对每道题设置了难度值、火力值、精致度等多类积分,用规则引导题目的难度和趣味度。大家请注意:签到题(https://ctf.pediy.com/game-season_fight-216.htm)将持续开放,整个比赛期间均可提交答案,获得积分哦~

第六题《病疫先兆》今日已经关闭答题通道,据统计,24小时内共有41支战队成功提交flag!
看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

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



出题团队简介


第6题《病疫先兆》出题方 中午吃什么 战队

战队成员id:wx_孤城、瑞皇、hmfzy
看雪2022 KCTF 秋季赛 | 第六题设计思路及解析


赛题设计思路


战队名称:中午吃什么


参赛题目:CrackMe(Windows)


题目答案:14725KCTF83690


使用方案一(老规则)

需要穷举爆破随机数种子(0-99999,穷举时间一分钟以内)


运行流程:输入序列号、输出success或error


详细的题目设计说明:
1、判断输入文本长度是否为14;

2、将输入文本拆分为3份(5字节、4字节、5字节);
3、将2个5字节转为int,作为随机数种子调用srand;
4、调用rand生成20个int数据;
5、rand生成的数据和全局数组相等且4字节文本为KCTF则成功。

#include <stdio.h>#include <stdlib.h>
unsigned int arrSeed_14725[] = { 15356, 8563 , 9659 , 14347, 11283, 30142, 29542, 18083, 5057 , 5531 , 23391, 21327, 20023, 14852, 4865 , 23820, 16725, 18665, 25042, 24920 };
unsigned int arrSeed_83690[] = { 11190, 27482, 980 , 5419 , 28164, 9548 , 16558, 22218, 6113 , 21959, 13889, 11580, 2625 , 19397, 25139, 8167 , 28165, 3950 , 25496, 27351 };
int my_strlen(const char* StrDest){ return ('' != *StrDest) ? (1 + my_strlen(StrDest + 1)) : 0;}
#if 0//爆破代码int main(){ int i, j; int isSuccess = 0; for (i = 0; i < 99999; i++) { isSuccess = 1; //printf("0x%08xn", i); srand(i); for (j = 0; j < 20; j++) { if (rand() != arrSeed_14725[j]) { isSuccess = 0; break; } } if (isSuccess != 0) { printf("种子1:[%d]n", i); //break; } } isSuccess = 0; for (i = 0; i < 99999; i++) { isSuccess = 1; //printf("0x%08xn", i); srand(i); for (j = 0; j < 20; j++) { if (rand() != arrSeed_83690[j]) { isSuccess = 0; break; } } if (isSuccess != 0) { printf("种子2:[%d]n", i); //break; } } system("pause"); return 0;}
#else
//题目程序int main(){ int i = 0; char szBuffer[128] = { 0 }; char szSeed1[6]; unsigned int dwSeed1; char szKCTF[5]; char szSeed2[6]; unsigned int dwSeed2; int isSuccess1; int isSuccess2; int isSuccess3;
//0-99999 //14725KCTF83690 printf("please input :n"); scanf_s("%s", szBuffer, sizeof(szBuffer) - 1); if (my_strlen(szBuffer) != 14) { printf("errorn"); system("pause"); return 0; }
szSeed1[5] = ''; for (i = 0; i < 5; i++) { szSeed1[i] = szBuffer[i + 0]; } dwSeed1 = atoi(szSeed1);
szKCTF[4] = ''; for (i = 0; i < 4; i++) { szKCTF[i] = szBuffer[i + 5]; }
szSeed2[5] = ''; for (i = 0; i < 5; i++) { szSeed2[i] = szBuffer[i + 5 + 4]; } dwSeed2 = atoi(szSeed2); //printf("%dn", dwSeed1); //14725 //printf("%sn", szKCTF); //KCTF //printf("%dn", dwSeed2); //83690
isSuccess1 = 1; isSuccess2 = 1; isSuccess3 = 0; srand(dwSeed1); for (i = 0; i < 20; i++) { if (rand() != arrSeed_14725[i]) { isSuccess1 = 0; break; } } srand(dwSeed2); for (i = 0; i < 20; i++) { if (rand() != arrSeed_83690[i]) { isSuccess1 = 0; break; } } if (szKCTF[0] == 'K' && szKCTF[1] == 'C' && szKCTF[2] == 'T' && szKCTF[3] == 'F') { isSuccess3 = 1; }
if (isSuccess1 != 0 && isSuccess2 != 0 && isSuccess3 != 0) { printf("success : %sn", szBuffer); system("pause"); return 0; } printf("errorn"); system("pause"); return 0;}
#endif


破解思路:
爆破2个5位数随机数种子(0-99999)
种子1+KCTF+种子2即为正确FALG



赛题解析


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


看雪2022 KCTF 秋季赛 | 第六题设计思路及解析
个人论坛主页:https://bbs.pediy.com/user-home-542902.htm

工具:IDA、Python




观察程序运行结果


C:Userssurface>C:UserssurfaceOneDriveCrackCTFKanxue2022KCTFAutumn6CrackMeCrackMe.exeplease input :0123456789error请按任意键继续. . . C:Userssurface>

代码错误时:输出error




IDA反编译伪代码


主程序:

int __cdecl main(int argc, const char **argv, const char **envp){  int preValue; // eax  unsigned int preValueCopy; // ebx  int sufValue; // eax  unsigned int sufValueCopy; // edi  int *v7; // esi  int *v8; // esi  char inputSN[128]; // [esp+4h] [ebp-A0h] BYREF  char sufStr[8]; // [esp+84h] [ebp-20h] BYREF  char preStr[8]; // [esp+8Ch] [ebp-18h] BYREF  int v13; // [esp+94h] [ebp-10h]  int v14; // [esp+98h] [ebp-Ch]  int v15; // [esp+9Ch] [ebp-8h]   memset(inputSN, 0, sizeof(inputSN));  printf("please input :n");  scanf_s("%s", inputSN);  if ( sub_B91000(inputSN) != 0xE )    goto LABEL_19;  preStr[5] = 0;                                // 字符串截断符:只允许5个字节  *(_DWORD *)preStr = *(_DWORD *)inputSN;  preStr[4] = inputSN[4];  preValue = atoi(preStr);  v15 = *(_DWORD *)&inputSN[5];  sufStr[5] = 0;                                // 字符串截断符:只允许5个字节  *(_DWORD *)sufStr = *(_DWORD *)&inputSN[9];  preValueCopy = preValue;  sufStr[4] = inputSN[0xD];  sufValue = atoi(sufStr);  v13 = 0;  sufValueCopy = sufValue;  v14 = 1;                                      // 需保证为1  srand(preValueCopy);  v7 = dword_B9F000;  while ( rand() == *v7 )                       // 依次获取的随机值需与内置全局数组相等  {    if ( (int)++v7 >= (int)dword_B9F050 )      goto LABEL_7;                             // 要跳出来。避开 v14=0  }  v14 = 0;                                      // 执行了这一步就错LABEL_7:  srand(sufValueCopy);  v8 = dword_B9F050;  while ( rand() == *v8 )                       // 依次获取的随机值需与内置全局数组相等  {    if ( (int)++v8 >= (int)&dword_B9F0A0 )      goto LABEL_12;                            // 要跳出来。避开 v14=0  }  v14 = 0;                                      // 执行了这一步就错LABEL_12:  if ( (_BYTE)v15 == 'K' && *(_WORD *)((char *)&v15 + 1) == 'TC' && HIBYTE(v15) == 'F' )// KCTF    v13 = 1;  if ( v14 && v13 )  {    printf("success : %sn", inputSN);    system("pause");  }  else  {LABEL_19:    printf("errorn");    system("pause");  }  return 0;}


初始化随机种子

void __cdecl srand(unsigned int Seed){  *(_DWORD *)(_getptd() + 0x14) = Seed;}


随机函数:

int __cdecl rand(){  int v0; // ecx  unsigned int v1; // eax   v0 = _getptd();  v1 = 0x343FD * *(_DWORD *)(v0 + 0x14) + 0x269EC3;  *(_DWORD *)(v0 + 0x14) = v1;  return HIWORD(v1) & 0x7FFF;}
程序整理的逻辑结构清晰,最终通过 if ( v14 && v13 ) 之后,才判断正确。




整理加密逻辑


1、获取用户字符串

2、检查字符串长度:0xE = 14

  if ( sub_B91000(inputSN) != 0xE )


3、将字符串分为三部分

第一部分:前5个字符,转为整数

preStr[5] = 0;                                // 字符串截断符:只允许5个字节*(_DWORD *)preStr = *(_DWORD *)inputSN;preStr[4] = inputSN[4];preValue = atoi(preStr);


第二部分:KCTF

v15 = *(_DWORD *)&inputSN[5];


第三部分:后5个字符,转为整数

v15 = *(_DWORD *)&inputSN[5];sufStr[5] = 0;                                // 字符串截断符:只允许5个字节*(_DWORD *)sufStr = *(_DWORD *)&inputSN[9];preValueCopy = preValue;sufStr[4] = inputSN[0xD];sufValue = atoi(sufStr);


4、校验

第一部分:用转换后的整数初始化种子,依次获取20个数,与内置全局数组相等

srand(preValueCopy);v7 = dword_B9F000;while ( rand() == *v7 )                       // 依次获取的随机值需与内置全局数组相等{  if ( (int)++v7 >= (int)dword_B9F050 )    goto LABEL_7;                             // 要跳出来。避开 v14=0}v14 = 0;                                      // 执行了这一步就错


第二部分:KCTF

if ( (_BYTE)v15 == 'K' && *(_WORD *)((char *)&v15 + 1) == 'TC' && HIBYTE(v15) == 'F' )// KCTF


第三部分:用转换后的整数初始化种子,依次获取20个数,与内置全局数组相等

srand(sufValueCopy);v8 = dword_B9F050;while ( rand() == *v8 )                       // 依次获取的随机值需与内置全局数组相等{  if ( (int)++v8 >= (int)&dword_B9F0A0 )    goto LABEL_12;                            // 要跳出来。避开 v14=0}v14 = 0;                                      // 执行了这一步就错





编写破解代码


构造序列号为:XXXXXKCTFYYYYY

直接采取爆破的方式,分别获取前后的5个数字字符。

5位数值依次传给种子,逐个生成,并与内置数值比较,成功即记录并退出循环。
'''日期:2022-11-28作者:htg.心学'''###########################################################seed = 0###初始化种子def InitSand(sd):    global seed    seed = sd###获取随机值def GetSand():    global seed    seed = seed * 0x343FD    seed = seed & 0xFFFFFFFF    seed = seed + 0x269EC3    seed = seed & 0xFFFFFFFF    returnValue = seed >> 0x10    returnValue = returnValue & 0x7FFF    return returnValue###两个列表preList = [0x00003BFC,0x00002173,0x000025BB,0x0000380B,0x00002C13,0x000075BE,0x00007366,0x000046A3,0x000013C1,0x0000159B,0x00005B5F,0x0000534F,0x00004E37,0x00003A04,0x00001301,0x00005D0C,0x00004155,0x000048E9,0x000061D2,0x00006158]sufList = [0x00002BB6,0x00006B5A,0x000003D4,0x0000152B,0x00006E04,0x0000254C,0x000040AE,0x000056CA,0x000017E1,0x000055C7,0x00003641,0x00002D3C,0x00000A41,0x00004BC5,0x00006233,0x00001FE7,0x00006E05,0x00000F6E,0x00006398,0x00006AD7]#################################################################三部分:XXXXXKCTFYYYYY##第一部分:XXXXX##第二部分:KCTF##第三部分:YYYYY###########################################################preFound = FalsesufFound = Falseprint("寻找第一部分......")for i in range(100000):    InitSand(i)    preFound = True    for a in preList:        if GetSand() != a:            preFound = False    if preFound:        print("t找到第一部分:{:05}".format(i))        breakif not preFound:    print("Failed!")###########################################################print("寻找第三部分......")for j in range(100000):    InitSand(j)    sufFound = True    for a in sufList:        if GetSand() != a:            sufFound = False    if sufFound:        print("t找到第三部分:{:05}".format(j))        breakif not sufFound:    print("Failed!") SN = "{:05}KCTF{:05}".format(i,j)print("序列号:{}".format(SN))###########################################################

看雪2022 KCTF 秋季赛 | 第六题设计思路及解析
第七题《广厦万间》比赛正在进行

https://ctf.pediy.com/game-season_fight-222.htm

看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

欢迎参与和围观看雪2022 KCTF 秋季赛 | 第六题设计思路及解析



看雪2022 KCTF 秋季赛 | 第六题设计思路及解析
– End –


看雪2022 KCTF 秋季赛 | 第六题设计思路及解析


看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

球分享

看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

球点赞

看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

球在看



看雪2022 KCTF 秋季赛 | 第六题设计思路及解析
“阅读原文查看详情!

原文始发于微信公众号(看雪学苑):看雪2022 KCTF 秋季赛 | 第六题设计思路及解析

版权声明:admin 发表于 2022年11月29日 下午6:02。
转载请注明:看雪2022 KCTF 秋季赛 | 第六题设计思路及解析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...