第九届浙江省大学生网络与信息安全竞赛 预赛WP

First Post:

Last Update:

Word Count:
1.8k

Read Time:
10 min

MISC

奇怪的图片

使用 StegSolve 在图片的 Red 0 通道提取 flag

WEB

FileDesk

测试发现上传归档后系统会自动解压并返回归档内容

通过 .htaccess 文件使 Apache 将 PDF 作为 PHP 格式解释

1
2
3
AddType application/x-httpd-php .pdf     # 让 .pdf 当 PHP 执行
# 或者
SetHandler application/x-httpd-php # 该目录下所有文件都当 PHP 执行

在 PDF 文件中直接写入一句话木马,通过命令 cat$IFS/flag.txt 得到 flag

Pickle Me

源码里的兜底就是 flag

1
2
3
4
5
...
return f.read().strip()
except Exception:
return "DASCTF{p1ckl3_0pc0de_by_p4ss_f1nd_cl4ss_g0tr00t}" # 本地测试兜底
...

Crypto

1byte

下载附件可知给出的 python 代码即为解密程序,密钥未知

根据题目,生成密钥的私钥只有 1 字节即 0-255

直接使用循环尝试每个数值

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

with open("params.json") as f:
d = json.load(f)

p = int(d["p"], 16)
g = int(d["g"], 16)
h = int(d["public_key_h"], 16)
c1 = int(d["ciphertext"]["c1"], 16)
c2 = int(d["ciphertext"]["c2"], 16)
for x in range(256):
if pow(g,x,p) != h:
continue
m = c2 * pow(pow(c1,x,p),-1,p) % p
print(m.to_bytes((m.bit_length() + 7) // 8, "big"))
print(x)

Reverse

shift

反编译得自定义 Base64 字母表

1
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/

即大小写字母交换位置,继续分析得求解步骤

  1. 将自定义 Base64 字母表映射回标准字母表并解码;
  2. 按 0x... 拆分十六进制文本;
  3. 使用 ROR8 反向恢复每一步的原始字节;
  4. 去掉结尾的 \0。

编写解密脚本,从目标可执行文件中提取目标

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
import base64
import re
import sys
from pathlib import Path


ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
STANDARD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
INITIAL_KEY = 0x28


def rol8(value: int, count: int) -> int:
count &= 7
return ((value << count) | (value >> ((8 - count) & 7))) & 0xFF


def ror8(value: int, count: int) -> int:
return rol8(value, (8 - (count & 7)) & 7)


def encode_flag(flag: bytes) -> str:
buffer = bytearray(flag + b"\x00")
state = INITIAL_KEY
for index, value in enumerate(buffer):
buffer[index] = rol8(value, state)
state = buffer[index]

hex_string = "".join(f"0x{value:X}" for value in buffer)
return (
base64.b64encode(hex_string.encode())
.decode()
.translate(str.maketrans(STANDARD, ALPHABET))
)


binary_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).parent / "nested" / "shift"
binary = binary_path.read_bytes()

candidates = re.findall(rb"[a-zA-Z0-9+/]{64,}={0,2}", binary)
target = max(candidates, key=len).decode()
encoded_bytes = base64.b64decode(target.translate(str.maketrans(ALPHABET, STANDARD)))
encoded_hex = encoded_bytes.decode()

tokens = re.findall(r"0x([0-9A-F]+?)(?=0x|$)", encoded_hex)
transformed = bytes(int(token, 16) for token in tokens)

recovered = bytearray()
state = INITIAL_KEY
for value in transformed:
recovered.append(ror8(value, state))
state = value

flag = bytes(recovered).rstrip(b"\x00")
assert encode_flag(flag) == target
print(flag.decode())

easyaosp

感觉题目很熟悉,尤其是这两个 Base64 解码的之前肯定做过

StringFog 类用于对应用中的密文进行解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import base64

class StringFog:
def decrypt(self, bArr, bArr2):
return self.xor(base64.b64decode(bArr), base64.b64decode(bArr2))

def xor(self, bArr, bArr2):
l = len(bArr)
l2 = len(bArr2)
bArrc = [int(b) for b in bArr]
i = 0
i2 = 0
while i < l:
if i2 >= l2:
i2 = 0
bArrc[i] = (bArr[i] ^ bArr2[i2])
i += 1
i2 += 1
return bytes(bArrc)

sf = StringFog()
print(sf.decrypt('8Y0kH3yJtTzjhyQDfZu0Lg==', 'kOhXbBX63E8='))

运行得到 AES 密钥 aessisissosohaha

与加密后输入值比较的数据来自 GIVEN_CIPHER 进行 xorWithKey 函数变换

1
2
3
4
5
6
7
GIVEN_CIPHER = [-119, -7, -41, 12, -97, 58, 16, 54, 18, 124, 12, -34, -89, 10, -1, 114, 103, -28, -31, 35, -98, -83, -127, 118, 29, 85, 62, 76, -21, 118, 51, 71, -30, 122, 72, -60, 12, 94, 106, 29, 9, -106, -14, -63, -12, 127, -118, -78]

def xorWithKey(bArr, b):
bArr2 = [b1 ^ b for b1 in bArr]
return bArr2

print(xorWithKey(GIVEN_CIPHER, 86))

得出 AES 加密结果放到 Java 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

class Main {
private static final byte[] GIVEN_CIPHER = {-33, -81, -127, 90, -55, 108, 70, 96, 68, 42, 90, -120, -15, 92, -87, 36, 49, -78, -73, 117, -56, -5, -41, 32, 75, 3, 104, 26, -67, 32, 101, 17, -76, 44, 30, -110, 90, 8, 60, 75, 95, -64, -92, -105, -94, 41, -36, -28};

public static void main(String[] args) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, getAESKEy("aessisissosohaha"));
System.out.println(new String(cipher.doFinal(GIVEN_CIPHER)));
} catch (Exception e) {

}
}
public static SecretKey getAESKEy(String str) {
byte[] bytes = str.getBytes();
return new SecretKeySpec(bytes, "AES");
}

}

数据安全

CedarRoom_1

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
import csv

def id_check(id):
if len(id) != 18: return False
if id[-1] == 'X':
if not id[:-1].isdigit(): return False
else:
if not id.isdigit(): return False
if id_verify(id) != id[-1].upper(): return False
return True

def id_verify(id):
c = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
b = [int(id[i]) * c[i] for i in range(17)]
a = sum(b) % 11
m = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
return m[a]

def mobile_check(mobile):
if len(mobile) != 11: return False
if not mobile.isdigit(): return False
if int(mobile[:3]) not in [134, 135, 136, 137, 138, 139, 147, 148, 150, 151, 152, 157, 158, 159, 172, 178, 182, 183, 184, 187, 188, 195, 198, 130, 131, 132, 140, 145, 146, 155, 156, 166, 167, 171, 175, 176, 185, 186, 196, 133, 149, 153, 173, 174, 177, 180, 181, 189, 190, 191, 193, 199]: return False
return True

with open('reading_room.sql', 'r', encoding='utf-8') as sql:
lines = sql.readlines()
with open('reading_room.csv', 'w', encoding='utf-8') as c:
out = csv.writer(c)
out.writerow(['loan_id','reader_name','mobile','credential_no','borrowed_at','due_at'])
for l in lines:
if l.startswith('INSERT INTO'):
l = l.split('(')[1].split(')')[0]
l = l.replace('\'', '').split(',')
## 过滤器开始
# 纳入范围
if not(l[8] in ['BORROWED', 'OVERDUE'] and l[7] == 'NULL'):
continue
# 读者姓名
l[1] = l[1][0] + '*' * (len(l[1]) - 1)
# 手机号
l[2] = l[2].replace('-', '').replace(' ', '').replace('+86', '')
if not mobile_check(l[2]):
l[2] = 'INVALID'
else:
l[2] = l[2][:3] + "*"*4 + l[2][-4:]
# 身份证
if not id_check(l[3]):
l[3] = 'INVALID'
else:
l[3] = l[3][:2] + '*'*14 + l[3][-2:]
# 删除多余字段
l.pop(-1) # snapshot_at
l.pop(-1) # status
l.pop(-1) # returned_at
l.pop(-3) # book_title
## 过滤器结束
out.writerow(l)

PaperKite

时间不够没做完(杂乱代码警告)

需要从抓包中提取出上传的文件及文件对应的上传时元数据和上传后生成的 manifest 数据,各 ID 需要对应

需要的 review_reference 提取还没写

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
import pyshark
import os

rpts = {}
manifests = []
upload_ids = {}

cap = pyshark.FileCapture('batch.pcapng')

for pkt in cap:
if 'HTTP' in pkt:
try:
code = pkt.http.response_code
uri = pkt.http.request_uri
# 提取上传时元数据
if '/v3/uploads' in uri:
upload_id = uri.split('/')[3]
response_body_hex = pkt.http.file_data.replace(':', '')
response_body = bytes.fromhex(response_body_hex)
resp_json = json.loads(response_body)
print(upload_id)
if upload_id not in upload_ids.keys():
upload_ids[upload_id] = []
upload_ids[upload_id].append(resp_json['report_id'])
if '/v3/reports' in uri:
# 提取上传文件内容并按 report_id 及分片序号合并
if '/archive' in uri:
report_id = uri.split('/')[3]
ranges = int(uri.split('/')[6])
deliveries = uri.split('/')[8]
report_path = os.path.join('dumps', report_id)
print(report_id, ranges, deliveries)
response_body_hex = pkt.http.file_data.replace(':', '')
if report_id not in rpts.keys():
rpts[report_id] = []
while len(rpts[report_id]) - 1 <= ranges:
rpts[report_id].append('')
rpts[report_id][ranges] = response_body_hex
# 提取 manifest 数据
if '/manifest' in uri:
report_id = uri.split('/')[3]
print(report_id)
response_body_hex = pkt.http.file_data.replace(':', '')
response_body = bytes.fromhex(response_body_hex)
resp_json = json.loads(response_body)
manifests.append([report_id, resp_json['source_upload_id'], resp_json['policy_version'], resp_json['output_sha256']])
except AttributeError:
pass

for k in rpts.keys():
print('Writing', k, end='')
with open(os.path.join('dumps', f'{k}.csv'), 'w', encoding='utf-8') as f:
try:
f.write(bytes.fromhex(''.join(rpts[k])).decode('utf-8'))
print()
except:
f.write(bytes.fromhex(''.join(rpts[k])).decode('utf-8', 'replace'))
print(' ERROR')

with open('manifests.csv', 'w') as h:
c = csv.writer(h)
c.writerow(['report', 'source_upload_id', 'policy_version ', 'hash'])
c.writerows(manifests)

with open('uploads.csv', 'w') as h:
c = csv.writer(h)
c.writerow(['report_id', 'upload_id'])
for k in upload_ids.keys():
for v in upload_ids[k]:
c.writerow([k, v])

其中 RPT-8fa848797c43fe24c3fa7860 有一个中文编码错误