看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析
新的一周在高温中开始了...
经过一周的奋战,来看看大家的战绩
第八题出题者以被5人攻破的成绩,位列第一位
本题过后 风间仁冲向首位,acdxvfscd 上升2位至第三名。
选手 新手慢慢来 冲进前十名。
风间仁曾在看雪2017 CTF 年中赛中取得第一名
此次究竟能否继续卫冕冠军呢?
让我们拭目以待!
看雪版主&评委 netwind 点评
本题主要考察算法的逆向识别求解能力,用到了汉诺塔求解,费马大定理,大数二元一次方程,crc校验等算法,逆向识别出算法模型后便可求解。
看雪.京东 2018 CTF 第八题 作者简介
看场雪
bbs.pediy.com/user-187526
第八题出题者简介:
看雪ID:瞧红尘
性格随性+理想主义, 爱好哲学,物理,计算机,做梦,修仙
看雪.京东 2018 CTF 第八题 设计思路
最小步数,求解法(这里选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进制,汉诺塔移动。
避免多解需要对输入进行长度判断
注:汉诺塔多步会出现多解.(事先我并不知道有多解)
是无整数解的,利用/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");
}
反校验需要4字节连续空隙即可伪造任意crc,这步需要最后去做,但是他的判断在解方程前面
if(b2 && !(*x==0) && !(*y==0) & !crc ) //正确结果是 true , lastB = 0 ,lastMov = 1
{ //默认crc的值为任意,不要开始考虑
//解方程后,最后修复crc
}
(如果能解出费马大定理,这个方程组就不用解了)
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)
输入数据的结构
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进制)
对代码区域进行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;
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]
合并相邻的两个操作:(最后多余的一个必须是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折优惠!