Octane的TSCTF-J Writeup
比赛前早早地注册了Octane的比赛ID,但比赛开始时居然忘了密码,于是只能临时注册了一个Nonane的ID来打orz
最后拿了个总榜第十一,领到了奖品。同时也学到了很多,看到了自己很多的不足和以后该努力的方向。
这次比赛主要做的是Crypto和MISC。感觉套路题比较少,至少很无脑的套路不多。比赛前做了一些RSA的简单套路,积累了一点解题脚本,本以为能多水两道,但做比赛才发现Crypto出题人把套路全都放在放在第一道题上了(虽然Padding也是套路题,但到比赛结束也还是没整出来orz)。剩下几道完全是数学题,因为智商不够而被薄纱orz
Misc的各种图片隐写和压缩包隐写的题型是一道也没考(之前看了去年的隐写题还有点期待今年的来着)。和编码有关的题还出在了Crypto板块中。虽然说隐写题只是和出题人对电波而已…但感觉Black_Tea才是更电波的啊,没见过这种问题就完全没有头绪,上网搜都不知道该搜什么orz。Misc的游戏题确实挺好玩的,也辛苦游戏制作者了。
比赛题目存档和官方wp:https://github.com/MakeMerakGreatAgain/tsctf-j_2022
Reverse
Baby_xor
签到题,放进IDA里直接F5,可以看到代码。
数组data[ ]中每个元素分别与i和0x46进行异或运算即可得到flag
解题脚本:
data = [......] # 输入略 |
Byte_code
题目给了一个txt文件,打开后是这样的代码:
上网查了一下,这个是Python的字节码。用python -m dis命令即可将py文件转换为字节码。原来想找一个可以将字节码直接转换回py文件的程序,但代码看起来并没有很长,就干脆直接自己对照着字节码写出python代码,每写一行就编译成字节码和原文件对照。最后还原出来的python脚本是这样的,再运行一下就可以得到flag。
a=[114, 101, 118,101,114,115,101,95,116,104,101,95,98,121,116,101] |
Crypto
锟斤拷烫烫烫
查看题目:
烫烫烫锟斤拷/烫烫烫锟斤拷锟斤拷锟斤拷/烫烫烫锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷烫烫烫/烫烫烫烫烫烫锟斤拷锟斤拷/烫烫烫锟斤拷锟斤拷/锟斤拷锟斤拷烫烫烫锟斤拷/烫烫烫烫烫烫/锟斤拷烫烫烫烫烫烫烫烫烫/烫烫烫锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷烫烫烫/烫烫烫烫烫烫锟斤拷烫烫烫/锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷/锟斤拷烫烫烫锟斤拷锟斤拷/锟斤拷锟斤拷烫烫烫锟斤拷/烫烫烫锟斤拷/锟斤拷烫烫烫烫烫烫烫烫烫/锟斤拷锟斤拷烫烫烫/锟斤拷烫烫烫烫烫烫/锟斤拷锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫烫烫烫锟斤拷锟斤拷/锟斤拷锟斤拷锟斤拷烫烫烫烫烫烫/烫烫烫烫烫烫锟斤拷锟斤拷/烫烫烫烫烫烫/锟斤拷锟斤拷锟斤拷烫烫烫/锟斤拷烫烫烫烫烫烫/烫烫烫烫烫烫锟斤拷/烫烫烫锟斤拷烫烫烫/锟斤拷锟斤拷锟斤拷锟斤拷烫烫烫/锟斤拷烫烫烫锟斤拷锟斤拷/锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷/烫烫烫锟斤拷烫烫烫烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫/烫烫烫锟斤拷锟斤拷锟斤拷烫烫烫 |
虽然”锟斤拷烫烫烫”是一种众所周知的中文乱码,但这种乱码已经丢失了原本的数据,是不能还原出原始信息的!因此这道题不可能是真的”锟斤拷烫烫烫”乱码,而是隐藏着某种别的加密或编码方式。
观察文本内容很容易发现,文段只由”锟斤拷”和”烫烫烫”和斜杠组成。由此很容易想到摩斯电码。
用记事本的替换功能,将”锟斤拷”替换成”.”,将”烫烫烫”替换成”-“,以摩斯电码翻译,得到一串字符:
nbxxkzdfmjxxq5lfnjuw4z3zmvwgk4lvny======
观察字符很容易发现,字符尾部有数个等号,并且只有小写字母和数字(因为摩斯电码本身不区分大小写)。很明显是base32编码。将小写字母全部替换为大写字母,进行base32解码,得到柏油校训的拼音,即是flag:
TSCTF-J{houdeboxuejingyelequn}
T0ni’s RSA
查看题目:
from Crypto.Util.number import * |
由题目可知flag分为四段,分别加密四次,根据每次加密的方式分别解密即可得到flag
解题脚本:
from Crypto.Util.number import long_to_bytes |
Nonograms
似乎是某种填字游戏,总共14张图。曾经在steam玩过玩法类似的SquareCells。
hint提示用画图的填充工具做,本想找到一个可以在线解密的网站。但最后还是自己做了。
先在前几个挑了几个简单的做,得到这个
由此可知前8个图是’TSCTF-J{‘。最后一个图是’}’。因此中间9-13五张图才是flag的实际内容。
由此很容易可以猜测出flag:
TSCTF-J{旗开得勝!}
Two Keys
由题目描述可知flag分为两段,分别用key和KEY加密。
第一段flag用RSA加密,p、q、phi都是已知的,而e就是key,即是Question1的答案。
询问出题人这个问题的具体规则,得知不能走回头路,整个过程只能网上或往右走。
虽然不会算这种问题,但可以估计最终的答案不会非常大。能爆破的题为什么还要动脑算?
于是写一个脚本直接爆破e解密,从而得到第一段flag。
from gmpy2 import invert |
Qustion2:
print("Question 2:\n\nKEY is a string of 8 lowercase letter.\n\nsha256(KEY) == 2b87ea3983c646fcecc476f6930c18bf75935cab40471930f560bef2f370b82e and len(KEY) == 8\n") |
第二段flag用DES加密,密钥KEY就是Question2的答案。Question2给出了KEY的sha256哈希值,并且已知KEY由八个小写英文字母组成。于是用hashcat进行哈希碰撞得到KEY:
很快就可以解出KEY是’vmefifty’。用KEY解密DES密文即可得到第二段flag:
from Crypto.Cipher import DES |
最终得到完整flag:
TSCTF-J{C0mbinAt0rial_M4themat1cs_aNd_Ha$h-Alg0rithms_aRe_1mportaNt_in_CryptOgraphy}
T0ni’s Encode
查看题目:
from Crypto.Util.number import * |
分析题目,首先已知’key’中的字符一定在’data’里,即一定是小写字母和数字。并且题目给出了明文即flag的前十个字符’TSCTF-J{Ba’。
分析加密过程,首先题目将flag明文按每五个字符分为一组,分组进行加密。每一组的明文的sum如果能被2整除,该组明文与key的前五位进行异或运算;当明文的sum不能被2整除时,明文与key的后五位进行异或运算。两种情况都在加密后用’T0nihash’函数对key进行一次变换,并且变换时key的前五位和后五位的前后顺序不同,以使得每一组加密时使用的密钥都互不相同。
从加密过程可以得知,加密前后各组数据的顺序和大小都没有改变,密文的第n位一定对应明文的第n位。而每一次加密的步骤用的是可逆得到异或运算。我们已知前两组加密使用的明文分别是’TSCTF’和’-J{BA’,因此很容易求出加密前两组使用的密钥。由于解密和加密的顺序系统,解密时密钥的变换方式与加密时相同,因此我们只要知道初始密钥,再按与加密相同的步骤依次进行异或运算即可得到明文。
首先分别求出前两组明文的sum,以判定它们时以key的前五位还是后五位加密的。
known = b'TSCTF-J{Ba' |
由此可知第一组明文是与初始状态key的前五位进行异或运算的。而第二组明文是与经过一次变换的key的后五位进行异或运算的。由已知的明文与对应的密文进行异或运算得到对应的key:
key1 = long_to_bytes(bytes_to_long(cipher[:5])^bytes_to_long(known[:5])) |
我们由此得知初始key的前五位是’a2002’。但第二次得到的key的后五位,即key02看起来是乱码,这是因为它是经过一轮’T0nihash’变换之后的key。’T0nihash’函数是已知的,我们可以以此进一步推算出初始密钥的后五位。
‘T0nihash’函数含有取余运算,直接逆推非常困难。但我们知道初始key只含有数字和小写字母,因此我们可以对初始key的后五位进行爆破。当初始key的前五位和初始key的后五位的’T0nihash’运算结果与key02相同时,我们就找到了正确的初始key。爆破脚本如下:
key1 = b'a2002' |
由此可知初始key是’a20021130a’
我们得到了初始key,即可进行解密。解密的顺序和加密的顺序相同,均为从前往后,并且加解密时密钥的变换方式相同。由于我们不知道每一组的明文的sum值,因此要用try-except语句尝试两种情况。解密脚本如下:
from Crypto.Util.number import long_to_bytes, bytes_to_long |
flag终于求出来了…但hint是啥?懒得看了。
L1nearAlgebra
先看题目:
from sage.all import * |
大概题目就是先造了一个矩阵M,然后把很多矩阵M用flag经过某种变换后叠在矩阵C上。但每叠一个矩阵M之后矩阵C的第一行和第一列的最后一个数就是上一个叠的矩阵M乘以的因数,也是flag的ASCII码。逐个把这个数取出来并减去相应的矩阵,就可以得到flag了。
矩阵运算用的是sympy库。解题脚本:
from sympy import Matrix, zeros |
flag提到了个叫Jordan矩阵的东西。之前没有了解过这个,等以后有时间的时候一定研究一下。
Misc
北邮人之声
题目是一段倒放的音频。用Audacity的 效果-反相(时间) 选项处理音频,再适当降低速度,提升音高使语音更容易分辨。按国际航空无线电的字母读法听,即可听出flag。
TSCTF-J{WELCOMETOBUPT}
Just_Play
一个奇怪的rpg。整个流程比较短。flag分为四个部分,每部分之间用下划线连接。首先翻一下游戏文件,找到FF.mp3,进行一段极其抽象的英语听力得到flag part4。在游戏中完成游戏流程,集齐宝石得到part3,击败最终boss bridge得到part1。在flag图迷宫的地形里可以看到由墙组成的part2。最后全部组合起来即是flag:
TSCTF-J{Th1s_G4mE_1s_S0_Ez2zZzz_4_Y0U_ri9h7?}
关于游戏:
在游戏里找到茯苓,输入暗号”1919810”可以解锁非常强的角色,前期打小boss和刷钱很快。但最终打bridge时这个角色会被ban掉。
塔上有坑。上塔之前最好备份一下存档。
游戏中的字体的0和O很难区分,从游戏中读flag时也要注意一下。
strange base64
只要写一个交互脚本把777个base64接收后解码成字节再发送出去就可以。默认编码即可,不用特意考虑中文编码问题。
解题脚本:
import base64 |
Web
词超人
题目是一个单词测试的网站,题很多,似乎全部做对才能得到flag。但每道题的答案都直接在网页源代码里给了出来。用BurpSuite抓包发现判题的机制是网页将每道题的id和用户填写的答案POST给服务器,再由服务器返回结果。因此,可以用Word的搜索/替换功能,将网页源代码保留题目ID和答案,替换成网页POST给服务器的格式,发送给服务器就可以返回flag:
TSCTF-J{naughty_or_diligent-which_type_you_are?^_^}
真真历险记
进网页后按F12,在源代码的style.css里可以找到三段奇怪的注释,只由[(])+!几个字符组成,很明显是JSf*ck,将三段编码连起来,用解码即可得到flag
http://www.hiencode.com/jsfuck.html
需要注意的是,由于Jsf*ck的特性,分别解码是不能得到完整的flag的,必须将三段代码按顺序连起来一起解码才能得到flag:
TSCTF-J{1_tEs7_y0Ur_c()de}
Pwn
checkin
是非常简单的栈溢出,直接nc题目地址,只要溢出就能得到flag。flag具体是啥忘了。
End
总之,感谢学长们用心准备的这次比赛。之后有时间的时候会把今年MoeCTF的WP给整理一下发出来。因为它虽然是西电的新生赛,但其中很多题和想法对咱自己来说还是很有价值的,同时很多做题时学到的知识和思路也快忘记了,整理一遍wp也权当复习,作为笔记放在这里给自己参考和提示。(意识到写wp的重要性)