看雪.京东 2018 CTF 第十三题 NeuralCrackme 点评与解析
你知道第十三题过去,就剩两题了。
万里长征就剩最后800米了,
小伙伴们冲啊!
第十三题作者 xk以被17人攻破的成绩,位列第八名。
第12题过后,攻击方
jackandkx在本题表现突出,上升4位,从第9名升至第5名。
距离比赛还有三题!
所剩机会不多了,
谁能最终亲临颁奖典礼现场?
真的是非常期待啊!
看雪版主&评委 netwind 点评
本题设计了一个3层BP网络算法,输入值有2个,输出值有1个,神经元18个,输入样本820组,对样本训练252次。通过把注册码输入神经网络来进行算法验证。此题可以通过分析BP网络算法来求解,也可以在不了解BP网络算法的情况下暴力枚举求解。题目构思巧妙,设计新颖。
看雪.京东 2018 CTF 第十三题 作者简介
lelfei
bbs.pediy.com/user-30
第十三题出题者简介:
lelfei,业余Crack爱好者,学生时代对电脑产生了浓厚的兴趣,经历了很长时间的游戏沉迷后,开始慢慢转向学习技术,工作后自学了ASM,VB,VC,HTML,ASP,Python等语言的入门。工作原因上网较少,对单机的逆向分析、算法比较感兴趣,但是由于缺少系统的学习,水平处于“入行较早层次较低知识较杂”的阶段。
看雪.京东 2018 CTF 第十三题 设计思路
说明:纯算法题,无壳无花无ANTI,希望你不要被绕晕。
注册码:F13FE02140。注册成功后显示“Congratulation”
设计思路
最近对人工智能比较感兴趣,收集了一堆神经网络BP算法。当前比较火的是G家的TensorFlow库,在Python下封装了大部分常见神经网络算法,比如卷积神经CNN(通常用作图像识别),循环神经RNN(通常用作自然语言识别)等等,实现起来非常简单,还有可视化的图表动态显示训练过程,很好玩。但是封装好的东西就学不到原理了,又找了一些入门级的BP源码,C语言实现起来居然非常简单,这个Crackme就是基于3层神经网络实现的加法,训练过程非常快,误差到0.003以内只需要252次训练,对于10以内的浮点数加法计算结果还是比较准确的。下面具体说一下这个Crackme的实现过程。
1、设计一个3层BP网络,输入值有2个,输出值有1个,神经元18个。(为什么用18个神经元?因为对同一样本测试了不同神经元数量,发现18个时收敛的最快。)
2、输入样本820组,样本数据见源码。(为什么是820组?因为网上的源码就是这么定的。为什么要用这个样本?因为对于随机样本,一般需要1000到2000次训练才能达到误差0.003以内,大约需要2秒多时间,但是这个样本居然只要252次不到0.3秒就到了,可能是样本的随机性比较好吧。)
3、对样本训练252次。训练过程为:
1>initBPNework()对样本进行归一化处理,即把输入数和输出数映射到(0,1)之间。
2>trainNetwork()样本输入BP算法第一层开始学习,前向传播算法forwardTransfer()用的是sigmod()函数。统计每轮学习后的误差。
3>训练到指定次数或误差小于指定值时,训练结束。
4、Crackme算法的实现过程:
1>注册码输入10位16进制字符,转化为5字节16进制数据。后面会要求注册码第6位必须为“0”。
2>分别用前2字节和后3字节初始化2个浮点数,要求浮点值为1到10之间。
3>2个浮点数作为输入值,给训练好的神经网络进行计算(即计算加法结果),得到1个输出值。
4>输出的浮点值转化为字符,检测第2位必须为“.”。
5>把第1,3,4位作为三角形的三条边,计算三角形的面积,面积大于15.5;且输入值相加的结果与输出值的误差小于0.003时,即为正确注册码。
5、采用gcc+m64编译,64位程序先把OD踢出局,不过在IDA里接近于源码显示了。
说实话我自己都觉得算法太绕了,验证条件拆分成了很多小步,想要靠分析算法得到答案真的太难,建议还是穷举吧。
先说说逆向分析怎么得到答案:
1、根据算法第5条,三角形三边长度都在0到9之间,必须为9、9、9时才能面积大于15.5,结合第4条,得到输出值为9.99。
2、分析出BP算法其实就是加法,如果懂神经网络的话应该一眼就能看出输入值与输出值之间的规律。
3、可得知输入码分成二段作为浮点数,相加结果为9.99。
4、结合算法第2条浮点值为1到10之间,浮点数1是F03F,浮点数9是2240,输入值范围已经大大缩小。
5、测试输入不同的值观察输出值,输入0040002040(即2和8),输出为10.017779,输入0840001C40(即3和7)输出为10.030374,输入1440001440输出10.034630,发现算法与第1个输入值成正比,第1个值越大则和值越大。且第1个输入值为2时结果已经超过了10,不能得到9.99。
6、可得知第1个值要尽量小,2个字节能表示出的最接近1的值是F13F,即1.0625。第2个值要小于9,分别从3字节能表示出的最接近9的值开始试,F02140即8.96875时输出10.029751,E02140即8.9375时输出9.998681,D02140即8.90625时输出9.967608,C02140即8.875时输出9.936532。只有E02140时符合条件。
此时误差为1.0625+8.9375-9.998681=0.001319,符合条件5。得到最终注册码为:F13FE02140。
穷举的话,不需要了解BP算法,只需要识别出二项条件:
一是2个浮点数值为1到10之间,即前2字节取值范围为3FF0-4024,后3字节取值范围为3FF000-4024F0(最后一位必须为0),范围其实相当小;
二是计算结果为9.99到10之间。难点在于修改程序直接调用BP算法的验证过程,需要手输大量ASM代码并跳过算法陷阱,或者IDA还原出BP算法然后自己写一个程序,这二种方法实现起来都比较麻烦。构造好穷举程序后,穷举过程不到0.1秒。
源码中main - kg.c文件是穷举过程,总共只有38416种可能,调用BP算法得到输出值为9.99的只有24种,误差在0.003以内的只有1种。
源码中main - my.c文件是网上源码经过我的简单改写,训练完成后可以输入2个10以内值来查看输出值。
源码中main.c是crackme源码。
PS:这是去年秋季赛时做的,由于当时题库已经满了,没提交。这次本来设计了一个新算法,一不小心翻出去年的这题,就拿这题冲锋陷阵吧。
看雪.京东 2018 CTF 第十三题解析
本解析来自看雪论坛 oooAooo
IDA载入后发现有2个TlsCallback函数,初步看这两个函数,没发现特别的东西,暂时不管,先分析下main的代码。
1、获取输入sn,并判断sn长度如果不是10,则输出key error错误提示。(条件一)
2、调用401CE0函数,将sn字符串转换成一个64位16进制数称为keyValue,同时要求输入的字符必须在"0123456789ABCDEF"范围内。(条件二) 输入"123456789A",将转换成为0x9A78453412。
3、将64位16进制数,拆分成2个double数称为 keyValue_x 和 keyValue_y,分别为低4字节和高6字节,为:0x3412000000000000,0x9A78560000000000。
4、调用4015E0函数将上面的2个double数经过了一系列的浮点运算获得一个双精度值称之为:newKeyValue。同时判断上面的两个双精度数必须在 1.0 和10.0之间(条件三)
5、将 newKeyValue格式化成字符串 newKeyString,并要求 newKeyString [1]必须是小数点。(条件四)
6、将newKeyString[0]、 newKeyString[2]、 newKeyString[3]字符减去0x30,并将它们的平方和作为输入调用403360函数,在其内部进行了一系列的浮点运算,并将运算结果赋给 newKeyValue2。
7、判断 newKeyValue2 不小于15.5。(条件五)
8、要求 keyValue的第五位必须为0。(条件六)
9、 (keyValue_x+ keyValue_y - newKeyValue) <0.003。(条件七)满足上面的7个条件则输出“Congratulation”表示成功。
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
double v3; // xmm1_8
__int64 v4; // rdx
__int64 v5; // rax
__int64 v6; // rdx
__int64 v7; // rax
__int64 v8; // rdx
__int64 v10; // rax
__int64 v11; // rax
double v12; // xmm1_8
__int64 v13; // rax
int newKey1; // [rsp+20h] [rbp-198h]
char v15; // [rsp+24h] [rbp-194h]
char Format[4]; // [rsp+30h] [rbp-188h]
char v17; // [rsp+34h] [rbp-184h]
double v18; // [rsp+40h] [rbp-178h]
double low4Byte_newKey; // [rsp+50h] [rbp-168h]
double high5_newKey1; // [rsp+58h] [rbp-160h]
double low4Byte_newKey_1; // [rsp+60h] [rbp-158h]
double high5_newKey1_1; // [rsp+68h] [rbp-150h]
__int64 v23; // [rsp+70h] [rbp-148h]
__int64 v24; // [rsp+78h] [rbp-140h]
__int64 v25; // [rsp+80h] [rbp-138h]
__int64 v26; // [rsp+88h] [rbp-130h]
char inputKey[256]; // [rsp+90h] [rbp-128h]
int v28; // [rsp+190h] [rbp-28h]
sub_401F00(*(__int64 *)&argc, (__int64 *)argv);
v15 = 0;
memset(inputKey, 0, sizeof(inputKey));
v17 = 0;
low4Byte_newKey = 0.0;
high5_newKey1 = 0.0;
low4Byte_newKey_1 = 0.0;
high5_newKey1_1 = 0.0;
v18 = 0.0;
v28 = 0;
newKey1 = 0;
*(_DWORD *)Format = 0;
v23 = 0LL;
v24 = 0LL;
v25 = 0LL;
v26 = 0LL;
sub_401790();
sub_401C50((__int64)&v28, (__int64)inputKey, v4, 252);
v5 = 0LL;
do
{
v6 = (byte_408D00[v5] ^ 0x19) - (unsigned int)v5;
*((_BYTE *)&v23 + v5) = (byte_408D00[v5] ^ 0x19) - v5;
++v5;
}
while ( v5 != 16 );
printf(Format, inputKey, v6, &v23);
Format[0] = byte_408D70 ^ 0x25;
Format[1] = (byte_408D71 ^ 0x25) - 1;
scanf(Format, inputKey, inputKey, Format);
if ( strlen(inputKey) != 10 ) // 条件1:长度为10
{
v7 = 0LL;
do
{
v8 = (byte_408D10[v7] ^ 0xF) - (unsigned int)v7;
*((_BYTE *)&v23 + v7) = (byte_408D10[v7] ^ 0xF) - v7;
++v7;
}
while ( v7 != 16 );
goto LABEL_6;
}
if ( (unsigned int)charToHex((__int64)Format, (__int64)inputKey, (__int64)&newKey1, (__int64)inputKey, 10) != 5 )// 将输入转换成整数
{
v10 = 0LL;
do
{
v8 = (byte_408D20[v10] ^ 0x21) - (unsigned int)v10;
*((_BYTE *)&v23 + v10) = (byte_408D20[v10] ^ 0x21) - v10;
++v10;
}
while ( v10 != 16 );
goto LABEL_6;
}
HIWORD(low4Byte_newKey) = newKey1; // "1234" --> 3412000000000000
low4Byte_newKey_1 = low4Byte_newKey;
*(_WORD *)((char *)&high5_newKey1 + 5) = HIWORD(newKey1);
HIBYTE(high5_newKey1) = v15; // 123456789A --->9A78560000000000
high5_newKey1_1 = high5_newKey1;
sub_4015E0((__int64)Format, (__int64)inputKey, &v18, &low4Byte_newKey);// if ( v17 <= 1.0 || v17 >= 10.0 || (v18 = v8[3], v18 <= 1.0) || v18 >= 10.0 )
Format[0] = byte_408D72 ^ 0x12;
Format[1] = (byte_408D73 ^ 0x12) - 1;
Format[2] = (byte_408D74 ^ 0x12) - 2;
sprintf(Format, inputKey, Format, inputKey, *(_QWORD *)&v18, v18, v3, v18);
if ( inputKey[1] != '.' )
{
v11 = 0LL; // Ohhh.Try again
do
{
v8 = (byte_408D40[v11] ^ 0x3F) - (unsigned int)v11;
*((_BYTE *)&v23 + v11) = (byte_408D40[v11] ^ 0x3F) - v11;
++v11;
}
while ( v11 != 16 );
goto LABEL_6;
}
v12 = (double)(inputKey[3] - 0x30) * (double)(inputKey[3] - 0x30);
if ( sub_403360(
(__int64)Format,
(__int64)inputKey,
(double)(inputKey[0] - 0x30) * (double)(inputKey[0] - 0x30)
+ (double)(inputKey[2] - 0x30) * (double)(inputKey[2] - 0x30)
+ v12,
v12) <= 15.5
|| newKey1 & 0xF0000 )
{
v13 = 0LL;
}
else
{
v13 = 0LL;
if ( COERCE_DOUBLE(COERCE_UNSIGNED_INT64(low4Byte_newKey_1 + high5_newKey1_1 - v18) & xmmword_409080) < 0.003 )
{
do // Congratulation
{
v8 = (byte_408D50[v13] ^ 0x47) - (unsigned int)v13;
*((_BYTE *)&v23 + v13) = (byte_408D50[v13] ^ 0x47) - v13;
++v13;
}
while ( v13 != 16 );
goto LABEL_6;
}
}
do
{
v8 = (byte_408D60[v13] ^ 0x37) - (unsigned int)v13;// comeon go on
*((_BYTE *)&v23 + v13) = (byte_408D60[v13] ^ 0x37) - v13;
++v13;
}
while ( v13 != 16 );
LABEL_6:
printf(Format, inputKey, v8, &v23);
return 0;
}
一共有7个条件,不想分析浮点运算了。利用下面4个条件爆破sn,具体如下:
1、长度为10
2、必须是"0123456789ABCDEF";
3、转换的2个双精度数值必须大于1.0小于10.0,其中1.0对应的为 0x3FF0000000000000,10.0对应为0x4024000000000000。因此要求输入的 keyValue_x取值范围为0x3FF1和0x4024之间, keyValue_y取值范围为0x3FF000和402400之间。
4、
keyValue的第5位为0,也就说明上面的递增步长是0x10。
脚本如下:
import subprocess
import sys
pname ="NNCrackme.exe"
def getSn():
a=['0','0','0','0','0','0','0','0','0','0',0]
i =0x3FF1
while i<0x4024:
print('i='+ str(hex(i)))
temp = i&0x000f
a[1]= (i&0x000f)
a[0]= ((i&0x00f0)4)
a[3]= ((i&0x0f00)8)
a[2]= ((i&0xf000)12)
j = 0x3FF010
while j<0x402400:
a[5]= (j&0x00000f)
a[4]= ((j&0x0000f0)4)
a[7]= ((j&0x000f00)8)
a[6]= ((j&0x00f000)12)
a[9]= ((j&0x0f0000)16)
a[8]= ((j&0xf00000)20)
k =0
keystr = ''
while k <10:
keystr=keystr+str(hex(a[k]))[2:].upper()
k=k+1
print(keystr)
arg =keystr
io = subprocess.Popen(pname,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
ret=io.communicate(input=arg)
res = ret[0].decode()[0:]
print(res)
if(res.find('Congratulation')>=0):
print("the flag is:%s"%keystr)
return
else:
j=j+0x10
i=i+1
return
getSn()
爆破结果如下:
F13F802140
Input your KEY:Come on, go on
F13F902140
Input your KEY:Come on, go on
F13FA02140
Input your KEY:Come on, go on
F13FB02140
Input your KEY:Come on, go on
F13FC02140
Input your KEY:Come on, go on
F13FD02140
Input your KEY:Come on, go on
F13FE02140
Input your KEY:Congratulation
the flag is:F13FE02140
flag = F13FE02140
合作伙伴
京东集团是中国收入最大的互联网企业之一,于2014年5月在美国纳斯达克证券交易所正式挂牌上市,业务涉及电商、金融和物流三大板块。
京东是一家技术驱动成长的公司,并发布了“第四次零售革命”下的京东技术发展战略。信息安全作为保障业务发展顺利进行的基石发挥着举足轻重的作用。为此,京东信息安全部从成立伊始就投入大量技术和资源,支撑京东全业务线安全发展,为用户、供应商和京东打造强大的安全防护盾。
随着京东全面走向技术化,大力发展人工智能、大数据、机器自动化等技术,将过去十余年积累的技术与运营优势全面升级。面向AI安全、IoT安全、云安全的机遇及挑战,京东安全积极布局全球化背景下的安全人才,开展前瞻性技术研究,成立了硅谷研发中心、安全攻防实验室等,并且与全球AI安全领域知名的高校、研究机构建立了深度合作。
京东不仅积极践行企业安全责任,同时希望以中立、开放、共赢的态度,与友商、行业、高校、政府等共同建设互联网安全生态,促进整个互联网的安全发展。
CTF 旗帜已经升起,等你来战!
扫描二维码,立即参战!
✎看雪.京东 2018 CTF
看雪.京东 2018 CTF 第八题 薛定谔之猫 点评与解析
看雪.京东 2018 CTF 第九题 PWN-羞耻player 点评与解析
看雪2018安全开发者峰会
2018年7月21日,拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,倾力打造一场技术干货的饕餮盛宴——2018 安全开发者峰会,将在国家会议中心隆重举行。会议面向开发者、安全人员及高端技术从业人员,是国内开发者与安全人才的年度盛事。此外峰会将展现当前最新、最前沿技术成果,汇聚年度最强实践案例,为中国软件开发者们呈献了一份年度技术实战解析全景图。
戳下图↓,立即购票,享5折优惠!