看雪2022 KCTF 春季赛 | 第二题设计思路及解析(含视频)

资讯 作者:看雪学院 2022-05-14 18:23:35 阅读:380
看雪 2022 KCTF春季赛 于5月10日中午12点正式开赛!第二题《末日邀请》已截止答题,经统计,本题围观人数3473人,共计40支战队成功破解。辣鸡战队战队用时4小时30分55秒拿下第二题的“一血”。

现在第二题《末日邀请》已截止答题,想必不少小伙伴对该赛题本身也充满兴趣,接下来和我一起来看看该赛题的设计思路和相关解析吧~


出题团队简介


第二题《末日邀请》出题方 嵌入式一普度 战队:



赛题设计思路


题目描述

帮小学小朋友看奥数的时候看到的,觉得很有意思就出题了,然后加了一些回忆杀在里面(砍传奇啊)

第一个规律
从任意一个正整数开始,重复对其进行下面的操作:
如果这个数是偶数,把它除以 2 ;
如果这个数是奇数,则把它扩大到原来的 3 倍后再加 1 。
你会发现,序列最终总会变成 4, 2, 1, 4, 2, 1, … 的循环。
第二个规律

用 1 到 9 组成一个九位数,//每个数字都用了一次
使得这个数的第一位能被 1 整除,
前两位组成的两位数能被 2 整除,前三位组成的三位数能被 3 整除,
以此类推,一直到整个九位数能被 9 整除。
381654729 是唯一一个满足要求的数!

题目不难,主要是比较有意思, 投稿看看能不能做签到题,或者第二容易的题目用用。
 
序列号 是 "421KCTF381654729" (中间加入个KCTF 应景)


赛题解析


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


工具:IDA

分析步骤:先简略看看程序运行情况

1、找关键代码

【方法一】
查阅字符串方法,View->Strings,搜索

【方法二】
找 main 方法:
_main   按F5即可转换伪代码

2、显示中文字符串

Options->General->Strings:Default8 8 bits :修改为GBK

如果没有 GBK ,那么点开之后,按住 insert 即可弹出一个新的对话框,直接输入 GBK。

设置完成之后,在 View->Strings 里 右键 Rebuild 对字符串进行重新搜索建立。

如果还是没有,那么关闭IDA,再打开IDA,重新来一遍

3、分析主函数结构


sub_EB100C(
"%s\n 而你,作为一个操控韩立的人,千万不要让韩立 GAME OVER 了.\n现在,输入你的操作ID吧

asc_EB55C0);

sub_EB103A("%s", Arglist);【获取用户输入,此处经过测算,长度<= 0x1E 即 30 位,查看局部变量表】

sub_EB100C("\n现在,你就是韩立,韩立就是你,如遇绝境,吼:男人至死是少年!");

v41 = 0;

lenSN = strlen(Arglist);

SN构造一个 resultA (一个字节)
resultA = 0; if ( lenSN ) {   v7 = Arglist;   do   {     v5 ^= *v7;     --v6;     ++v7;     v8 = 8;     do     {       v9 = 2 * v5;       v10 = v9 ^ 7;       if ( v9 >= 0 )         v10 = v9;       v5 = v10;       --v8;     }     while ( v8 );   }   while ( v6 );   lenSN = lenSNcopy;   resultA = v10;                              // resultA是输出的结果 }

initArrayA();                                 // 构造:dword_EB5B20 数组。这个是全局变量,与用户输入无关

用SN及arrayA:生成一个 signA(一个字),必须等于 0xF52E0765
v11 = -1; for ( i = 0; i < lenSN; ++i )   v11 = arrayA[(unsigned __int8)(v11 ^ Arglist[i])] ^ (v11 >> 8);// 0x0AD1F89A signA = ~v11;                                 // signA == 0xF52E0765

这个之后,Arglist 也就是 SN 是已经处理过了:
  stringToInt((int)Arglist, lenSN);             // 对用户输入的字符串进行转换:将字符转换成数值,'0123456789ABCDEF'-->0123456789ABCDEF

resultA构造一个arrayB
取出 arrayB里三个字节进行或运算得到signB
取出 Arglist 里前三个字节进行异或运算,得到的结果必须等于signB
v13 = resultA; v36 = 1; v35 = resultA + 1; v14 = v35; do {   v15 = v13;   for ( j = 1; j < 0xC8; ++j )   {     if ( (v15 & 1) != 0 )       v15 = 3 * v15 + 1;     else       v15 >>= 1;     arrayB[j] = v15;   }   ++v13; } while ( v13 < v14 );                          // 只循环一遍,构造了v33表 signB = arrayB[0xC6] | arrayB[0xC5] | arrayB[0xC4];// 1^2^4=7 lenSNcopy2 = lenSNcopy; if ( signB != (Arglist[2] ^ Arglist[1] ^ Arglist[0]) )// 第一个判断:前三位异或之后结果为7

计算中间字符数量(其实是尾部),他是 signB + 2

剩余数量为0,题目挖了一个坑
midBytes = signB + 2;restBytes = lenSNcopy2 - midBytes - 7;

四个固定字符:KCTF注意此时的字符,减去了0x37
if ( Arglist[3] != 0x14 )if ( Arglist[4] != 0xC )if ( Arglist[5] != 0x1D )if ( Arglist[6] != 0xF )

按位取出字符,必须被当前的数字位数进行整除,比如123被3整除,12345 被 5 整除,具体多少位数,看后面
v21 = 0; lenSNcopy = 0; if ( midBytes > 0 ) {   v22 = 1;   do   {     v23 = Arglist[v22 + 6] + 10 * v37;        // 十进制算法。十进制数值字符串转换成十进制数值??     v24 = v23 - 0x37373737;                   // 7777     if ( v23 <= 0x4B435445 )                  // KCTE       v24 = v23;     v37 = v24;     if ( v24 % v36 )                          // 不能跳进去:需要被整除。       goto LABEL_50;     v21 = lenSNcopy + 1;     v22 = v36 + 1;     lenSNcopy = v21;     ++v36;   }   while ( v21 < midBytes ); }

对刚才的判断的字符串,进行升序排序
排序结果有参考或要求么?看后面
v25 = midBytes - 1;if ( midBytes - 1 > 0 )                       // 冒泡排序算法:Arglist【7:7+signBsubTwo-1】即对从7开始的signBsubTwo个数进行排序{  v26 = midBytes - 1;  do  {    v27 = 0;    if ( v25 > 0 )    {      do      {        v28 = Arglist[v27 + 7];        v29 = Arglist[v27 + 8];        if ( v28 > v29 )        {          Arglist[v27 + 7] = v29;          Arglist[v27 + 8] = v28;        }        ++v27;      }      while ( v27 < v25 );      v3 = 0;    }    --v25;    --v26;  }  while ( v26 );}

对全局常量进行转换,结果就是 1234567890,后面就不用看了,具体原因看后面
  stringToInt((int)a1234567890Abcd, midBytes);  // 1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ转换成1234567890,0x28,abcdef,0x16,0x17,0x18,......,0x23

这是关键的判断,刚才一大堆的针对中间部分字符串的处理
要保证排序之后的结果与 1234567890 相同
那么最长只有 123456789 ,最短是1
先分析123456789:排序之前,要保证数值的位数 整除 数值,这就要进行爆破分析,详见后面
v30 = 0; if ( midBytes > 0 ) {   while ( a1234567890Abcd[v30] == Arglist[v30 + 7] )// 第六个判断:必须满足   {     if ( ++v30 >= midBytes )                  // 必须跳出去:重复次数是 signBsubTwo       goto LABEL_41;   }   goto LABEL_49; }

这个一个坑,对后面的字符进行处理(如有),这个方法换算也没搞明白sub_EB10E1,感觉就是随便一个算法
Arglist7laterTrans = &Arglist[midBytes + 7];sub_EB10E1((int)Arglist7laterTrans, restBytes);if ( restBytes <= 0 )                         // 第七个判断

这个就有迷惑性了,第一感觉就是,restBytes>0,执行else部分,然后再跳到LABEL_45,好多人掉进去了。

其实:restBytes=0,不用执行else部分,直接进行signA == 0xF52E0765
if ( restBytes <= 0 )                         // 第七个判断  {LABEL_45:    sub_EB100C("\n你想吃猪肉,到猪洞七层准备打个白野猪.白野猪死后尸体尽然掉出一把 屠龙 .");    if ( signA == 0xF52E0765 )                  // 第八个判断   ……………………   }   else  {                                             // 这是一个大坑,没必要进入    while ( ((unsigned __int8)asc_EB55C0[v3] ^ (unsigned __int8)Arglist7laterTrans[v3]) == asc_EB3DC8[v3] )    {      if ( ++v3 >= restBytes )        goto LABEL_45;        ………………    }  }


4、编写Python代码


详见下面内容
import os #从IDA里拷贝出来【如有时间,再考虑具体算法】#初始化大数组def InitArrayA():    #先直接从 IDA 里拷贝。    tmpArray = [ 0x00000000,0x09073096,0x120E612C,0x1B0951BA,0xFF6DC419,0xF66AF48F,0xED63A535,0xE46495A3,0xFEDB8832,0xF7DCB8A4,0xECD5E91E,0xE5D2D988,0x01B64C2B,0x08B17CBD,0x13B82D07,0x1ABF1D91,0xFDB71064,0xF4B020F2,0xEFB97148,0xE6BE41DE,0x02DAD47D,0x0BDDE4EB,0x10D4B551,0x19D385C7,0x036C9856,0x0A6BA8C0,0x1162F97A,0x1865C9EC,0xFC015C4F,0xF5066CD9,0xEE0F3D63,0xE7080DF5,0xFB6E20C8,0xF269105E,0xE96041E4,0xE0677172,0x0403E4D1,0x0D04D447,0x160D85FD,0x1F0AB56B,0x05B5A8FA,0x0CB2986C,0x17BBC9D6,0x1EBCF940,0xFAD86CE3,0xF3DF5C75,0xE8D60DCF,0xE1D13D59,0x06D930AC,0x0FDE003A,0x14D75180,0x1DD06116,0xF9B4F4B5,0xF0B3C423,0xEBBA9599,0xE2BDA50F,0xF802B89E,0xF1058808,0xEA0CD9B2,0xE30BE924,0x076F7C87,0x0E684C11,0x15611DAB,0x1C662D3D,0xF6DC4190,0xFFDB7106,0xE4D220BC,0xEDD5102A,0x09B18589,0x00B6B51F,0x1BBFE4A5,0x12B8D433,0x0807C9A2,0x0100F934,0x1A09A88E,0x130E9818,0xF76A0DBB,0xFE6D3D2D,0xE5646C97,0xEC635C01,0x0B6B51F4,0x026C6162,0x196530D8,0x1062004E,0xF40695ED,0xFD01A57B,0xE608F4C1,0xEF0FC457,0xF5B0D9C6,0xFCB7E950,0xE7BEB8EA,0xEEB9887C,0x0ADD1DDF,0x03DA2D49,0x18D37CF3,0x11D44C65,0x0DB26158,0x04B551CE,0x1FBC0074,0x16BB30E2,0xF2DFA541,0xFBD895D7,0xE0D1C46D,0xE9D6F4FB,0xF369E96A,0xFA6ED9FC,0xE1678846,0xE860B8D0,0x0C042D73,0x05031DE5,0x1E0A4C5F,0x170D7CC9,0xF005713C,0xF90241AA,0xE20B1010,0xEB0C2086,0x0F68B525,0x066F85B3,0x1D66D409,0x1461E49F,0x0EDEF90E,0x07D9C998,0x1CD09822,0x15D7A8B4,0xF1B33D17,0xF8B40D81,0xE3BD5C3B,0xEABA6CAD,0xEDB88320,0xE4BFB3B6,0xFFB6E20C,0xF6B1D29A,0x12D54739,0x1BD277AF,0x00DB2615,0x09DC1683,0x13630B12,0x1A643B84,0x016D6A3E,0x086A5AA8,0xEC0ECF0B,0xE509FF9D,0xFE00AE27,0xF7079EB1,0x100F9344,0x1908A3D2,0x0201F268,0x0B06C2FE,0xEF62575D,0xE66567CB,0xFD6C3671,0xF46B06E7,0xEED41B76,0xE7D32BE0,0xFCDA7A5A,0xF5DD4ACC,0x11B9DF6F,0x18BEEFF9,0x03B7BE43,0x0AB08ED5,0x16D6A3E8,0x1FD1937E,0x04D8C2C4,0x0DDFF252,0xE9BB67F1,0xE0BC5767,0xFBB506DD,0xF2B2364B,0xE80D2BDA,0xE10A1B4C,0xFA034AF6,0xF3047A60,0x1760EFC3,0x1E67DF55,0x056E8EEF,0x0C69BE79,0xEB61B38C,0xE266831A,0xF96FD2A0,0xF068E236,0x140C7795,0x1D0B4703,0x060216B9,0x0F05262F,0x15BA3BBE,0x1CBD0B28,0x07B45A92,0x0EB36A04,0xEAD7FFA7,0xE3D0CF31,0xF8D99E8B,0xF1DEAE1D,0x1B64C2B0,0x1263F226,0x096AA39C,0x006D930A,0xE40906A9,0xED0E363F,0xF6076785,0xFF005713,0xE5BF4A82,0xECB87A14,0xF7B12BAE,0xFEB61B38,0x1AD28E9B,0x13D5BE0D,0x08DCEFB7,0x01DBDF21,0xE6D3D2D4,0xEFD4E242,0xF4DDB3F8,0xFDDA836E,0x19BE16CD,0x10B9265B,0x0BB077E1,0x02B74777,0x18085AE6,0x110F6A70,0x0A063BCA,0x03010B5C,0xE7659EFF,0xEE62AE69,0xF56BFFD3,0xFC6CCF45,0xE00AE278,0xE90DD2EE,0xF2048354,0xFB03B3C2,0x1F672661,0x166016F7,0x0D69474D,0x046E77DB,0x1ED16A4A,0x17D65ADC,0x0CDF0B66,0x05D83BF0,0xE1BCAE53,0xE8BB9EC5,0xF3B2CF7F,0xFAB5FFE9,0x1DBDF21C,0x14BAC28A,0x0FB39330,0x06B4A3A6,0xE2D03605,0xEBD70693,0xF0DE5729,0xF9D967BF,0xE3667A2E,0xEA614AB8,0xF1681B02,0xF86F2B94,0x1C0BBE37,0x150C8EA1,0x0E05DF1B,0x0702EF8D ]    return tmpArray#根据输入的SN,获取resultAdef CalcResultA(SN):    initByte = 0x00    tmpByteA = 0x00    tmpByteB = 0x00    for ch in SN:        #print("ch:{}".format(ch))        initByte ^= ord(ch)        for i in range(8):            tmpByteA = (2 * initByte) & 0xFF            #print("tmpByteA:{}".format(hex(tmpByteA)[2:]))            tmpByteB = tmpByteA ^ 7            if tmpByteA < 0x80 :   #对应于 正数                tmpByteB = tmpByteA            initByte = tmpByteB        #print("tmpByteB:{}".format(hex(tmpByteB)[2:]))    return tmpByteB #对输入的字符进行处理:'0'->0   '9'--->9   'a'---->10 ....def String2IntList(SN):    returnList =[]    for ch in SN:        tmpBase = 0x30        if ord(ch)>=0x3A:            tmpBase = 0x37        returnList.append(ord(ch)-tmpBase)    return returnList #搜索函数def DoSearch(SN):    global arrayA    #print("当前的SN:{}\n 长度:{}".format(SN,len(SN)))    #os.system("pause")    #TODO:已经构造完成:开始进行判断    resultA = CalcResultA(SN)    #print("resultA:{}".format(hex(resultA)))    signA = 0xFFFFFFFF    lenSN = len(SN)    for ch in SN:        tmpIndex = (signA ^ ord(ch)) & 0xFF        tmpValue = arrayA[tmpIndex]        #print("tmpIndex:{}".format(hex(tmpIndex)))        #SAR算数移位        if signA >= 0x80000000:            #负数,右移之后,要在最高位带上FF            signA = ((signA >> 8)|0xFF000000) ^ tmpValue        else:            signA = (signA >> 8) ^ tmpValue        #print("signA:{}".format(hex(signA)))    signA = signA ^ 0xFFFFFFFF #取反 就是 异或 全1     if signA != 0xF52E0765:        #print("signA: {} != 0xF52E0765".format(hex(signA)))        #os.system("pause")        return None     #输出一个结果:      print("找到SN:{}".format(SN))    os.system("pause") #主函数入口def main():    #序列号    SN = ""    #分析算法结构    '''    01  3个字符    02  4个固定字符,即为 KCTF    03  不大于9个字符,且必须为数值1-9    04  没有字符,题目埋了一个坑    '''    #第3部分:9个字符。    part3Code = "381654729"    for a in usedList:        for b in usedList:            for c in usedList:                SN = chr(a) + chr(b) + chr(c)                #对SN:前3个字符进行计算,以确保其相互异或,结果为 7                tmpIntLst = String2IntList(SN)                if tmpIntLst[0] ^ tmpIntLst[1] ^ tmpIntLst[2] != 0x7:                    continue                #print("找到SN:{}".format(SN))                #前单个字符要进行sign换算                SN = SN +'KCTF' + part3Code                #调用另外一个命令,继续构造                DoSearch(SN) if __name__ == "__main__":    global arrayA    arrayA=[]    usedList = list(range(0x21,0x7E,1))    print("usedCharList:\n{}".format([hex(x)[2:].upper() for x in usedList]))    arrayA = InitArrayA()    #main()    '''    以下是辅助代码    constStr = '1234567890_ABCDEFGHIJKLMNOPQRSTUVWXYZ'    constIntList = String2IntList(constStr)    print("constIntList")    print(','.join(hex(x)[2:] for x in constIntList))     print('测试最后的部分')    asc_EB55C0 = '我,韩立,作为一名光荣的程序员,月亮不睡我不睡,太阳没起我就起,夜以继日,终于把肉身修炼腐朽了...然后,穿越了...'    asc_EB3DC8 = '我辈读书人一生所求不过四事:为天地立心,为生民立命,为往圣继绝学,为万世开太平.'     asc_EB55C0_to_gbk = asc_EB55C0.encode("gbk")    asc_EB3DC8_to_gbk = asc_EB3DC8.encode("gbk")     returnRestList = []    for i in range(20):        returnRestList.append(asc_EB55C0_to_gbk[i]^asc_EB3DC8_to_gbk[i])    print("returnRestList")    print(','.join(hex(x)[2:] for x in returnRestList))    #0,0,9d,8,1d,0,68,c5,1f,3c,1c,11,1b,41,8,2,7e,11,7a,62    '''

找9位数值代码:
###找到满足要求的数值:9import itertoolsfrom itertools import permutations'''num_list = [1,2,3,4,5,6,7,8,9]num_list = [1,2,3,4,5,6,7,8]num_list = [1,2,3,4,5,6,7] #nonenum_list = [1,2,3,4,5,6]num_list = [1,2,3,4,5] #nonenum_list = [1,2,3,4]#nonenum_list = [1,2,3]num_list = [1,2]num_list = [1]'''def permute(nums):    result = []    for i in permutations(nums,len(nums)):        #print(list(i))        tmp = "".join(str(x) for x in list(i))        #print("tmp:{}".format(tmp))        result.append(tmp)     return result  def main():    #print('\n')    #print(permute(num_list))    for i in range(9):        num_list = []        ##生成 num_list        for j in range(i+1):            num_list.append(j+1)        print("#"*50+"\n"+"num_list:{}".format(num_list))        ##num_list 生成 permute(num_list) 用作候选               lst = permute(num_list)        ##开始查找        cur = len(num_list)        for k in lst:            for m in range(cur):                n = m + 1                x = int(k[:n])                if x % n != 0:                    break                if n == cur:                    print("found:\t{}".format(k)) if __name__ == "__main__":    print("开始查找")    main()       print("#"*50)'''found:381654729found:38165472found:123654found:321654found:123found:321found:12found:1'''


第三题《石像病毒》还在火热进行中,

在线申请SSL证书行业最低 =>立即申请

[广告]赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

#
公众号 关注KnowSafe微信公众号
随时掌握互联网精彩
赞助链接