2025高校网络安全管理运维赛

MISC

1、Phishing

先使用CHMunpacker获取到包含的文件

获取到文件后,发现是将一堆字节写入一个zip文件,我们利用脚本实现

# Retry extraction with a more robust pattern
import re, base64, zipfile
from pathlib import Path, PurePosixPath

html_path = Path("test.html")
out_zip = Path("reversed.zip")
extracted_dir = Path("reversed_extracted")
actions = []

html = html_path.read_text(encoding='utf-8', errors='replace')

m = re.search(r'var\s+command\s*=\s*"([^"]*)";', html, flags=re.DOTALL)
if not m:
    raise ValueError("Could not find var command = \"...\" in the HTML.")
cmd_str = m.group(1)
actions.append(f"Full command string length: {len(cmd_str)}")

# Try to extract the payload between 'echo ' and ' > reversed.zip'
m2 = re.search(r'echo\s+(.+?)\s*>\s*reversed\.zip', cmd_str, flags=re.DOTALL)
if not m2:
    # maybe no spaces before >
    m2 = re.search(r'echo\s+(.+?)>\s*reversed\.zip', cmd_str, flags=re.DOTALL)
if not m2:
    # fallback: remove leading 'echo ' and trailing ' > reversed.zip' if present by endswith checks
    if cmd_str.startswith("echo "):
        payload = cmd_str[5:]
        # remove trailing '> reversed.zip' if present
        payload = re.sub(r'\s*>\s*reversed\.zip\s*$', '', payload)
    else:
        payload = cmd_str
else:
    payload = m2.group(1)

payload = payload.strip()
actions.append(f"Extracted payload length: {len(payload)}")

# Attempt base64 decode of payload
b64 = re.sub(r'\s+', '', payload)
decoded = None
for pad in ("", "=", "==", "==="):
    try:
        decoded = base64.b64decode(b64 + pad, validate=True)
        actions.append(f"Base64 decode succeeded with padding '{pad}'. Decoded bytes: {len(decoded)}")
        break
    except Exception as e:
        pass

if decoded is None:
    actions.append("Base64 decode failed; trying latin-1 bytes fallback.")
    decoded = payload.encode('latin-1', errors='ignore')

out_zip.write_bytes(decoded)
actions.append(f"Wrote {out_zip} ({out_zip.stat().st_size} bytes)")

is_zip = False
try:
    with zipfile.ZipFile(out_zip, 'r') as zf:
        is_zip = True
        actions.append("reversed.zip is a valid zip. Contents:")
        for name in zf.namelist():
            actions.append(" - " + name)
        extracted_dir.mkdir(exist_ok=True)
        zf.extractall(path=extracted_dir)
except zipfile.BadZipFile:
    actions.append("reversed.zip is NOT a valid zip file (BadZipFile).")
except Exception as e:
    actions.append(f"Error trying to open zip: {e}")

print("=== ACTIONS ===")
for a in actions:
    print(a)

if is_zip:
    # search extracted files for flag patterns
    patterns = [r'FLAG\{.*?\}', r'flag\{.*?\}', r'CTF\{.*?\}', r'FLAG: ?\S+', r'flag: ?\S+']
    found = []
    for p in extracted_dir.rglob("*"):
        if p.is_file():
            b = p.read_bytes()
            for enc in ('utf-8','latin-1','cp1252'):
                try:
                    text = b.decode(enc)
                    break
                except:
                    text = None
            if text:
                for pat in patterns:
                    for mm in re.finditer(pat, text, flags=re.IGNORECASE|re.DOTALL):
                        found.append((p.name, mm.group(0)))
    if found:
        print("\nFound flag-like patterns:")
        for f in found:
            print(f" - {f[0]} : {f[1]}")
    else:
        print("\nNo flag-like patterns in extracted files.")
else:
    # If not zip, try checking decoded bytes for ZIP signature anywhere and extract that slice
    data = decoded
    idx = data.find(b'PK\x03\x04')
    if idx != -1:
        candidate = out_zip.parent / "embedded_part.zip"
        candidate.write_bytes(data[idx:])
        print(f"\nFound ZIP signature at offset {idx}, wrote to {candidate}")
        try:
            with zipfile.ZipFile(candidate, 'r') as zf:
                print("embedded_part.zip contents:", zf.namelist())
                zf.extractall(path=extracted_dir)
                print("Extracted to", extracted_dir)
        except Exception as e:
            print("Could not open embedded_part.zip:", e)
    else:
        print("\nNo ZIP signature found in decoded bytes.")

然后我们可以成功获取到一个损坏的ZIP文件,注意观察其十六进制文件,我们可以看到这个文件字节被反转了

我们来修复一下

# Try reversing the decoded bytes and checking for ZIP signature / opening as zip.
from pathlib import Path
import zipfile

in_path = Path("reversed.zip")
rev_path = Path("reversed_fix.zip")
data = in_path.read_bytes()
rev = data[::-1]
rev_path.write_bytes(rev)
print("Wrote reversed bytes to", rev_path, "size", rev_path.stat().st_size)

import zipfile, io
try:
    with zipfile.ZipFile(rev_path, 'r') as zf:
        print("rev zip contents:", zf.namelist())
        zf.extractall(path="reversed_extracted")
        print("Extracted to reversed_extracted")
except zipfile.BadZipFile as e:
    print("reversed_fix.zip is NOT a valid zip:", e)
    # check for ZIP signature in rev
    idx = rev.find(b'PK\x03\x04')
    print("ZIP signature index in reversed bytes:", idx)

修复成功,并且发现其中有一个文件filename.dll,我们继续提取

# Try reversing the decoded bytes and checking for ZIP signature / opening as zip.
from pathlib import Path
import zipfile

in_path = Path("reversed.zip")
rev_path = Path("reversed_fix.zip")
data = in_path.read_bytes()
rev = data[::-1]
rev_path.write_bytes(rev)
print("Wrote reversed bytes to", rev_path, "size", rev_path.stat().st_size)

import zipfile, io
try:
    with zipfile.ZipFile(rev_path, 'r') as zf:
        print("rev zip contents:", zf.namelist())
        zf.extractall(path="reversed_extracted")
        print("Extracted to reversed_extracted")
except zipfile.BadZipFile as e:
    print("reversed_fix.zip is NOT a valid zip:", e)
    # check for ZIP signature in rev
    idx = rev.find(b'PK\x03\x04')
    print("ZIP signature index in reversed bytes:", idx)

提取成功,由于DLL 是 Windows 的 PE 文件,内部所有「宽字符串」(wchar_t*在内存/文件中的字节序都是 UTF-16LE。所以我们用该编码搜索字符串

# Extract a larger UTF-16LE region around the found index and search for flag{...} pattern
from pathlib import Path, re
f = Path("reversed_extracted/filename.dll")
data = f.read_bytes()
# find utf-16le 'f\0l\0a\0g\0' index
idx = data.find(b'f\x00l\x00a\x00g\x00')
start = max(0, idx-500)
end = min(len(data), idx+2000)
seg = data[start:end]
text = seg.decode('utf-16le', errors='ignore')
print("Decoded utf-16le segment length:", len(text))
m = re.search(r'flag\{.*?\}', text, flags=re.IGNORECASE|re.DOTALL)
if m:
    print("Found flag:", m.group(0))
else:
    # print the decoded segment to inspect
    print("No full flag found by simple regex. Showing decoded segment:\n")
    print(text[:2000])

获得flag

2、DNS分身术

题目可知,本体的flag应该是在DNS解析中,我们先去查询,字符串大概率是TXT解析。

发现flag1和flag2,我们先查flag1

找到最后一段flag和告诉我们使用不同网络,我们使用ITdog,因为他有联通线路,找到中间段

最前一段需要教育网,那么我们指定客户端子网地址,使用上海交大的DNS,得到flag前段

所以得到flag1:flag{DNS_V1eW_1s_P0w3rfu1_1t_depends_0n_ECS_5o_we_gEt_The_wh01e_fl@g}

接下来flag2,同样方法,看到提示

发现只有受认证网络才能看到,那我们就使用172.32.255.255

然后再使用另一个授权地址获取到另一半flag即可

所以flag2:flag{Auth0r1z3d_N3tw0rk_W1th_TCP_Supp0rt}

3、网络运维小助手

本题属于大模型Prompt逆向工程,我们需要获取到两个安全密钥

解码得到第一个密钥 flag{S@y-the-w0rd}

然后获得第二个密钥,现在需要在开启知识库的情况下询问,我们可以利用第一个密钥获得管理员身份,然后读取第二个

flag{RAG-w3ll-h@cked}

Reverse

校园网络保卫战

flag1:这个flag是从远端获取flag,然后进行解密后与用户输入进行对比,若相同,则正确,我们先通过抓包得到具体请求地址,这里使用burpsute+proxifier的组合,设置过程就不赘述,在输入flag1后,抓取到请求地址

我们使用repeter重放请求获得密文

显然是一段乱码,不方便,我们转十六进制。然后我们找到加密逻辑并逆向。先是用百度做了连通性检查,然后携带请求头访问目标网址,那么接下来显然就是加密与对比,找到加密逻辑

注意到有异或,获取异或的对象内容byte_40A14C,解密即可

data = [0x24,0x59,0x19,0xc3,0xbd,0xc2,0xb6,0xc2,0x9d,0x27,0x43,0x1d,
        0xc3,0xa8,0xc2,0xbe,0xc5,0xa0,0x1d,0x58,0x1d,0xc3,0x85,0xc3,0xba,
        0xc2,0x8d,0x7b,0x56,0x49,0xc2,0xa9,0xc2,0xac,0xc3,0x9d,0x26,0x50,
        0x19,0xc3,0xbe,0xc2,0xaf,0xc5,0xa0,0x27,0x53,0x05]

key = [0x42,0x35,0x78,0x9A,0xCD,0xEF]

# 把给定字节当作原始字节流的 UTF-8 表示,先解码成 Unicode 字符
b = bytes(data)
s = b.decode('utf-8', errors='replace')

# 用每个 Unicode 字符的低 8 位 (ord(ch) & 0xFF) 作为原始字节(处理被多字节编码的情况)
orig = [ord(ch) & 0xFF for ch in s]

# XOR 还原明文
plain = bytes([orig[i] ^ key[i % len(key)] for i in range(len(orig))])
print(plain)               # 可见原始 bytes
print(plain.decode('utf-8', errors='replace'))  # 可读形式

虽然有不可见字符,但是很容易看出其为e,结束。

flag2:

第二题是硬编码的flag,查找字符串,发现flag2判断逻辑

通过sub_402270加密的数据,与&unk_40A120密文对比,若相同则对,那么就是说用&unk_40A120逆向即可得到flag,查看sub_402270函数逻辑,把这个当成密钥种子扩建成 256 字节的自定义 S 盒。

这个s盒是非标准的,是作者自己写的一个逻辑:以 16 字节种子为起点,循环 16 次,每次都算出一组新的 16 字节 → 拼成 256 字节数组 S[0…255]。

题目对flag进行了4层加密:

1、首先是逐字节和0x33进行异或

2、把字节当做s盒的下标,取出s[]当做密文字节,就是s盒替换

3、之后将结果循环右移3位

4、最后在xor (i – 86)&0xFF

找到种子

逆向得到S盒,从而解出flag

# Extract xmmword_40B330 from uploaded PE at VA 0x0040B330, build S and recover flag bytes.
# This runs in the notebook environment and reads malware.exe provided earlier.

from binascii import hexlify
import struct, sys, os

PE_PATH = 'malware.exe'
VA_target = 0x0040B330  # address from disassembly
IMAGE_BASE_HINT = 0x400000  # typical

if not os.path.exists(PE_PATH):
    raise SystemExit(f"PE file not found at {PE_PATH}")

data = open(PE_PATH, 'rb').read()

def read_u16(buf, off): return struct.unpack_from('<H', buf, off)[0]
def read_u32(buf, off): return struct.unpack_from('<I', buf, off)[0]

# Parse DOS header
if data[:2] != b'MZ':
    raise SystemExit("Not a PE (MZ header missing)")
e_lfanew = read_u32(data, 0x3C)
if data[e_lfanew:e_lfanew+4] != b'PE\x00\x00':
    raise SystemExit("Invalid PE signature")
# COFF header
machine = read_u16(data, e_lfanew+4)
num_sections = read_u16(data, e_lfanew+6)
size_optional = read_u16(data, e_lfanew+20)
optional_header_offset = e_lfanew + 24
magic = read_u16(data, optional_header_offset)
# determine 32/64-bit
is_pe32_plus = (magic == 0x20B)
if is_pe32_plus:
    image_base = read_u64 = struct.unpack_from('<Q', data, optional_header_offset+24)[0]
else:
    image_base = read_u32(data, optional_header_offset+28)
# But many disassemblers show VA as full 0x0040B330 with image base 0x400000.
# We'll compute RVA = VA - image_base
rva = VA_target - image_base
# Section headers start after optional header
section_table_off = optional_header_offset + size_optional
section_found = None
for i in range(num_sections):
    off = section_table_off + i*40  # IMAGE_SECTION_HEADER size
    name = data[off:off+8].rstrip(b'\x00').decode(errors='ignore')
    virtual_size = read_u32(data, off+8)
    virtual_address = read_u32(data, off+12)
    size_of_raw = read_u32(data, off+16)
    pointer_raw = read_u32(data, off+20)
    if virtual_address <= rva < virtual_address + max(virtual_size, size_of_raw):
        section_found = (name, virtual_address, virtual_size, pointer_raw, size_of_raw)
        break

if not section_found:
    raise SystemExit(f"Could not map VA 0x{VA_target:08X} (RVA 0x{rva:X}) to a section. Image base used: 0x{image_base:X}")

name, virt_addr, virt_size, ptr_raw, size_raw = section_found
file_offset = ptr_raw + (rva - virt_addr)
if file_offset + 16 > len(data):
    raise SystemExit("File too small to read 16 bytes at computed file offset")

xmm = data[file_offset:file_offset+16]
print("Found section:", name)
print("Image base (from optional header): 0x%X" % image_base)
print("RVA: 0x%X -> file offset: 0x%X" % (rva, file_offset))
print("Extracted xmmword_40B330 (hex):", hexlify(xmm).decode())

# Now reuse the transformation functions to build S and invert UNK
UNK = bytes([
  0x94, 0x58, 0xB2, 0x65, 0xE6, 0xF2, 0x42, 0xAF, 0x40, 0xBA,
  0xE7, 0x7C, 0xA8, 0x9E, 0xA6, 0x4A, 0xA9, 0xE6, 0xB5, 0xE0,
  0x77, 0x81, 0x32, 0x13, 0x0B, 0xD8, 0x57, 0x40, 0x2E, 0x7D,
  0x9B, 0x33, 0xD4, 0xBB, 0x16, 0x9E, 0xD0, 0xF1, 0x43, 0x79,
  0xCC, 0x7B, 0x47, 0x5D
])

def mm_cvtsi32_si128(x):
    return struct.pack('<I', x) + b'\x00'*12
def mm_shuffle_epi32(vec_bytes, imm=0):
    d0 = struct.unpack('<I', vec_bytes[:4])[0]
    return struct.pack('<I', d0) * 4
def mm_add_epi32(a_bytes, b_bytes):
    a = list(struct.unpack('<4I', a_bytes))
    b = list(struct.unpack('<4I', b_bytes))
    c = [ (a[i] + b[i]) & 0xFFFFFFFF for i in range(4) ]
    return struct.pack('<4I', *c)
def mm_unpacklo_epi16(a_bytes, b_bytes):
    a16 = list(struct.unpack('<8H', a_bytes))
    b16 = list(struct.unpack('<8H', b_bytes))
    out = []
    for i in range(0,4):
        out.append(a16[i]); out.append(b16[i])
    return struct.pack('<8H', *out)
def mm_unpackhi_epi16(a_bytes, b_bytes):
    a16 = list(struct.unpack('<8H', a_bytes))
    b16 = list(struct.unpack('<8H', b_bytes))
    out = []
    for i in range(4,8):
        out.append(a16[i]); out.append(b16[i])
    return struct.pack('<8H', *out)
def mm_srli_epi16(a_bytes, imm):
    a16 = list(struct.unpack('<8H', a_bytes))
    return struct.pack('<8H', *[(x >> imm) & 0xFFFF for x in a16])
def mm_slli_epi16(a_bytes, imm):
    a16 = list(struct.unpack('<8H', a_bytes))
    return struct.pack('<8H', *[((x << imm) & 0xFFFF) for x in a16])
def mm_and_si128(a_bytes,b_bytes):
    return bytes([a_bytes[i] & b_bytes[i] for i in range(16)])
def mm_add_epi8(a_bytes, b_bytes):
    return bytes([(a_bytes[i] + b_bytes[i]) & 0xFF for i in range(16)])
def mm_sub_epi8(a_bytes, b_bytes):
    return bytes([(a_bytes[i] - b_bytes[i]) & 0xFF for i in range(16)])
def mm_packus_epi16(a_bytes,b_bytes):
    a16 = list(struct.unpack('<8H', a_bytes))
    b16 = list(struct.unpack('<8H', b_bytes))
    out = []
    for x in (a16 + b16):
        if x < 0: x = 0
        if x > 255: x = 255
        out.append(x)
    return bytes(out)

def build_S(xmmword_bytes):
    v48 = bytearray(256)
    v7 = xmmword_bytes
    v46 = mm_shuffle_epi32(mm_cvtsi32_si128(0xC), 0)
    v9 = mm_srli_epi16(b'\xff'*16, 8)         # mask 0x00FF per 16-bit lane
    v44 = mm_shuffle_epi32(mm_cvtsi32_si128(0xF0F0F0F0), 0)
    v10 = mm_shuffle_epi32(mm_cvtsi32_si128(4), 0)
    v11 = mm_shuffle_epi32(mm_cvtsi32_si128(8), 0)
    for i in range(16):
        v12 = mm_add_epi32(v7, v10)
        v13 = mm_unpackhi_epi16(v7, v12)
        v14 = mm_unpacklo_epi16(v7, v12)
        v15 = mm_add_epi32(v46, v7)
        t1 = mm_unpacklo_epi16(v14, v13)
        t2 = mm_unpackhi_epi16(v14, v13)
        v16 = mm_unpacklo_epi16(t1, t2)
        v17 = mm_add_epi32(v7, v11)
        v18 = mm_unpacklo_epi16(v17, v15)
        v19 = mm_unpackhi_epi16(v17, v15)
        a = mm_and_si128(v16, v9)
        t3 = mm_unpacklo_epi16(v18, v19)
        t4 = mm_unpackhi_epi16(v18, v19)
        b = mm_and_si128(mm_unpacklo_epi16(t3, t4), v9)
        v20 = mm_packus_epi16(a, b)
        v21 = mm_add_epi8(v20, v20)
        v22 = mm_add_epi8(v20, mm_add_epi8(v21, v21))
        const2A = mm_shuffle_epi32(mm_cvtsi32_si128(0x2A2A2A2A), 0)
        temp = mm_and_si128(mm_slli_epi16(v22,4), v44)
        temp2 = mm_add_epi8(v22, temp)
        out = mm_sub_epi8(const2A, temp2)
        v48[i*16:(i+1)*16] = out
        v7 = mm_add_epi32(v7, mm_shuffle_epi32(mm_cvtsi32_si128(0x10), 0))
    return bytes(v48)

def rol8(x, n):
    return ((x << n) & 0xFF) | ((x >> (8-n)) & 0xFF)

def invert_transform(target_bytes, S):
    invS = [0]*256
    for i,v in enumerate(S):
        invS[v] = i
    n = len(target_bytes)
    res = bytearray(n)
    for i, b in enumerate(target_bytes):
        t = b ^ ((i - 86) & 0xFF)
        t = rol8(t, 3)            # inverse of ROR3
        j = invS[t]              # find preimage
        res[i] = j ^ 0x33
    return bytes(res)

S = build_S(xmm)
unique = len(set(S))
print("Generated S unique values:", unique)
if unique != 256:
    print("S is not a full permutation; aborting inversion to avoid wrong result.")
else:
    flag = invert_transform(UNK, S)
    print("Recovered flag bytes (hex):", hexlify(flag).decode())
    try:
        print("Recovered flag (utf-8):", flag.decode('utf-8'))
    except:
        print("Recovered flag (latin-1):", flag.decode('latin-1'))

获得flag

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇