85

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

 5 years ago
source link: http://www.10tiao.com/html/523/201807/2458290345/1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

第八题出题者以被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折优惠!







戳原文,立刻加入战斗!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK