-
-
[比赛]看雪.WiFi万能钥匙 CTF 2017第八题 点评及解题思路
-
发表于: 2017-6-30 17:27 2649
-
看雪CTF 2017 比赛进行至第八题
截止至今天中午12点,第八题破解人数为5人!
攻击方排名前十名波动不大,
loudy从第十位上升至第7位,
poyoten再次冲进前十位~
前十名能否保守住自己的位置呢?
是否会有黑马一战成名呢?
期待ing......
接下来我们来回顾一下第八题
看看 看雪评委和出题者是怎么说的ヾ(๑╹◡╹)ノ"。
看雪评委 netwind 点评
该题亮点依然在反调试上,用虚拟机进行代码保护,通过线程动态加解密主要代码,通过校验和检查代码完整性,根据时间来反调试,另外还将实际代码替换为假代码,防止反汇编,在反调试方面让攻方选手举步维艰。算法上采用了RSA算法的思想,解决掉反调试后,参考RSA算法可以求解。
作者简介
作者loudy,原名吴林峰,毕业于北京航空航天大学电子信息工程专业,当前从事工作出于保密规定暂不公开。爱好跑步、篮球、电影;热衷编程、特别是逆向分析。2007年接触看雪,以潜水学习为主,也希望多认识有相同爱好的朋友。
看雪 CTF2017 第八题设计思路
一、答案
UFdVXVRTVVFYWltdXV9XQkFDQEUtLVBTV1BWVVFQUVxeWVxfWENDQkRCQE5CTEBCWFZaVVRTVlxZU1lcXC0tVGhpNV9pc19BX1ZlcnlfU2ltcGxlX1Rlc3Qh
二、基本设计思想
(1)简单虚拟机(使用虚拟寄存器传值)
实现了系统函数调用、CMP、JNZ、XOR等调用
(2)反调试
主要使用了3种
1、 线程动态加密解密主要代码
2、 CheckSum检查主要代码
3、 时间判断
(3)反汇编
主要通过将实际代码替换为假代码,防止反汇编。
(4)算法设计
主要是利用RSA思想,其中(比较小)
P:900F0CA3041C345B
Q:98FCAE63A170C363
N:56172073862A662A8D6BB1A135999031
E:D9C382944A461EE3
D:4A064A97921BDF9E3F354E9020AE054F
首先对输入进行base64解码,然后将解码后注册码分成三段,
1、 第一段解码得到E,再和固定值做大数乘法运算,结果和另一固定值比较,相等则到下一步
2、 第二段解码得到N,再和固定值做大数乘法运算,结果和另一固定值比较,相等则到下一步
3、 通过E和N对第三段加密,和内存固定值比较,相等则成功
三、破解思路
1、先解决反调试和反反汇编,将真正代码还原
2、识别大数乘法运算,通过固定值和大数除法还原E、N,得到第一段和第二段注册码。
3、通过E、N解出D
4、用N和D对固定值解密,得到第三段注册码。
5、组合三段注册码,base64加密,得到最终的注册码。
下面选取攻击者 风间仁 的破解分析
1. TlsCallback
创建了6个线程, 前3个线程是负责smc解码的, 后3个线程没用
1 | .text:00402D30 TlsCallback_2 |
线程1: 解码004025DC处的跳转及check1函数
1 2 3 4 5 6 7 | .text:00402830 thread1 .text:004025DC push offset loc_4025E2 .text:004025E1 retn .text:004025E2 lea ecx, [ebp+var_110] .text:004025E8 push ecx .text:004025E9 call check1 |
线程2: 解码0040263C处的跳转及check2函数
1 2 3 4 5 6 7 | .text:00402970 thread2 .text:0040263C push offset loc_402642 .text:00402641 retn .text:00402642 lea eax, [ebp+var_110] .text:00402648 push eax .text:00402649 call check2 |
线程3: 解码0040269C处的跳转及check3函数
1 2 3 4 5 6 7 | .text:00402A90 thread3 .text:0040269C push offset loc_4026A2 .text:004026A1 retn .text:004026A2 lea edx, [ebp+var_110] .text:004026A8 push edx .text:004026A9 call check3 |
2. 主流程
一个自定义的虚拟机
校验函数
1 | .text:00402700 fn_check |
检测代码改动
1 | .text:00402481 call check_text_areas |
sn=base64_decode(sn)
1 | .text:0040251D call base64_decode |
sn的格式(共90位): (20位)--(39位)--(27位)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | .text:00402573 cmp [ebp+var_130], 90 .text:0040257A jnz short loc_4025AC .text:0040257C movsx edx, [ebp+Dst+14h] .text:00402583 cmp edx, '-' .text:00402586 jnz short loc_4025AC .text:00402588 movsx eax, [ebp+Dst+15h] .text:0040258F cmp eax, '-' .text:00402592 jnz short loc_4025AC .text:00402594 movsx ecx, [ebp+Dst+3Dh] .text:0040259B cmp ecx, '-' .text:0040259E jnz short loc_4025AC .text:004025A0 movsx edx, [ebp+Dst+3Eh] .text:004025A7 cmp edx, '-' .text:004025AA jz short loc_4025B2 |
校验前20位
(sn1 xor abcdefg...) * 98765432109876543210123 == 1549780652036258484424751705102781884386113
1 | .text:004025E9 call check1 |
校验中间39位
(sn2 xor abcdefg...) ^ 2 == 13095069099216326605010245808779535277211541324456558063162414338128147458401
1 | .text:00402649 call check2 |
检验后27位, sn3视为16进制
sn3 ^ 15691529100101820131 mod 114433688655117320765854989491151409201 == 71639176673360967005214790689576394595
1 | .text:004026A9 call check3 |
sn3作为16进制转换的时候是有符号转换的, 所以导致>=80的值无法正常转换
这里只需要关心能产生0x00的值即可(有挺多个, 如0x89)
1 2 3 4 5 6 7 8 9 10 11 12 13 | for ( int i = 8; i < 16; i++) { for ( int k = 0; k < 16; k++) { int v = ( char )((i << 4) | k); char v1 = v / 16; char v2 = v % 16; v1 += v1 < 0 ? 0x37 : 0x30; v2 += v2 < 0 ? 0x37 : 0x30; // 这里非数字字母的字符, 后面都会被0替代 printf ( "%02X: %c%c\n" , ( BYTE )v, v1, v2); } } |
3. 后27位计算
已知:
e = 15691529100101820131
n = 114433688655117320765854989491151409201
m = 71639176673360967005214790689576394595
分解得到
d = 98395538376216701876091105738065053007
c = m ^ d mod n = 55986991232018409201158808992848352475 (2A1EB3C9579DFA307CF5B6C8730114DB)
c中>=0x80的是无法通过转换得到的(如B3, C9, ...)
sn3(27字节) > c (16字节)
c ^ e mod n = m, 根据(a * b) mod n = (a mod n) * (b mod n) mod n
得 (c mod n) * (c mod n) * ... mod n = m
再由c mod n = (c + k * n) mod n, 代入上式得到
(c + k * n) ^ e mod n = m
根据下面这两个条件可以立刻得到很多解(如: 0208674855670d0560353c3a1d3e242e217766)
sn3 = c + k * n
sn3中的字节必须小于0x80
得到的解, 在前面填充可以转换成00的值: 89898989898989890208674855670d0560353c3a1d3e242e217766
与前面的串连接起来base64得到最终结果
UFdVXVRTVVFYWltdXV9XQkFDQEUtLVBTV1BWVVFQUVxeWVxfWENDQkRCQE5CTEBCWFZaVVRTVlxZU1lcXC0tiYmJiYmJiYkCCGdIVWcNBWA1PDodPiQuIXdm
4. 穷举脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import itertools n=114433688655117320765854989491151409201 e=15691529100101820131 c=55986991232018409201158808992848352475 def valid_result(v): sv=hex(v)[2:] sv_len=len(sv) if (sv_len&1) != 0: sv= '0' +sv sv_len=sv_len+1 for i in itertools.count(0,2): if (i>=sv_len): break if (ord(sv[i])>=0x38): return False return True def solve(ibegin,iend): g=0 nc=c k=0 for i in itertools.count(0): g=g+1 nc=nc+n if ((ibegin+i)>=iend): break ; #print(nc) #print( pow (nc,e,n)) if (g>=1000000): print(ibegin+i) g=0 if (valid_result(nc)): print( '---' ) print(i) print(nc) print(hex(nc)) # break return solve(0,4294967296) '' ' --- 396154 45333533916159234226406484520676079360374630 0x208674855670d0560353c3a1d3e242e217766 --- 456935 52288927946305920099875916636937753163020611 0x2583f59392b7266031a47246a1e672b013943 .. |
最后感谢 WiFi 万能钥匙安全应急响应中心的赞助支持, 接下来的比赛大家一定要使出洪荒之力哦!↖(^ω^)↗ 比心 ❤
赞助商
上海连尚网络科技有限公司成立于 2013 年,是一家专注于提供免费上网和内容服务的移动互联网企业。连尚网络自主研发的核心产品 WiFi 万能钥匙,以分享经济的模式,通过云计算和大数据技术,利用热点主人分享的闲置WiFi资源,为用户提供免费、稳定、安全的上网服务,以帮助更多的人上网,找到属于他们的机会,改变自己的命运。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [话题] 9月10日 教师节到了,说说你记忆深刻的老师 4519
- [原创] 我和程序猿男朋友的爱恨情仇【结帖】 8666
- [推荐]看雪杯AFSRC造洞节,最棒的福利送给看雪的你! 6463
- [注意]某白帽未授权渗透测试政府网站被抓 8526
- [分享] 本周 安全类会议 大汇总 4688