查看原文
其他

看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析

看雪CTF 看雪学院 2019-05-27

新的一周在高温中开始了...

经过一周的奋战,来看看大家的战绩

第八题出题者以被5人攻破的成绩,位列第一位



本题过后 风间仁冲向首位,acdxvfscd 上升2位至第三名。

选手 新手慢慢来 冲进前十名。

风间仁曾在看雪2017 CTF 年中赛中取得第一名

此次究竟能否继续卫冕冠军呢?

让我们拭目以待!



看雪版主&评委 netwind 点评


本题主要考察算法的逆向识别求解能力,用到了汉诺塔求解,费马大定理,大数二元一次方程,crc校验等算法,逆向识别出算法模型后便可求解。


看雪.京东 2018 CTF 第八题 作者简介


看场雪

bbs.pediy.com/user-187526

第八题出题者简介:


看雪ID:瞧红尘           

性格随性+理想主义,  爱好哲学,物理,计算机,做梦,修仙



看雪.京东 2018 CTF 第八题 设计思路


1、汉诺塔求解

最小步数,求解法(这里选5层)


A:16 + 8 + 4 + 2 + 1 = 31

B:0

C:0

每次移动一个数,1,2,4,8,16,如A-1  B+1就相当移动最小的一块到B,他只有6种移动方式

即:A->B A->C B->A B->C C->A C->B,分别对应 0,1,2,3,4,5


解为1,0,5,1,2,3,1,0,5,4,2,5,1,0,5,1,2,3,1,2,5,4,2,3,1,0,5,1,2,3,1 (6进制)

(36进制:BLUB36BLUN3UBLUB)

(16进制:3dd7c4ddec9ae7c5e8c1)

解最少次数也是31次


正确结果

A:0

B:0

C:31


判断最后A = 0, B = 0 (必然 C = 31)  

执行了31步,避免多解

输入36进制,避免被一眼看出是6进制,汉诺塔移动。

避免多解需要对输入进行长度判断


注:汉诺塔多步会出现多解.(事先我并不知道有多解)


2、费马大定理

是无整数解的,利用/0异常跳出


用上面的B作为分母,正常的时候B=0,必然会产生异常

x ^ n + y^ n = z^ n (n>2,x,y>0)  无整数解(费马大定理)


代码:

try
     {
       for(int i = 3 ; i < 10; i++)
       {
         if((*x ^ i) + (*y ^ i) == (*z ^ i)){  //费马大定理(无整数解) x>0,y>0
           lastB = 0;//诱惑分析
           break;
         }
         
         if(*x == (*x / lastB))//产生异常
           kTmp2++;
       }
       if(kTmp2 > 10)//不可能
         lastB = 0;
     }
     catch (...)
     {
       isFlag = 2;
       //printf("异常=======================================\n");
     }


3、对输入的数据进行计算CRC32校验

反校验需要4字节连续空隙即可伪造任意crc,这步需要最后去做,但是他的判断在解方程前面

if(b2 && !(*x==0) && !(*y==0) & !crc ) //正确结果是 true , lastB = 0 ,lastMov = 1

{ //默认crc的值为任意,不要开始考虑

//解方程后,最后修复crc

}


4、大数解二元一次方程


(如果能解出费马大定理,这个方程组就不用解了)

x - y == "Author by Lookhc" (64进制)

x * 2 + y * 4 == "welcome to bbs.pediy.com." (64进制)


相当于:

x - y =13479427470606219437251685094 (10进制)

2 * x + 4 * y=1307640379757893473170432799524731441641106494 (10进制)

先把这个转换成10进制用数学工具/笔算也不难

x =217940063292982254514690446991601531774641145 (10)

y =217940063292982241035262976385382094522956051 (10)

x = 09c5d44875c2a969b2d8d6e76abd1b81a123f9 (16)

y = 09c5d44875c2a93e24ed0b8784c9260ed63913 (16)


5、动态长度

输入数据的结构

byte len1; //汉诺塔解的长度 =10

byte len2; //汉诺塔解的长度 + 4 == z的长度 =14

byte len3; //x长度 =19

byte len4; //y长度 =19


Hanbuff[] ;       //汉诺塔的解

crc[4] //crc填充位置

x[] //x

y[] //y

注:Hanbuff[] + crc[4] ==> z

09c5d44875c2a93e24ed0b8784c9260ed63913 09c5d44875c2a969b2d8d6e76abd1b81a123f9 00000000 3dd7c4ddec9ae7c5e8c1 13130e0a

09c5d44875c2a93e24ed0b8784c9260ed6391309c5d44875c2a969b2d8d6e76abd1b81a123f9000000003dd7c4ddec9ae7c5e8c113130e0a

09c5d44875c2a93e24ed0b8784c9260ed6391309c5d44875c2a969b2d8d6e76abd1b81a123f92c2f1ca73dd7c4ddec9ae7c5e8c113130e0a

转62进制

6OqTbC16uYclIp3aqSoJuSpYO4I9JodXMs1oaaI40wwzue79rqVxXflyoZeLxs3Q1yxO1yUoz06 (62进制)


6、crc对抗分析

对代码区域进行crc计算,用值当成函数调用地址。如果代码被修改,则地址会变换。


crc=crc32(代码区域.....)
 if(crc)
 {
   call crc  //函数调用
 }
 else
 {
   返回 crc32(代码区域,参数) //返回常数
 }

 int x = 200;
 int y = 300;
 int z = fun(x,y);

 //机密后的格式
 x = testCRC(.....)
 y = testCRC(.....)
 z = testCRC(.....)


函数、常数,都无法直接通过代码直观看出来(爆破,F2断,ida分析)



破解思路


a.先确定函数的真正调用地址和参数,OD跟踪(不要下F2断)即可得出,然后再给IDA分析,函数都没加花指令。


b.需要理解的是这里的对象是 任意进制的 大数的 加减乘除/进制转换,这题已经解决了一半

  1.输入的62进制转256进制(16进制)

  2.汉诺塔的6进制(6种移动方式),36进制的转换

  3.64进制二元一次方程

    64进制的费马大定理


c.知道crc32校验可以伪造,填充连续4字节,可以构造任意CRC32值

  需要在最后的阶段用CRC32逆向算法对齐


注意:

1、费马大定理是无解的。

2、CRC32需要最后再填充(加密的时候也是最后填充),虽然解方程需要在最后。但填充需要在最后,这里需要先直接跳过crc值的判断校验时候的顺序,因为输入的和内存中校验的顺序不一定一致。

3、汉诺塔不是判断C=31,而是判断A=0,B=0,

      B=0不直接当成结论,而是当成下一个判断依据的条件。




看雪.京东 2018 CTF 第八题解析


*本解析来自看雪论坛 acdxvfsvd



首先拿到程序,发现程序非常的复杂,IDA反编译代码中存在很多函数传进去了63个参数,猜测应该是将一个很大的对象直接传了进去。

 

首先发现程序里有奇怪的一个handler:


v11 = a1 ^ 0x1F2E3D00;
 if ( a3 == 523124224 )
   return a2 ^ 0x1F2E3D00;
 v13 = a4;
 v14 = a4;
 if ( v11 )
 {
   v13 = (int (__stdcall *)(int))hash((int)a4, v11 - (_DWORD)a4, 0);
   v14 = v13;
 }
 if ( !v13 )
   return hash((int)a4, v11 - (_DWORD)a4, dword_40F1B8[(unsigned __int8)a3]);
 switch ( (unsigned __int8)(a3 ^ dword_40F3B8[((unsigned int)v13 >> 8) & 0xF]) )
 {
   case 2u:
     result = v14(a5);
     break;
   case 3u:
     result = ((int (__stdcall *)(int, int))v14)(a5, a6);
     break;
   case 4u:
     result = ((int (__stdcall *)(int, int, int))v14)(a5, a6, a7);
     break;
   case 5u:
     result = ((int (__stdcall *)(int, int, int, int))v14)(a5, a6, a7, a8);
     break;
   case 6u:
     result = ((int (__stdcall *)(int, int, int, int, int))v14)(a5, a6, a7, a8, a9);
     break;
   case 7u:
     result = ((int (__stdcall *)(int, int, int, int, int, int))v14)(a5, a6, a7, a8, a9, a10);
     break;
   case 8u:
     result = ((int (__stdcall *)(int, int, int, int, int, int, int))v14)(a5, a6, a7, a8, a9, a10, a11);
     break;
   default:
     result = ((int (*)(void))v14)();
     break;
 }
 return result;
}


将第一个参数先与固定值异或,作为起始地址,然后带着起始地址和长度进入一个哈希函数,计算出哈希值后跳转过去。这里可以发现,被哈希的字节在代码段中,所以用0xCC做软件断点的调试方法在这里行不通,需要使用硬件断点来将程序断下。当然,也可以人工计算出哈希值,然后patch掉这个函数,用正确的函数替换。

 

经过很多次的调试,发现程序中操作的那个巨大的对象,实际上是一个任意进制大整数的对象。定义大概是这样:

__int32 base;                // 进制
__int32 is_invalid;          // 是否正常,是则0
__int32 length;              // 长度
__int8  is_negative;         // 是否是负数,是则1
__int8  buf[0x200];          // 内容


一段一段地看程序:

v155 = &v111;
 system(aCls);
 v3 = 0;
 v135 = 0;
 memset(&v136, 0, 0x60u);
 v137 = 0;
 v138 = 0;
 printf((int)&unk_40F170, (int)s1);
 fgets(&v135, 100, &stru_40F418);
 v4 = strlen(&v135);
 if ( v4 > 0 )
 {
   v5 = *(&v134 + v4);
   v6 = &v134 + v4;
   if ( v5 == 10 )
     *v6 = 0;
 }
 result = handler(
            0x1F6E1EC3,
            0x4A4EBD2C,
            0x6787,
            (int (__stdcall *)(int))byte_40143C,
            (int)&v135,
            strlen(&v135),
            v111,
            v112,
            v113,
            v114.base,
            v114.invalid);  // 4013c0 -> 是否是字母数字
 if ( !result )
   return result;
 v8 = (num *)operator new(0x210u);
 if ( v8 )
 {
   LOBYTE(v8->invalid) = 0;
   v8->base = 256;
   v8->is_negative = 0;
   v8->length = 0;
   v3 = v8;
   memset(v8->num_buf, 0, 0x200u);
 }
 v147 = (int)v3;
 result = (unsigned __int8)init_num_by_str(
                             v3,
                             &v135,
                             strlen(&v135),
                             strlen(a0123456789abcd),
                             (int)a0123456789abcd,
                             45,
                             43);
 v154 = (num *)result;


读入最长100字节的Flag,判断是否为字母数字,然后以01234……ABCD……abcd……为字符集,将Flag作为62进制数储存。
LOBYTE(result) = result != 0;
 if ( !(_BYTE)result )
   return result;
 v9 = v3->base;
 if ( v3->base < 2u || v9 > 0x100 )
   return result;
 v10 = v3->length;
 for ( result = 0; result < v10; ++result )
 {
   if ( (unsigned __int8)v3->num_buf[result] >= v9 )
     return result;
 }
 v11 = (num *)handler(
                0x1F6E1EA0,
                -655007228,
                2,
                (int (__stdcall *)(int))&loc_40158E,
                v111,
                v112,
                v113,
                v114.base,
                v114.invalid,
                v114.length,
                *(int *)&v114.is_negative); // 返回0x100
 v12 = v3->base;
 v13 = v3->base == (_DWORD)v11;
 v148 = v11;
 if ( !v13 )
 {
   v149 = v3->is_negative;
   a3 = 1;
   init_num_by_buf(&v131, v12, &a3, 1u, 0);
   init_num_by_buf(&v129, (int)v11, &a3, 1u, 0);
   v14 = (int)&v11->base + 1;
   v15 = (num *)operator new(528 * v14);
   lpMem = v15;
   v156 = 0;
   if ( v15 )
   {
     v151 = v15;
     if ( v14 - 1 >= 0 )
     {
       v154 = (num *)v14;
       do
       {
         new_empty_num(v151);
         v13 = v154 == (num *)1;
         v151 = (num *)((char *)v151 + 528);
         v154 = (num *)((char *)v154 - 1);
       }
       while ( !v13 );
     }
   }
   else
   {
     v15 = 0;
   }
   v152 = v15;
   v156 = -1;
   v16 = (num *)operator new(528 * v14);
   v154 = v16;
   v156 = 1;
   if ( v16 )
   {
     loop_function_call((int)v16, 528, v14, (int (__thiscall *)(int))new_empty_num);
     v17 = v154;
   }
   else
   {
     v17 = 0;
   }
   v18 = v148;
   v15->base = v3->base;
   v15->is_negative = 0;
   lpMem = v17;
   v156 = -1;
   v17->base = (int)v18;
   v17->is_negative = 0;
   if ( v14 > 1 )
   {
     v151 = (num *)((char *)v15 + 528);
     v19 = v17;
     v144 = (char *)((char *)v15 - (char *)v17);
     v154 = (num *)(v14 - 1);
     do
     {
       qmemcpy(&v110, &v131, 0x210u);
       v20 = add_num((num *)((char *)v19 + (_DWORD)v144), &a2, v110);
       qmemcpy(v151, v20, 0x210u);
       v142 = (num *)((char *)v19 + 528);
       qmemcpy(&v108, &v129, 0x210u);
       v21 = add_num(v19, &a6, v108);
       v19 = v142;
       v22 = v21;
       v23 = v154;
       qmemcpy(v142, v22, 0x210u);
       v151 = (num *)((char *)v151 + 528);
       v154 = (num *)((char *)v23 - 1);
     }
     while ( v23 != (num *)1 );
   }
   v24 = v147;
   qmemcpy(&a1, (const void *)v147, 0x210u);

这个神奇的handler居然还有返回某个数的功能……这里返回的是0x100。
这里的作用是在内存中生成了一个1-256的表,存1-256这些数字作为256进制大整数。

new_empty_basen_num(&v132, (int)v148, 0);
   qmemcpy(&v107, v152, 0x210u);
   v144 = 0;
   if ( cmp_num(&a1, v107) )
   {
     v154 = (num *)((char *)v152 + 528 * (_DWORD)v148);
     do
     {
       qmemcpy(&v106, v154, 0x210u);
       mod_num(&a1, v24, (int)&savedregs, (int)&v109, (int)&v154[1].num_buf[246], (int)&v127, v106);
       v24 = 0;
       if ( v148 )
       {
         v151 = v152;
         while ( 1 )
         {
           qmemcpy(&v104, v151, 0x210u);
           if ( !cmp_num(&v127, v104) )
             break;
           ++v24;
           v151 = (num *)((char *)v151 + 528);
           if ( v24 >= (unsigned int)v148 )
             goto LABEL_34;
         }
         qmemcpy(&v126, (char *)lpMem + 528 * v24, 0x210u);
         qmemcpy(&v103, &v127, 0x210u);
         qmemcpy(&a1, minus_num(&a1, &a2, v103), 0x210u);
         qmemcpy(&v102, v154, 0x210u);
         v101 = &a6;
         qmemcpy(
           &a1,
           (const void *)div_num(&a1, v24, (int)&savedregs, (int)&v105, (int)&v154[1].num_buf[246], *(num *)&v101),
           0x210u);
         v25 = v144;
         sub_402B40((int)&v126, (signed int)v144);
         v144 = v25 + 1;
         qmemcpy(&v91, &v126, 0x210u);
         qmemcpy(&v132, add_num(&v132, &v124, v91), 0x210u);
       }
LABEL_34:
       qmemcpy(&v90, v152, 0x210u);
     }
     while ( cmp_num(&a1, v90) );
     v24 = v147;
   }
   v26 = v149;
   qmemcpy((void *)v24, &v132, 0x210u);
   *(_BYTE *)(v24 + 12) = v26;
 }

这里标完函数之后,就显得很清晰,经典的进制转换结构,把62进制的Flag转换为256进制bytes。(话说不是有个进制转换的函数吗,为啥不用那个= = )
v27 = (unsigned __int8 *)operator new(0x104u);
memset(v27, 0, 0x104u);
v145 = v27;
result = *(_DWORD *)(v147 + 8);
qmemcpy(v27, (const void *)(v147 + 13), result);
LOBYTE(result) = *v27;
if ( *v27 < 1u )
  return result;
if ( (unsigned __int8)result > 0x18u )
  return result;
LOBYTE(v154) = v27[1];
if ( (unsigned __int8)v154 < 1u )
  return result;
if ( (unsigned __int8)v154 > 0x18u )
  return result;
v28 = v27[2];
if ( v28 < 1u )
  return result;
if ( v28 > 0x18u )
  return result;
v29 = v27[3];
if ( v29 < 1u )
  return result;
if ( v29 > 0x18u )
  return result;
result = (unsigned __int8)result;
if ( (unsigned __int8)result + 4 != (unsigned __int8)v154 )
  return result;
result += v27[2] + v27[3] + 8;
if ( result != *(_DWORD *)(v147 + 8) )
  return result;

这里就开始check了,读出前四个字节,判断范围在(1, 0x18),并且bytes[0] + 4 == bytes[1], bytes[1] + bytes[2] + bytes[3] + 4 == 总长度。
v30 = handler(527310496, 882113057, 0, (int (__stdcall *)(int))&loc_401662, v94, v95, v96, v97, v98, v99, v100); // 返回 0x400
 v144 = (char *)operator new(v30);
 v31 = (num *)operator new(0x210u);
 v32 = v31;
 v154 = v31;
 v156 = 2;
 if ( v31 )
 {
   v33 = *v145;
   v31->base = (int)v148;
   LOBYTE(v31->invalid) = 0;
   v31->length = v33;
   v31->is_negative = 0;
   memset(v31->num_buf, 0, 0x200u);
   qmemcpy(v31->num_buf, v145 + 4, v33);
   if ( !check_num(v31) )
     LOBYTE(v32->invalid) = 1;
   lpMem = v32;
 }
 else
 {
   lpMem = 0;
 }
 v93 = 528;
 v156 = -1;
 v152 = (num *)*v145;
 v34 = (num *)operator new(0x210u);
 v35 = v34;
 v154 = v34;
 v156 = 3;
 if ( v34 )
 {
   v36 = v145[1];
   v34->base = (int)v148;
   LOBYTE(v34->invalid) = 0;
   v34->length = v36;
   v34->is_negative = 0;
   memset(v34->num_buf, 0, 0x200u);
   qmemcpy(v34->num_buf, v145 + 4, v36);
   if ( !check_num(v34) )
     LOBYTE(v35->invalid) = 1;
   v151 = v35;
 }
 else
 {
   v151 = 0;
 }
 v156 = -1;
 v152 = (num *)((char *)v152 + 4);
 v37 = (num *)operator new(0x210u);
 v154 = v37;
 v156 = 4;
 if ( v37 )
 {
   v38 = v145[2];
   v37->base = (int)v148;
   LOBYTE(v37->invalid) = 0;
   v37->length = v38;
   v37->is_negative = 0;
   memset(v37->num_buf, 0, 0x200u);
   qmemcpy(v37->num_buf, &v145[(_DWORD)v152 + 4], v38);
   if ( !check_num(v37) )
     LOBYTE(v37->invalid) = 1;
   v154 = v37;
 }
 else
 {
   v154 = 0;
 }
 v39 = v145[2];
 v156 = -1;
 v142 = v154;
 v152 = (num *)((char *)v152 + v39);
 v40 = (num *)operator new(0x210u);
 v139 = v40;
 v156 = 5;
 if ( v40 )
 {
   v41 = v145[3];
   v40->base = (int)v148;
   LOBYTE(v40->invalid) = 0;
   v40->length = v41;
   v40->is_negative = 0;
   memset(v40->num_buf, 0, 0x200u);
   qmemcpy(v40->num_buf, &v145[(_DWORD)v152 + 4], v41);
   if ( !check_num(v40) )
     LOBYTE(v40->invalid) = 1;
   v148 = v40;
 }
 else
 {
   v148 = 0;
 }
 v152 = v148;
 v156 = -1;

这里是按顺序读4段并放到4个大整数对象中。稍微整理一下可以得到这样的结构:(按实际数字顺序)
=========================================================
|   d   |    c    | (4) |   a   |len_d|len_c|len_b|len_a|
=========================================================

其中a和前面4字节合起来作为b。
init_num_by_str(&num1, s1, strlen(s1), strlen(a0123456789abcd_0), (int)a0123456789abcd_0, 45, 43);
 init_num_by_str(&num2, ::a2, strlen(::a2), strlen(a0123456789abcd_0), (int)a0123456789abcd_0, 45, 43);
 v42 = 0;
 v43 = 0;
 while ( v43 >= 35 )
 {
LABEL_72:
   if ( ++v43 >= 36 )
   {
     if ( *((_BYTE *)lpMem + 12) )
     {
       v42 = 1;
       *v144 = 45;
     }
     qmemcpy(&v132, lpMem, 0x210u);
     change_base(&v132, 36);
     v45 = v132.length - 1;
     for ( i = v144; v45 >= 0; i[v42 - 1] = byte_40F0B0[v47] )
     {
       ++v42;
       v47 = (unsigned __int8)v132.num_buf[v45--];
     }
     i[v42] = 0;
     goto LABEL_78;
   }
 }
 v44 = &aBcdefghijklmno[v43];
 while ( byte_40F0B0[v43] != *v44 )
 {
   if ( (signed int)++v44 >= 0x40F0D4 )
     goto LABEL_72;
 }

载了固定的两个字符串成64进制大整数,然后将之前的a转换成36进制bytes。
v143 = 100;
 v141 = 0;
 v48 = sub_4010A0((int)v144, strlen(v144), 5, &v143, (unsigned int *)&v141);
 v93 = v141 - 1;
 v92 = *(_DWORD *)(v147 + 8);
 lpMem = (LPVOID)handler(
                   0x1F6E1EA0,
                   0x13114F0D,
                   0x6780,
                   (int (__stdcall *)(int))&loc_401ABF,
                   (int)v145,
                   v92,
                   v141 - 1,
                   v94,
                   v95,
                   v96,
                   v97);   // -> hash

这里这两个函数比较重要。将a转成的36进制bytes传给4010A0函数,算一下,然后再算一下整个256进制bytes的哈希。
4010A0函数:

v5 = 0;
 a = (unsigned int *)a4;
 *a5 = 0;
 v7 = __5 - 1;
 *a4 = 0;
 b = 0;
 c = 0;
 if ( __5 - 1 > 0 )
 {
   do
   {
     --v7;
     *a = 2 * (*a | 1);
   }
   while ( v7 );
 }
 v28 = 0;
 v8 = *a | 1;
 v30 = 0;
 *a = v8;
 v9 = v8;
 v10 = len;
 v27 = v9;                                     // 0x1f
 if ( len <= 0 )
   return 0;
 v26 = len;
 while ( 1 )
 {
   v11 = *(_BYTE *)(v5 + buf);
   if ( v11 >= 0x41u && v11 <= 0x5Au )
   {
     v12 = v11 - 65;
     goto LABEL_14;
   }
   if ( v11 >= 0x61u && v11 <= 0x7Au )
   {
     v12 = v11 - 97;
     goto LABEL_14;
   }
   if ( v11 >= 0x30u && v11 <= 0x39u )
     break;
LABEL_42:
   v30 = ++v5;
   --v26;
   if ( v5 >= v10 )
     return 0;
 }
 v12 = v11 - 22;
LABEL_14:
 v13 = 0;
 v25 = v12;
 v29 = 0;
 while ( 1 )
 {
   v14 = v25 % 6;
   v31 = v25 % 6;
   v15 = ((unsigned int)v25 - v14) / 6;
   v25 = v15;
   if ( !v13 )
     v28 = (unsigned __int8)v15 > 0u;
   v16 = sub_401080(*a);                       // 返回最大的2的整数次方的因子
   v17 = sub_401080(b);
   v18 = sub_401080(c);
   x1 = v16;
   x2 = v17;
   x3 = v18;
   if ( !v16 )
   {
     v14 = v31;
     x1 = 100;
   }
   if ( !v17 )
     x2 = 100;
   if ( !v18 )
     x3 = 100;
   switch ( v14 )
   {
     case 0:
       if ( x1 >= x2 )
         return 0;
       *a -= v16;
       b += v16;
       *a5 = v16;
       break;
     case 1:
       if ( x1 >= x3 )
         return 0;
       c += v16;
       *a -= v16;
       *a5 = v16;
       break;
     case 2:
       if ( x2 >= x1 )
         return 0;
       b -= v17;
       *a += v17;
       *a5 = v17;
       break;
     case 3:
       if ( x2 >= x3 )
         return 0;
       b -= v17;
       c += v17;
       *a5 = v17;
       break;
     case 4:
       if ( x3 >= x1 )
         return 0;
       *a += v18;
       c -= v18;
       *a5 = v18;
       break;
     case 5:
       if ( x3 >= x2 )
         return 0;
       c -= v18;
       b += v18;
       *a5 = v18;
       break;
     default:
       break;
   }
   if ( !--v27 && !*a && v26 == 1 && !v28 )
     return 1;
   v13 = v29 + 1;
   v22 = __OFSUB__(v29 + 1, 2);
   v21 = v29++ - 1 < 0;
   if ( !(v21 ^ v22) )
   {
     v10 = len;
     v5 = v30;
     goto LABEL_42;
   }
 }
}


这个函数我懵逼了很久……一开始将36进制字节转回数字,然后拆成低位和高位,分别扔进那个case里面去做操作……验证要0x1F次操作全部成功完成,v6的值变为0. 这个东西,实际上是一个汉诺塔小游戏。

 

a b c分别为3根柱子,用二进制位表示每一个权值的盘子(所以取二进制最后一个1,实际上是取到最上的一块盘子),权值小的放在权值大的上面,0x1F刚好是(11111),即5个盘子权值分别为16 8 4 2 1,最少的移动次序正好是31次。用0-5表示6种移动,两次合并为一个36进制字节。

 

手动玩一玩,得到一个序列:

[1, 0, 5, 1, 2, 3, 1, 0, 5, 4, 2, 5, 1, 0, 5, 1, 2, 3, 1, 2, 5, 4, 2, 3, 1, 0, 5, 1 ,2 ,3, 1]


// 这里似乎存在多解……没有check最后放在第2个还是第3个上
合并相邻的两个操作:(最后多余的一个必须是0)

[1, 11, 20, 1, 29, 32, 1, 11, 20, 13, 29, 20, 1, 11, 20, 1]
转为36进制bytes:

blub36blun3ublub
// 怎么看上去这么正常……怕不是这本来是单独的一道题……
转为数字:

3dd7c4ddec9ae7c5e8c1

这个就是a。

 

接下来是哈希函数,但是到这里的时候信息明显不足,这里选择暂且patch程序跳过这个check,继续向下分析。


if ( !v48 )
   goto GG;
 init_num_by_int(&v129, 0);
 qmemcpy(&v87, &v129, 0x210u);
 if ( !cmp_num(v154, v87) )
   goto GG;
 init_num_by_int(&v129, 0);
 qmemcpy(&v86, &v129, 0x210u);
 v49 = cmp_num(v148, v86);
 if ( v49 == 0 || lpMem != 0 )
   goto GG;
 lpMem = (LPVOID)1;
 v154 = 0;
 if ( v141 != 1 )
   goto GG;
 v50 = 3;
 v156 = 6;
 v147 = 3;
 while ( v50 < 10 )
 {
   if ( !v50 )
   {
     v89 = 0;
     v88 = 1;
     v51 = v151->base;
     v149 = 1;
     init_num_by_buf(&v129, v51, &v149, 1u, 0);
     qmemcpy(&v130, &v129, 0x210u);
LABEL_90:
     v89 = 0;
     v88 = 1;
     v53 = v152->base;
     a3 = 1;
     init_num_by_buf(&v120, v53, &a3, 1u, 0);
     qmemcpy(&v126, &v120, 0x210u);
LABEL_95:
     v89 = 0;
     v88 = 1;
     v55 = v142->base;
     v150 = 1;
     init_num_by_buf(&v124, v55, &v150, 1u, 0);
     v56 = &v124;
     goto LABEL_100;
   }
   v52 = 0;
   qmemcpy(&v132, v151, 0x210u);
   while ( v52 < v147 - 1 )
   {
     qmemcpy(&v84, v151, 0x210u);
     ++v52;
     qmemcpy(&v132, (const void *)mul_num(&v118, v84), 0x210u);
   }
   qmemcpy(&v130, &v132, 0x210u);
   if ( !v147 )
     goto LABEL_90;
   v54 = 0;
   qmemcpy(&a1, v152, 0x210u);
   while ( v54 < v147 - 1 )
   {
     qmemcpy(&v84, v152, 0x210u);
     ++v54;
     qmemcpy(&a1, (const void *)mul_num(&v115, v84), 0x210u);
   }
   qmemcpy(&v126, &a1, 0x210u);
   if ( !v147 )
     goto LABEL_95;
   v57 = 0;
   qmemcpy(&v127, v142, 0x210u);
   while ( v57 < v147 - 1 )
   {
     qmemcpy(&v84, v142, 0x210u);
     ++v57;
     qmemcpy(&v127, (const void *)mul_num(&v117, v84), 0x210u);
   }
   v56 = &v127;
LABEL_100:
   qmemcpy(&v131, v56, 0x210u);
   qmemcpy(&v84, &v126, 0x210u);
   v58 = add_num(&v131, &v116, v84);
   qmemcpy(&v83, &v130, 0x210u);
   if ( !cmp_num(v58, v83) )
   {
     v143 = 0;
     break;
   }
   if ( !v143 )
   {
     v140 = 1;
     _CxxThrowException(&v140, &_TI1H);
   }
   init_num_by_int(&a6, v143);
   v66 = v142;
   qmemcpy(&v82, &a6, 0x210u);
   v81 = &a2;
   div_num(v142, (int)v142, (int)&savedregs, (int)&v85, (int)&v126, *(num *)&v81);
   qmemcpy(&v79, &a2, 0x210u);
   if ( !cmp_num(v66, v79) )
     v154 = (num *)((char *)v154 + 1);
   v50 = v147++ + 1;
 }
 if ( (signed int)v154 > 10 )
   v143 = 0;
 v156 = -1;


这里乍一看是去check c的立方+d的立方=b的立方,但上过高中的人都知道,这个方程是不存在整数解的。再看下面有个除法,若除数为零则扔出一个异常。如果前面的步骤全部正确,这里这个除数必须为零,即这个异常必须被触发。

 

看一看Exception Handler结构体,发现是跳到这里:


mov     [ebp+lpMem], 2
mov     eax, offset loc_40211C
retn

显然,就是为下面的lpMem = 2创造条件的。

if ( lpMem == (LPVOID)2 )
 {
   ++v143;
   qmemcpy(&a2, v152, 0x210u);
   qmemcpy(&v131, v152, 0x210u);
   v131.is_negative = a2.is_negative == 0;
   qmemcpy(&v82, &v131, 0x210u);
   add_num(v142, &a6, v82);                    // c-d
   qmemcpy(&a2, &num2, 0x210u);
   qmemcpy(&v80, &a2, 0x210u);
   if ( !cmp_num(&a6, v80) )
   {
     init_num_by_int(&v123, 4);
     qmemcpy(&v78, &v123, 0x210u);
     mul_num(&v121, v78);
     init_num_by_int(&v122, 2);
     qmemcpy(&v70, &v122, 0x210u);
     mul_num(&v119, v70);
     qmemcpy(&v131, &num1, 0x210u);
     qmemcpy(&v69, &v121, 0x210u);
     v59 = add_num(&v119, &v114, v69);         // 2c+4d
     qmemcpy(&v130, &v131, 0x210u);
     if ( v59->base != v131.base )
       change_base(&v130, v59->base);
     v150 = v59->is_negative;
     v60 = v150;
     if ( v150 == v130.is_negative )
     {
       v61 = v59->length;
       v62 = 0;
       if ( v61 < v130.length )
         v61 = v130.length;
       if ( v61 > 0 )
       {
         v63 = (char *)&v130 - (char *)v59;
         v64 = v59->num_buf;
         v154 = (num *)(-13 - (_DWORD)v59);
         do
         {
           v65 = v64[v63];
           if ( (unsigned __int8)*v64 >= v65 )
           {
             if ( (unsigned __int8)*v64 > v65 )
               v62 = 1;
           }
           else
           {
             v62 = -1;
           }
           ++v64;
         }
         while ( (signed int)v154 + (signed int)v64 < v61 );
         v60 = v150;
       }
       if ( v60 )
         v62 = -v62;
       if ( !v62 )
         --v143;
     }
   }
 }
 if ( !v143 )
   return handler(527310531, 880460335, 19168, (int (__stdcall *)(int))&loc_40158E, v71, v72, v73, v74, v75, v76, v77);
GG:
 v67 = 0;
 v68 = 0;
 do
   v67 += v68++;
 while ( v68 < 3 );
 return printf((int)asc_40F0AC, v94);
}

这里计算了c+(-d)和2c+4d,然后和之前载入的两个64进制数比较。可以解出c和d。
c = 0x9c5d44875c2a969b2d8d6e76abd1b81a123f9
d = 0x9c5d44875c2a93e24ed0b8784c9260ed63913

这样的话,a c d全部确定,且长度域也确定,剩下4个字节,根据哈希函数返回0确定。
爆破得到:

0x2c2f1ca7
全部连起来得到数字
0x09c5d44875c2a93e24ed0b8784c9260ed6391309c5d44875c2a969b2d8d6e76abd1b81a123f92c2f1ca73dd7c4ddec9ae7c5e8c113130e0a
转为62进制,即为Flag
6OqTbC16uYclIp3aqSoJuSpYO4I9JodXMs1oaaI40wwzue79rqVxXflyoZeLxs3Q1yxO1yUoz06






合作伙伴

京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。

 

京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。

 

随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。

 

京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。


CTF 旗帜已经升起,等你来战!

扫描二维码,立即参战!



看雪.京东 2018 CTF 





看雪2018安全开发者峰会

2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。



戳下图↓,立即购票,享5折优惠!







戳原文,立刻加入战斗!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存