2024年NSSCTF秋季招新赛(校外赛道)WP

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

2024 NSSCTF 秋季招新赛(校外赛道)

在这里插入图片描述

咱说白了就是喜欢招新赛,因为说什么呢,因为就是什么呢,他就符合菜鸡的身份,ok啦家人们,菜鸡又来力!重铸荣光,吾辈义不容辞!



前言

我是菜鸡,做出来的真不多,凑合看吧,有问题还请大佬们海涵。


一、WEB

PS:
尴尬比赛结束了,环境开不了了(要开要我爆米),草率了,简单的题做的时候没记录,就把记录了的搞这里吧,决定就这样了!(菜鸡都觉得简单,说白了写了也没啥价值说是0。o)

(1)怎么多了个没用的PHP文件

题目描述:
notion.php 是一个没啥用的php文件,不知道被设计出来是干什么的,出题人傻啦?
在这里插入图片描述
我一看,文件上传的功能点,随便传点啥,这里先传个png吧
在这里插入图片描述
可以成功上传,可以正常查看图片,抓包改内容和后缀呢
在这里插入图片描述
寄了,试了下来是黑名单过滤,可以尝试改phtml等上传,可以上传,但是发现解析不了。这时候想到题目有提示了一个notion.php文件,他在uploads目录下面,好兄弟我直接就想到用.user.ini进行文件包含了
在这里插入图片描述

.user.ini 是⼀个⽤户⾃定义的php.ini ⽂件,会在其所在的当前⽬录⽣效,优先级⾼于php.ini 
在user.ini中写⼊以下内容 
auto_prepend_file = <filename>         
auto_append_file = <filename>          
写⼊其中⼀个即可
//包含在⽂件头 
//包含在⽂件尾 
<filename>就写成需要包含的⽂件名,后缀任意 
上传后,该⽬录下的php⽂件就会⾃动包含<filename> 

就是说我们先传一个.user.ini,然后让我们后面上传的png写一句话马包含到notion.php里就可以了,然后蚂剑连就ok了
在这里插入图片描述

(2)未选择的路

题目描述:到底选哪条路捏?我又该通往哪里呢?

<?php
 //
⼀⽚森林⾥分出两条路
————
⽽我选择了⼈迹更少的⼀条,从此决定了我⼀⽣的道路。
Include('check.php');
 highlight_file(__FILE__);
 error_reporting(0);
 $A=$_GET['easy'];
 $B=$_GET['hard'];
 if (isset($A)){
 eval('e'.'x'.'i'.'t'.'(); ?>'.$A.'<?php ;');//
这条路没有任何过滤诶,是不是好⾛
⼀些
}
 if (isset($B)){
 check($B);//
要被正则了,嘤嘤嘤
eval("#cmd".$B."inject");//
这条路怎么还要禁我东⻄啊,真下头
}

读下代码,输个hard=system试一下咯。
在这里插入图片描述
返回提示说passthru和system都禁⽤了,但是其他都能⽤,用反引号加?>闭合的方法哈,payload这样写
?hard=?><?php echo `id`;?>
在这里插入图片描述
成功RCE,就美滋滋的去拿flag吧

(3)Maxser Revenge

题⽬描述:T师傅很⽣⽓,因为在引导题⼏乎没有新⽣⽤正常思路做出来这道题,于是T师傅改了改⼜
把它拿上来了,不知道⼩东⻄们⼆周⽬还能不能给它拿下。

<?php
 highlight_file(__FILE__);
 error_reporting(0);
 include('check.php');
 class passthru{
 public $S;
 public $dir;
 public function __wakeup(){
 $this->dir='notion';
 }
 public function __destruct(){
 eval($this->S);
 }
 }
 $a=$_GET['NSS'];
 check($a);
 unserialize($a);

很明显php反序列化,只有⼀个passthru类,需要触发passthru类中的eval函数 但是传⼊的序列化字符串会被经过check函数。试一下check了些啥。经过⼀顿折腾啊,后发现修改序列化字符串中的第⼀个属性的名称为数字时就可以通过check。然后修改成字⺟下划线和⼩括号时就会被正则。
所以,我们可以猜测check函数检测的就是序列化字符串中的第⼀个属性的名称。到这⾥思路就清晰了,只需要把第⼀个属性名称写成数字,然后再在序列化字符串后⾯添加S属性即可 因为代码中最后执⾏eval的是S属性的值。构造payload为:NSS=O:8:"passthru":3{s:1:"1";s:11:"system(id);";s:3:"dir";N;s:1:"S";s:10:"echo `id`;";}
在这里插入图片描述
成功RCE,然后愉快的去拿flag吧!

(4)The future Revenge

题⽬描述:做不来?那就跟我玩躲猫猫游戏吧,找到我了我就跟你说。话说那个真的只是⼀个⼲扰项
吗?

没看懂,但是菜鸡搜索能力一流,找到了他考的点CVE-2024-2961,有现成的exp梭哈,感兴趣的可以下来自行研究,这里挂出几个大佬的参考文章:
https://err0r233.github.io/posts/28510.html
https://blog.csdn.net/jennycisp/article/details/140148391
我也没太整明白就不多献丑了。

(5)签到

题⽬描述:
卷了这么久了,来签个到吧

(做之前说一句,出题人你没良心啊,说叫签到搞那么难)
进⼊首页,页面有两个功能点:
sign in和show signed-in users
在这里插入图片描述
点击sign
在这里插入图片描述
正常填写注册信息
最后⼀个表单要求填博客url地址,这⾥猜测存在ssrf,试一下。
在这里插入图片描述
我当时一下就激动了啊,用file协议库库读flag,读半天没找到,那就读出他的源码,审源码。主要有四个源码index.phpsubmit.phpsave_user.phpshow_blog.php,重点是后三个嘛。太长了我就不贴出来了,大家做的时候也可以file协议整出来读一下file://///var/www/html/ 在这个路径下面。
跟着功能走一遍代码:

  1. 点击注册功能后先触发submit⽂件,先读submit
  2. 先通过POST接收⽤户填写的注册信息,接着把$username$nickname$platform这三个变量传入chack对象中,接着跟进查看check类
  3. 通过构造方法法给$name变量赋值,并且传入到isValidname方法中,该方法对传入的值进行过滤,只允许传⼊数字字母下划线和空格,该类中的其他方法就是返回$name的字符串变量
    $username$nickname$platform这三个变量都被check类进行了过滤,但是还有⼀个profile变量没有被check,它是直接进行赋值然后被带入下⼀步。(这里就是其中⼀个利用的关键点 )
  4. 接着在下⾯有⼀个对blog值进⾏检测的判断,把blog的值传⼊validateURL方法,该方法法使用了php内置的filter_var方法,该方法中的FILTER_VALIDATE_URL参数会判断传⼊的值是否为⼀个合法的url,如果是就返回true。
    所以我们最后知道的是什么呢?我们可控的5个变量中,$username$nickname$platform这三个只能写字母数字下划线空格,$profile量可以写任意字符,$blog必须写成合法的url。
    接着读发现以上的五个变量经过处理之后被赋值到$user这个数组中,然后对这个数组进行序列化操作,最后把序列化字符串写到data/user_data.txt文件中。访问一下这个文件。

在这里插入图片描述
接下来,就会去读取data/user_data.txt文件,也就是$user序列化的内容 ,接着定义了两个数组,然后把$user序列化的内容进行过滤,过滤方式是如果序列化字符串中存在$search数组中的值时,替换成error ,这里就很容易想到可以利用字符串逃逸接下来对处理过的序列化字符串进行反序列化,并赋值给$user ,接下来先判断$user是否为⼀个数组,若不是就die 然后创建⼀个data/user_profiles目录 读取$user数组中的usernameblog的值,并且赋给$username$blog这两个变量 最后把这两个值写⼊到data/user_profiles目录下的$username.txt文件中。
show_blog.php文件中,⼀开始就会去读取data/user_profiles⽬录下$username.txt文件,其中的
$filename变量是可控的,但是经过了basename的处理,不能进行穿越,所以没有利⽤点。这⾥的$filename其实就是我们写入的username的值 。
接下来进入第⼆个if判断,里面很明显有⼀个include函数,会包含data/user_profiles⽬录下
$filename.txt文件,而该文件中就是我们传⼊的usernameblog这两个变量的值,这里就出现了文件包含漏洞,因为可控变量可以入侵到代码的执行流中在include上⾯有⼀个ob_start()函数,该函数的作用是开启缓冲区,该函数执行以后,后面php代码的执行结果就会被输出到缓冲区中,而在include下⾯有⼀个ob_get_clean函数,该函数的作用是把缓冲区的内容输出,然后关闭缓冲区。也就是说,include执行的结果会被赋值给$fileContent变量,而该变量进⼊到下⾯的正则匹配最后把匹配到的内容经过htmlspecialchars实体化后输出到页面。

思路:给username赋值为php代码--->include⽂件包含执⾏ 
由于在前端输⼊username时会被过滤,要求只能输⼊字⺟数字下划线,但是别忘记了,它留了⼀个
$profile变量没有进⾏过滤,可以传任何字符,再加上后⾯会对序列化字符串的处理,所以绕过
username校验的⽅法就很明显了,通过字符串逃逸给username赋值就可以绕过chack类的检测,最后
再访问show_user.php这个⽂件来触发include去包含我们写⼊的php代码 

payload构造如下:

username=1&nickname=a&platform=systemsystemsystemsystemsystemsystemsystemsystemsy
 stemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsy&pr
 ofile=;s:8:"username";s:17:"<?php `ls>a.php`;&blog=file:////etc/passwd 

在这里插入图片描述
访问show_user.php ,点击访问,触发include
在这里插入图片描述
在这里插入图片描述
触发成功,访问a.php
在这里插入图片描述
成功查看执行结果
可见在当前目录下有⼀个readflag.sh
同样方式执行readflag.sh,然后把结果写入a.php ,
payload:

username=1&nickname=a&platform=systemsystemsystemsystemsystemsystemsystemsystemsy
 stemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsystemsy&pr
 ofile=;s:8:"username";s:29:"<?php `sh readflag.sh>a.php`;&blog=file:////etc/passwd

访问a.php即可得到flag。

小结

总结一下吧,web咱也是全造了,其他的都不难(其实是叫我爆米重新开环境不可能)。所以就记录这些吧。

二、PWN

最菜中之菜的一集,只做了一个签到环境也开不了,不写了。

三、REVERSE

(1)又是签到!?

没啥好说的安卓直接就是jadx打开,最喜欢的一集
在这里插入图片描述

(2)不是,哥们,还有签到!?

没啥好说的直接就是ida打开看flag,最喜欢的一集x2
在这里插入图片描述

(3)怎么才能看见flag呢

没啥好说的直接就是ida打开看flag,最喜欢的一集x3
在这里插入图片描述

(4)签到?

打开ida,我们先搜一波string,有个Corret! We are the champions!
在这里插入图片描述
跳过去看一下
在这里插入图片描述
在根据这个去跳其他关键的部分
在这里插入图片描述
简单读一下写代码跑即可

enc = [0x20, 0x27, 0x26, 0x25, 0x2C, 0x2D, 0x0F, 0x22, 0x14, 0x1E, 0x21, 0x18, 0x09, 0xDF, 0xC8, 0x1C, 0xE7, 0x05, 0xE5, 0xE2, 0xEE, 0x1A, 0xE6, 0x04, 0xD9, 0xC9, 0xE3, 0x0A, 0xF5,
                0xF1,0xF8, 0xF3, 0xFA, 0xEA, 0xFF, 0xE7, 0xF5, 0xB9, 0xE4]
key = [0x6e,0x73,0x73,0x63,0x74,0x66]
for i in range(len(enc)):
    print(chr(enc[i]^key[i%6]+i),end="")

在这里插入图片描述

(5)这也是py! ?

给到的是一个pyc的字节码,还原成py形式

import dis
 
a = '~hojutfsfuoJ`pt`th^dcnbdsxAzESBRRM'
b = [0] * 59
 
if __name__ == '__main__':
    print('PLZ input your flag: ')
    c = input()
 
    for i in range(17):
        b[i] = ord(c[33 - i]) + 1
        b[33 - i] = ord(c[i]) - 1
 
    for i in range(34):
        if b[i] != ord(a[i]):
            print('Wrong!!!')
            exit(0)
 
    print('Great!!!')

非常简单的解密,直接写代码

a = '~hojutfsfuoJ`pt`th^dcnbdsxAzESBRRM'
b = [0] * 59
 
for i in range(34):
    b[i] = ord(a[i])
 
c = [''] * 34
 
for i in range(17):
    c[33 - i] = chr(b[i] - 1)
    c[i] = chr(b[33 - i] + 1)
 
flag = ''.join(c)
print("Decrypted flag:", flag)
#NSSCTF{Bytecode_is_so_Interesting}

(6)web

首先,开环境看一下是个python flask的页面,给出了一个加密后的flag结果,把源码下下来,了解过flask的都知道主要的是app.py这个脚本,flag.py也看一下。
关键的是这段return (lambda f: (lambda e_f: jsonify({"result": "Right"}) if request.method == "POST" and (u := request.form.get("user_input")).startswith("NSSCTF{") and u.endswith("}") and f(u) == e_f else jsonify({"result": "Wrong"}) if request.method == "POST" else render_template("index.html", encrypted_flag=e_f))(f(flag)))(lambda t: "".join([hex(((int.from_bytes(b + b'\x00' * (8 - len(b)), byteorder='big') ^ KEY) ^ ((KEY >> 7) | (KEY << 5))) & 0xFFFFFFFFFFFFFFFF).upper()[2:] for b in [t.removeprefix("NSSCTF{").removesuffix("}").encode('utf-8')[i*8:(i+1)*8] for i in range((len(t.removeprefix("NSSCTF{").removesuffix("}").encode('utf-8'))+7)//8)]]))
我们可以分析出他的加密逻辑,这里我给出一个python写的对应加密过程

def encrypt(flag, key=0x4c494e):
    # 移除 NSSCTF{ 和 } 并转换为字节串
    start_index = flag.find("{")
    end_index = flag.rfind("}")
    if start_index >= 0 and end_index > start_index:
        plain_text = flag[start_index:end_index+1].encode('utf-8')
    else:
        raise ValueError("Invalid flag format, expected 'NSSCTF{...}'")

    # 分割字节串为8字节的块
    blocks = [plain_text[i*8:(i+1)*8] for i in range((len(plain_text)+7)//8)]
    
    # 加密每个块
    encrypted_blocks = [
        hex(
            (
                (
                    int.from_bytes(b + b'\x00' * (8 - len(b)), byteorder='big') ^ key
                ) ^ ((key >> 7) | (key << 5))
            ) & 0xFFFFFFFFFFFFFFFF
        )[2:].upper()
        for b in blocks
    ]
    
    # 合并所有加密块为单个字符串
    return "".join(encrypted_blocks)

# 示例使用
if __name__ == "__main__":
    flag = "NSSCTF{example_flag}"
    encrypted_flag = encrypt(flag)
    print(f"Original Flag: {flag}")
    print(f"Encrypted Flag: {encrypted_flag}")

那么只需要对应加密过程写出相应解密流程就可以得解了,解密代码如下

def decrypt(encrypted_flag, key=0x4c494e):
    plain_text_blocks = [
        bytes.fromhex(encrypted_block)
        for encrypted_block in [encrypted_flag[i*16:(i+1)*16] for i in range(len(encrypted_flag)//16)]
    ]
    
    decrypted_blocks = [
        (
            (
                int.from_bytes(block, byteorder='big') ^ ((key >> 7) | (key << 5))
            ) ^ key
        ).to_bytes(8, byteorder='big').rstrip(b'\x00')
        for block in plain_text_blocks
    ]    
    decrypted_text = b''.join(decrypted_blocks).decode('utf-8')
    
    return f"NSSCTF{{{decrypted_text}}}"

if __name__ == "__main__":
    encrypted_flag = "323534393CF6C8AF2D3639353FE8C4A961352D613CF5C1B13631376230FC95AA6263636609C5F09C"
    decrypted_flag = decrypt(encrypted_flag)
    print(f"Decrypted Flag: {decrypted_flag}")

在这里插入图片描述
顺利得解咯

小结

逆向确实难,会得不多

四、CRYPTO

(1)Aftermath

题目:

from Crypto.Util.number import getPrime, inverse, long_to_bytes, bytes_to_long
from sympy import gcd
import gmpy2



key_size = 1024

p = getPrime(key_size // 2)
q = getPrime(key_size // 2)

N = p * q
phi = (p - 1) * (q - 1)
e1 = 3
e2 = 7

message = b"NSSCTF{...............................................}"

m = bytes_to_long(message)

assert gcd(m, N) == 1

c1 = pow(m, e1, N)
c2 = pow(m, e2, N)

print(f"N = {N}")
print(f"e1 = {e1}")
print(f"c1 = {c1}")
print(f"e2 = {e2}")
print(f"c2 = {c2}")

e很小,我们首先应该想到立方根攻击中的一个情况(即能否使用gmpy2.iroot函数来直接求解m)

N = 80722936701364382749961243326484006977187702986017980842794443374132452156776306032868217795522046975068822236770836452911408536092460646410756678157902792329645719935468879960944028782788489463895870961967670931567205550383999951787250211085264314795753745003815839218062934564501884684565508432346164094171
e1 = 3
c1 = 77027474990431732719325428265107176934045610651944725251406683442684093440239073195437770144166442593914418380343458827052860752131667771506129334676070396374008929588455988149871039697387983766750148969695215583137356681988572655848921827794639096404716760310059622671470680330144220097050812716421370445797
e2 = 7
c2 = 13491956530007991248882899018888359080930858500993821006822695375714947537976202424265808646466853291165511243721829370428583392329886743499827454177786585477285598196204906977043127274613692623229137936467994670727274820568522666762615055848367486507714640497446688083840123758417442971555904294548595887600

m = gmpy2.iroot(c1,e1)[0]#c2,c2也可以试
print(long_to_bytes(m))

遗憾的失败了。
在这里插入图片描述
再看到有多组e,c,一眼丁真,共模攻击咯

import gmpy2
from Crypto.Util.number import getPrime,long_to_bytes
 
n = 80722936701364382749961243326484006977187702986017980842794443374132452156776306032868217795522046975068822236770836452911408536092460646410756678157902792329645719935468879960944028782788489463895870961967670931567205550383999951787250211085264314795753745003815839218062934564501884684565508432346164094171
e1 = 3
c1 = 77027474990431732719325428265107176934045610651944725251406683442684093440239073195437770144166442593914418380343458827052860752131667771506129334676070396374008929588455988149871039697387983766750148969695215583137356681988572655848921827794639096404716760310059622671470680330144220097050812716421370445797
e2 = 7
c2 = 13491956530007991248882899018888359080930858500993821006822695375714947537976202424265808646466853291165511243721829370428583392329886743499827454177786585477285598196204906977043127274613692623229137936467994670727274820568522666762615055848367486507714640497446688083840123758417442971555904294548595887600

_,s1, s2 = gmpy2.gcdext(e1, e2)
m = pow(c1, s1, n) * pow(c2, s2, n) % n
print(long_to_bytes(m))

运行即可得到结果。
在这里插入图片描述

(2)完全感觉Dreamer

题目:
GyAGD1ETr1EbnKZtnKZtoKxto3qhVTc1MTqyoJIhqPRuVRqiqPOho3EbnJ5aVUEiVUAurK0=

我的评价是cyberchef一把嗦
在这里插入图片描述

(3)看懂了就来拿flag吧

看似很难,实际上很简单(在送),给的代码就是实现了一个AES的加解密,明文内容其实已经给了,没错就是那么草率。

from Crypto.Cipher import AES
import base64

class Encrypt:
    def __init__(self,key,iv):
        self.key = key.encode('utf-8')
        self.iv = iv.encode('utf-8')

    def pkcs7padding(self, text):
        bs = 16
        length = len(text)
        bytes_length = len(text.encode('utf-8'))
        padding_size = length if (bytes_length == length) else bytes_length
        padding = bs - padding_size % bs
        padding_text = chr(padding) * padding
        self.coding = chr(padding)
        return text+padding_text

    def aes_encrypt(self, content):
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        content_padding = self.pkcs7padding(content)
        encrypt_bytes = cipher.encrypt(content_padding.encode('utf-8'))
        result = str(base64.b64encode(encrypt_bytes),encoding='utf-8')
        return result

    def aes_decrypt(self, content):
        cipher = AES.new(self.key, AES.MODE_CBC, self.iv)
        content = base64.b64decode(content)
        text = cipher.decrypt(content).decode('utf-8')
        return text.rstrip(self.coding)

if __name__ == '__main__':
    iv = '01pv928nv2i5ss68'
    key = '53f09k56nv2b10cf'
    a = Encrypt(key=key, iv=iv)
    e = a.aes_encrypt('{12evw_utker_5ythgf}')
    decrypted = a.aes_decrypt('ZZWm1bsah1+SuT8N5o2wBwbjelbHE9H711ydVqrg4TI=')
    print(decrypted)
    print(e)

e = a.aes_encrypt(‘{12evw_utker_5ythgf}’)就是要加密的内容为{12evw_utker_5ythgf}那flag就是这个咯,所以flag为NSSCTF{12evw_utker_5ythgf}。最喜欢的一集 x N。

(4)泰坦陨落2

这道题是一个xor和lcg的题
先看1.领取你的飞天克莱伯里面的妙蛙种子

# LCG参数
a = 1664525
c = 1013904223
m = 2**32

# 妙蛙种子
seed = .........

# 生成伪随机数
def lcg(seed, n):
    numbers = []
    for _ in range(n):
        seed = (a * seed + c) % m
        numbers.append(seed)
    return numbers

# 给你5个伪随机数
random_numbers = lcg(seed, 5)
print(random_numbers)
#[3771924608, 3319331295, 583630258, 2401321321, 611326900]

我们需要爆破的就是seed

# LCG参数
a = 1664525
c = 1013904223
m = 2 ** 32
random_numbers = [3771924608, 3319331295, 583630258, 2401321321, 611326900]

# 计算a的逆元
def modinv(a, m):
    m0, x0, x1 = m, 0, 1
    if (a == 1): return 1
    while (a > 1):

        q = a // m
        t = m

        m = a % m
        a = t
        t = x0
        x0 = x1 - q * x0
        x1 = t

    if (x1 < 0): x1 = x1 + m0
    return x1

# 逆向生成伪随机数
a_inv = modinv(a, m)

def reverse_lcg(numbers):
    seeds = [numbers[-1]]
    for num in reversed(numbers[:-1]):
        seed = (num - c) * a_inv % m
        seeds.append(seed)
    return seeds[::-1]  # 返回反转后的列表以得到正确的顺序

# 反向得到seed
seeds = reverse_lcg(random_numbers)
original_seed = seeds[0]
print(original_seed)

得到seed后我们看FLAG.txt,他其实就是一个xor加密,我们知道xor是可逆的,我们只需要再跑一遍脚本就可以得到结果。

def xor_encrypt_decrypt(data, key):

    key_bytes = key.to_bytes((key.bit_length() + 7) // 8, 'big')
    key_length = len(key_bytes)

    result = bytearray()
    for i in range(len(data)):
        result.append(data[i] ^ key_bytes[i % key_length])

    return bytes(result)

flag = "NSSCTF{........................................}"

flag_bytes = flag.encode('utf-8')

key = 543257189
encrypted_bytes = xor_encrypt_decrypt(flag_bytes, key)

# 输出加密后的字节和字符串
print("Encrypted Bytes:", encrypted_bytes)
#Encrypted Bytes: b"n2!&t'\t\x06A\x14\x01\x00\x00\x16\x17EA\x13\x17ET\t\x17EC\x0e\x1e\nR\x12R\x0cNA\x06\rEA\x16\x04R\n\x0f"

# 已知的加密后的字节串
encrypted_bytes = b"n2!&t'\t\x06A\x14\x01\x00\x00\x16\x17EA\x13\x17ET\t\x17EC\x0e\x1e\nR\x12R\x0cNA\x06\rEA\x16\x04R\n\x0f"

# 解密使用的密钥
key = 543257189

# 调用解密函数
decrypted_bytes = xor_encrypt_decrypt(encrypted_bytes, key)

# 将解密后的字节串转换为字符串
flag = decrypted_bytes.decode('utf-8')

print("Decrypted Flag:", flag)

运行即可得到结果
在这里插入图片描述

(5)拿脚本跑不出来了吧

直接看一下他给的条件,给了e1*e2,c1,c2,还是共模攻击哦,只是是个变种,也是直接上脚本。

n= 16282992590526808657350657123769110323293742472515808696156540766049532922340638986423163288656942484229334024198335416611687418341772216996129634991032127943095069143600315325916614910606100091970611448259491799589221889445348698100959509165262891180065554743420149168801638644589921791426690475846945077068114953844817073866258377206796158690941199907230130273657375727245023893672164113928189304228859412794067127721813637080447782673535996272223836127807775157150041664783263093604946744032762535394974814371771505843653571711445892969781888188805943142126747365056482511805191315474848971218180999336497135314654469910566730389765499603897685968204361422568601724914800686608628299192714352963744010136960423806304763245890692476493455775025753944860040020178234660999290356849442926396627701588938894161779071628447041006556793933320976506046066961014953196791133933438500843139378274786265308568167479880984705152809744111382599071097574636570516674122980589207824718402382459624138317432883921371298272851693734695823787102433937406420318428888224246291987404818042038201886113203158444083427668636941
c1= 15508846802476602732219982269293312372397631462289816533805702700260237855119470146237752798828431803179124957728439730580289236458563016332461725094295883030444173189424666004498359269921250956676320570006883951982237098373954348825003467019876101438948387668628518937831820206221522881150831840296199498447304138839838135264071071817072965792514115711621435317078108239744829134467948386247696344881838815422262901903767893118533887779588425725845820071451782420200868341564360095012698956683395031351656817392008005928265838760875070634021907630535014959579709368637536268853337028760833769278841040734409299575870823873616769863828516877971432999417800417684146077045836940988096634144368727546539602310924702126212020003620219218637652874119299016382481718659448722433296761241365473608283436835986184098161365747699791248301452334044327014782249692551362625130537300221641910570569803981153117200694806974917501061411963827755822672178568783269357196133308719688843211664095412087717861154226475203597889635926903753481174280305996204091501578865951177135086807765873529089048911740160698421289371229606
c2= 7038544062804420883340530319534054090343999593726615071597649914714397773106261660516938820194721330117082799104642674913839235601210294807255855747823709326405317366422536981850436536877639492293904186333547681934006229055311359852552059601531864585759120757265084674695094298158389804437120173997679271166467086009884419942249925895393890707373985126949313101489352481737754459985522998334847972008827503987883850638250024631354158979424169551575287515128697843093987592614974905262077415255065744686115142126350167970451060399517705823298929164793769442986603707135790651560436497661713972277808036463771768932747376668116480068277125579165831615220097562066809632099809702980365194257899499384219864311379004681733844738981954144617140038448109869114888325128710654235506628539192955240723379334422880368605005772426413018696218105733457019400100498450734710865067764542737004071080719589912326985050985424145053072697267879019954400205613591419766583673115931337146967400159040252514654983240188915104134405655336152730443436887872604467679522955837013574944135975481174502094839012368918547420588186051
e1e2= 59653
 
import libnum
import gmpy2
 
def rsa_gong_N_def(e1,e2,c1,c2,n):  #共模攻击函数
    e1, e2, c1, c2, n=int(e1),int(e2),int(c1),int(c2),int(n)
    print("e1,e2:",e1,e2)
    s = gmpy2.gcdext(e1, e2)
    print("mpz:",s)
    s1 = s[1]
    s2 = s[2]
    if s1 < 0:
        s1 = - s1
        c1 = gmpy2.invert(c1, n)
    elif s2 < 0:
        s2 = - s2
        c2 = gmpy2.invert(c2, n)
    m = (pow(c1,s1,n) * pow(c2 ,s2 ,n)) % n
    return int(m)
 
def de(c, e, n): #因为此时的m不是真正的m,而是m^k,所以对m^k进行爆破
    k = 0
    while k<1000: #指定k小于1000
        mk = c + n*k
        flag, true1 = gmpy2.iroot(mk, e)  #返回的第一个数值为开方数,第二个数值为布尔型,可整除为true,可自行测试
        if True == true1:
            # print(libnum.n2s(int(flag)))
            return flag
        k += 1
for e1 in range(2,e1e2):
    if e1e2%e1==0:         #爆破可整除的e
        e2=e1e2//e1
        c=rsa_gong_N_def(e1, e2, c1, c2, n)
        e=gmpy2.gcd(e1,e2)
        m1=de(c, e, n)
        if m1:  #指定输出m1
            print(libnum.n2s(int(m1)))

直接可以得到结果:
在这里插入图片描述

(6)Take what you want

题目如下
5gb 87yjnb

5tgbyujm 6yhnuik, jkiuhnm

fgtrdcv bgtyujm hgvbhu

ytfvbhn

ytfvb 6tfcyuhb ytfvbhn tgbyujm ytfvbjkiuhnm

rtygb tfvbhy

76thbv rtygb ytfvbhn 5tgby78 tyuhn

uygbnjm 76thujnbv resxcfv 7jm tgbyujm
是古典密码,提示了键盘密码了,我们就在按他给到的在键盘上连线一个个找对应的小写字母,就是有点花时间在键盘上描字母。没有什么难度。(狗的地方是他错了一个字母hhe改为the,交了百八十遍不对差点自闭)
NSSCTF{is_the_end_a_chance_to_start_again}

(7)Bury the light

(整鬼泣的活,家族传捅是吧!!)

看一下题,是道lsfr的题目,原理有兴趣可以下来了解一下,给了几乎全部代码和key所以不算难

from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
from Crypto.Util.Padding import unpad


mask1 = 131151158277430590850506190902325379931
mask2 = 234024231732616562506949148198103849397
mask3 = 175840838278158851471916948124781906887
mask4 = 270726596087586267913580004170375666103

def lfsr(R, mask):
    R_bin = [int(b) for b in bin(R)[2:].zfill(128)]
    mask_bin = [int(b) for b in bin(mask)[2:].zfill(128)]
    s = sum([R_bin[i] * mask_bin[i] for i in range(128)]) & 1
    R_bin = [s] + R_bin[:-1]
    return (int("".join(map(str, R_bin)), 2), s)

def ff(x0, x1, x2, x3):
    return (x0 ^ x1 ^ x2 ^ x3) & 1

def round(R, R1_mask, R2_mask, R3_mask, R4_mask):
    out = 0
    R1_NEW, _ = lfsr(R, R1_mask)
    R2_NEW, _ = lfsr(R, R2_mask)
    R3_NEW, _ = lfsr(R, R3_mask)
    R4_NEW, _ = lfsr(R, R4_mask)
    for _ in range(300):
        R1_NEW, x1 = lfsr(R1_NEW, R1_mask)
        R2_NEW, x2 = lfsr(R2_NEW, R2_mask)
        R3_NEW, x3 = lfsr(R3_NEW, R3_mask)
        R4_NEW, x4 = lfsr(R4_NEW, R4_mask)
        out = (out << 1) + ff(x1, x2, x3, x4)
    return out

# 给定的密钥
key = 268498734989386806140712175125788827088
out = round(key, mask1, mask2, mask3, mask4)

# 解密部分
cipher = AES.new(long_to_bytes(key), AES.MODE_ECB)
encrypted_flag = b'g\xf6\xc8\x1d\x1ap\xb9\xefd\xcc\xf0t\xe8/O\x7f\x89\xa3l \x8bR[\x91\xddd\x11\x98tA\x12\xcc\xa5Jl\x08\xd7\x87\xa2M\x1c\xe46rm\x16\x9b('
decrypted_flag = cipher.decrypt(encrypted_flag)
unpadded_flag = unpad(decrypted_flag, 16)
print(f"Decrypted flag: {unpadded_flag.decode()}")

运行得到结果
在这里插入图片描述

(8)flag_where_revenge

给了压缩包,我们先看task.py

from Crypto.Util.number import *
import gmpy2
flag1=b'******'

p = getPrime(512)
q = getPrime(512)
n = p*q
e = 3
m1 = bytes_to_long(flag1)
a = getPrime(128)
b = getPrime(128)
m2 = a*m1 + b

hint1 = a**2 + a*b 
hint2 = b**2 + b*a #(hint1+hint2)=(a+b)^2 (a+b)(a-b)

c1 = pow(m1, e, n) #c1 = m1^3 mod n
c2 = pow(m2, e, n) #c2 = a^3*m1^3 + b^3 mod n

print(f'n = {n}')
print(f'hint1 = {hint1}')
print(f'hint2 = {hint2}')
print(f'c1 = {c1}')
print(f'c2 = {c2}')

乍一看有点难,我们捋一捋已知条件咯。m1是flag1,我们知道e,两个素数a,b,m2和m1的线性关系m2 = a*m1 + b
先求a,b

h = gmpy2.iroot(hint1+hint2,2)[0]
print(h)
h1 = hint1-hint2
h2 = h1 // h 
a = (h+h2)//2
b = h - a
assert a**2+a*b == hint1
print(a,b)

然后,我们要介绍一种已知如果两条消息之间仅存在已知的固定差异 并且在相同的RSA模N下加密 那么就有可能同时恢复它们的攻击方法Franklin-Reiter相关消息攻击!!!原理这里也不多讲了,写代码实现。

from Crypto.Util.number import *
import gmpy2

n = 96294984374753089080583610747240203389088051930341615602841335596072081913930052484580770899610689065293206976889303327507604080242460321817406117877072425663471808350427893332726383611142246218026112051129578226014713958882307096859175042839198895428723757405196020266267824586199807170149650434306779718677
a = 217562055747111316427029035559575901669
b = 293227681709108659093110186239101833877
c1 = 19258639302759286032385037035129183959148363633353536085988651266927081471573889078520697158985164250184287591219408939982288951952002632371950165308028756191357634396067109728491154241759098403135297660541258808223827178690693818047525955978891748361038516201626720292976210372367169577574791733786601438737
c2 = 94728391095098686718854324913205273277837107275197779266203956111447917954217642996612375426900185362566372715775684730383657923112702380810451305560067414494793397392647318953106387697001580454130672044011060132072400728672015125332880303053556556640562753160804196189095016248121783366388642672259225712092
e = 3

def franklinReiter(n, e, c1, c2, a, b):
    PR.<x> = PolynomialRing(Zmod(n))
    g1 = (x)^e - c1
    g2 = (a*x + b)^e - c2
    
    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()
    print()
    m = -gcd(g1, g2)[0]

    return m

m = franklinReiter(n, e, c1, c2, a, b)
print(m)

用sagemath跑一下,你可以得到flag1,这是压缩包的密码。解压一下,是个图片
在这里插入图片描述
发现flag不全啊,(看来要处理杂项题了)。。。。
010打开看一下发现了一组e,n,c。又进入rsa环节哈。
e=3,我们想到立方根攻击中的一个情况(即能否使用gmpy2.iroot函数来直接求解m),总让我成功一次吧!?
在这里插入图片描述
好了直接运行得到结果
在这里插入图片描述

小结

最后一道题,后半部分,费马定理,其实你直接yafu分解也可得到p,q,前半部分什么鬼东西我是没看懂。还是做不到,只能空悲切。后半部分是emmemmemmemmemmemmemmemmemmemmle_a3_It_lo0ks}

五、MISC

(1)ez-QR

给了gif,像个二维码的切片轮放哈,先给图片提取出来,一共是九张图,拼一下,是个二维码(会违规,我就不放了自己拼一下),然后没啥好说的扫码得结果。

(2)怎么全是01,我flag呢

提示为我flag呢?我不到啊,滴滴答滴哒哒哒(提示:想想文件的本质是什么),给了一串二进制数,我们二进制转文件试试,转出来是一张二维码还是扫码得结果。

(3)少年的ctf奇遇

非常非常常规的图片题,用stegsolve的lsb隐写加修改高度来求解哈
在这里插入图片描述
在这里插入图片描述
得到答案NSSCTF{lao_po_s0_cute!}

(4)从千年真理部到游戏开发部day1

给了一个basebase.rar的压缩包,game.txt里是许多串base64字符,(解了一下是马云你听我说那首歌的英文,没绷住),丢工具里解,得到压缩包密码game,G@mee
在这里插入图片描述
在这里插入图片描述
解压出来是个非常经典的base64转图片哦,得到flag
在这里插入图片描述

(5)来听歌啦!!!

先听一下,(bitter是吧,算你小子有品位),回归正题,010 Editor打开看一下直接得到flag
在这里插入图片描述
flag:NSSCTF{hufh34tu0wj03ughfi}

(6)天干物燥,注意防火

题目描述:
上个月内有家商铺楼下电瓶车充电起了火,你想把这起火灾做个安全教育事例,可是你不清楚地点。你通过自己的osint技术找到了这里,发现这里去年还有一场非常严重的火灾。(请将去年火灾时间按YYYY/MM/DD/HH:MM格式计算32位小写MD5后包裹NSSCTF{}提交。
还给了一张图片,依稀可见广安两个字,直接搜关键字 广安 电瓶车 起火 2024
在这里插入图片描述
NSSCTF{d3b53e0118945a6c86180a97e888ab37},简单哈

(7)wc,是巨硬领域大蛇!

在这里插入图片描述
给到的附件可知flag是三部分组成的,第一个是一个pdf文档(改后缀为pdf),直接用相应工具解就是了。
第二个是docx文档,把后缀改为docx,打开一般情况下我们都是开了显示隐藏文字的(没开的自行百度),打开看一下(虽然但是二刺螈浓度超标),找到flag2
在这里插入图片描述
第三个是个表格啊,依然是改后缀为xlsx,打开。有些表格里面是有隐藏值的
在这里插入图片描述
所以最终flag为NSSCTF{YOU_ARE_HUGE_HARD_MAN!!!}

(8)逆天方式ADS

我直接就是7z打开,你ADS文件隐写没用好哥们。在这里插入图片描述
打开第二个文件
在这里插入图片描述
()里面就是这个古典密码的名字,解一下得到压缩包密码c0ngr4tulat1on!
在这里插入图片描述

然后是个猪圈密码的图片,推荐使用这个在线网站对照https://www.metools.info/code/c90.html,爽。解出来就是flag了

(9)从千年真理部到游戏开发部day2

音频隐写题,先用sstv(慢速扫描电视)识别出图片,得到压缩包密码BANG_KA_KA_BANG~
在这里插入图片描述
得到如上图片
一眼丁真鉴定为光栅隐写,整个脚本解一下吧

from PIL import Image

def is_red(pixel):
    # 判断一个像素是否为红色
    return pixel[0] > 200 and pixel[1] < 100 and pixel[2] < 100

def is_white(pixel):
    # 判断一个像素是否为白色
    return pixel[0] > 200 and pixel[1] > 200 and pixel[2] > 200

def process_image(input_image_path, output_image_path):
    # 打开图片
    img = Image.open(input_image_path)
    width, height = img.size
    output_img = Image.new('RGB', (width, height), (255, 255, 255))

    for m in range(width):
        first_pixel_found = False
        output_column = []
        
        for i in range(height):
            pixel = img.getpixel((m, i))

            if is_red(pixel) and i > 0 and is_white(img.getpixel((m, i - 1))):
                first_pixel_found = True
            
            if first_pixel_found:
                output_column.append(pixel)

                # 当列的输出达到高度后停止
                if len(output_column) >= height:
                    break

        # 将处理后的列写入输出图像
        for i in range(len(output_column)):
            output_img.putpixel((m, i), output_column[i])

    # 保存处理后的图片
    output_img.save(output_image_path)

# 运行脚本
process_image('1.png', '2.png')

在这里插入图片描述
大概看得清楚得了要什么自行车,flag为NSSCTF{Random_Height}

小结

没啥好说的,杂项就这样,想到了就能做想不到就寄

六、MOBILE

(1)hidden

拿到题目没啥好说的直接就是jadx打开看,映入眼帘的是flag_2
在这里插入图片描述
然后使用mobsf看一下
在这里插入图片描述
直接ctrl+f找flag,找到flag_1,flag_3的存放路径也是找到了,把apk后缀改zip解压,到相应路径下打开看就是了,至此我们就找到完整flag了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


总结

写累了,其他题下来有空再看看,就这样记录一下,散会!!!

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值