Rika's Blog

TJCTF 2020 部分题目的 Write-up

字数统计: 1.6k阅读时长: 7 min
2020/05/28 Share

TJCTF 2020 网址请点击此处,另外有支队伍在 Discord 群组里发了完整的 Write-up.

Difficult Decryption - 100 Point

题意

题目给出了 Alice 和 Bob 之间进行 Diffie-Hellman 密钥协商的过程。已知所用模数 Modulus 和相应原根 Base,并且已知 (Base ** A) % Modulus(Base ** B) % Modulus 的值,给定密文 cipher = message ^ (pow((Base ** B % Modulus), A, modulus))(注意:这里的 “^” 表示异或运算),求解明文 plain = bytes.fromhex(hex(message)[2:]),题目给出的值如下:

Modulus: 491988559103692092263984889813697016406

Base: 5

Base ** A % Modulus: 232042342203461569340683568996607232345

Base ** B % Modulus: 76405255723702450233149901853450417505

cipher: 12259991521844666821961395299843462461536060465691388049371797540470

解答

由异或运算的基本性质可知,message = cipher ^ (pow((Base ** B % Modulus), A, Modulus)),故只需解出 A 即可。可以直接使用 SageMathCell 在线求解。

1
2
3
4
ZmodN = Zmod(491988559103692092263984889813697016406)
m = ZmodN(232042342203461569340683568996607232345)
base = ZmodN(5)
print(m.log(base))

解得 A = 25222735067058727456,代入上式可知 message = 12259991521844666821961395299795867143478147128538917271758674534781
解得明文 plain = tjctf{Ali3ns_1iv3_am0ng_us!}


Gamer W - 60 Point

题意

题目给了一个用 Unity WebGL 制作的在线小游戏,题目提示为下载 Chrome 浏览器的 CETUS 插件,并通过作弊取得游戏胜利。

解答

CETUS 插件的用法和 Cheat Engine 类似。游戏需要修改攻击力才能击败最终 Boss(否则 Boss 回血比打掉的还多),Boss 丝血时玩家会被封住。这时可以把人物移速改得比较大,从而绕过障碍物的判定并击杀 Boss,然后就能得到 flag.


Gamer F - 80 Point

题意

题目给了一个用 Unity 制作的游戏,要求找出隐藏在其中的 flag.

解答

dnSpy 可以对 C# 代码进行反编译,用 AssetStudio(被 DMCA Takedown 之前叫 UnityStudio)可以拆包 Unity 的资源文件。

对本题而言,需要反编译的文件是 ./SnakeTris/SnakeTris_Data/Managed/Assembly-CSharp.dll,需要拆包的文件是 ./SnakeTris/SnakeTris_Data/level0

dnSpy 反编译的效果很好,图中的 16 进制数用 python 的 bytes.fromhex() 函数处理可以得到 flag 的第一部分。

flag_part_1

用 AssetStudio 打开 level0,选择 Exports - All Assets 可以导出拆包后的资源文件,在 AudioChip 文件夹下可以找到 Victory Sound01/02.wav 文件,用英语/日语念了一遍 flag 的第二部分。

直接用文本编辑器打开 level0,可以看到有一个 string 是 flag 的第三部分。不清楚 AssetStudio 拆包时候会把 string 拆到什么位置,笔者没有找到。


Gamer R - 80 Point

题意

题目给了一个用 Unity 制作的游戏,要求找出隐藏在其中的 flag.

解答

还是用 dnSpy 拆包,可以找到打印 flag 的代码。但是笔者不知道用到的字符串是在哪里初始化的。

flag

所以我们直接右键 - 编辑方法,把本来会一个字符一个字符打印出的 flag 存进字符串里(具体代码见下图),然后右键 - 添加断点。

要动态调试 Unity 游戏, 需要从 dnSpy 的 releases 里下载 Unity-debugging-2019.x.zip(具体选哪个依据游戏 .exe 文件属性中的文件版本决定),然后用相应版本(本题为unity-2019.1.2, win64)的 mono-2.0-bdwgc.dll 换掉游戏中 MonoBleedingEdge/EmbedRuntime 下的同名文件。

然后在dnSpy中点击保存(运行游戏并不会自动保存),并点击启动,选择 Unity 调试引擎,输入游戏可执行文件的路径开始调试,结果如下:

flag_print

得到字符串:"Here's the flag you're looking for! haha jkjk... unless..? tjctf{orenji_manggoe}"


Gamer M - 100 Point

题意

题目给了一个使用 nc p1.tjctf.org 8007 连接到的远程服务器及服务器的源代码 .py 文件,要求得到服务器上的 flag.

解答

可以看到源代码中的 shuffle 函数:

1
2
3
4
5
def shuffle(s):
for i in range(len(s)):
j = random.randint(0, len(s) - 1)
s[i], s[j] = s[j], s[i]
return s

并非完全随机。我们把这个函数单独拿出来实验,可以得到如果传入的列表有五个元素的话,原本的第 n 个元素最可能在第 n + 1 个返回(n 取 1, 2, 3, 4),剩下的元素就是原本的第一个元素。

可以写出如下脚本来重复进行 5000 次游戏并且保存结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import socket

host = 'p1.tjctf.org'
port = 8007
addr=(host,port)
filepath = "./myanswer.txt"

for k in range(5000):
tcpCliSock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcpCliSock.connect(addr)
while True:
myfile = open(filepath, 'a+', encoding = "utf-8")
data = tcpCliSock.recv(512)
mystr = data.decode()
msg1 = 'rock\n'
msg2 = 'paper\n'
msg3 = 'scissors\n'
if mystr.find('A disciple stands in your way! Take your action!') != -1:
tcpCliSock.send(msg1.encode())
if mystr.find('A disciple blocks your way! Take your action!') != -1:
tcpCliSock.send(msg2.encode())
if mystr.find('A disciple stalls your advance! Take your action!') != -1:
tcpCliSock.send(msg3.encode())
print(mystr, file = myfile)
if mystr.find('You triumphed over all trials!') != -1:
tcpCliSock.close()
myfile.close()
break

然后用如下脚本处理游戏结果,获得出现次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
perlist = [{"t","6","0","R","{"},{"j","3","i","e","b"},{"c","n","4","o","5"},{"t","r","C","A","2"},{"f","F","x","J","}"}]
result = []
result.append([{"t":0,"6":0,"0":0,"R":0,"{":0},{"t":0,"6":0,"0":0,"R":0,"{":0},{"t":0,"6":0,"0":0,"R":0,"{":0},{"t":0,"6":0,"0":0,"R":0,"{":0},{"t":0,"6":0,"0":0,"R":0,"{":0}])
result.append([{"j":0,"3":0,"i":0,"e":0,"b":0},{"j":0,"3":0,"i":0,"e":0,"b":0},{"j":0,"3":0,"i":0,"e":0,"b":0},{"j":0,"3":0,"i":0,"e":0,"b":0},{"j":0,"3":0,"i":0,"e":0,"b":0}])
result.append([{"c":0,"n":0,"4":0,"o":0,"5":0},{"c":0,"n":0,"4":0,"o":0,"5":0},{"c":0,"n":0,"4":0,"o":0,"5":0},{"c":0,"n":0,"4":0,"o":0,"5":0},{"c":0,"n":0,"4":0,"o":0,"5":0}])
result.append([{"t":0,"r":0,"C":0,"A":0,"2":0},{"t":0,"r":0,"C":0,"A":0,"2":0},{"t":0,"r":0,"C":0,"A":0,"2":0},{"t":0,"r":0,"C":0,"A":0,"2":0},{"t":0,"r":0,"C":0,"A":0,"2":0}])
result.append([{"f":0,"F":0,"x":0,"J":0,"}":0},{"f":0,"F":0,"x":0,"J":0,"}":0},{"f":0,"F":0,"x":0,"J":0,"}":0},{"f":0,"F":0,"x":0,"J":0,"}":0},{"f":0,"F":0,"x":0,"J":0,"}":0}])
filepath = "./myanswer.txt"

with open(filepath, 'r+', encoding = "utf-8") as myfile:
mylist = []
for line in myfile.readlines():
if line.find("dropped") != -1:
mylist.append(line[-2])
if len(mylist) == 5:
for i in range(5):
if perlist[i] == set(mylist):
for j in range(5):
result[i][j][mylist[j]] += 1
mylist = []
break
print(result)

然后应该就能分析出 flag,如果运气差,词频有问题,可以再执行一遍第一个脚本,接着来 5000 次游戏。

发表日期:May 28th 2020, 2:31:36 pm

最后更新:May 28th 2020, 6:31:51 pm

说明:本文依 CC-BY 4.0 发布,转载请注明出处

CATALOG
  1. 1. Difficult Decryption - 100 Point
    1. 1.1. ¶题意
    2. 1.2. ¶解答
  2. 2. Gamer W - 60 Point
    1. 2.1. ¶题意
    2. 2.2. ¶解答
  3. 3. Gamer F - 80 Point
    1. 3.1. ¶题意
    2. 3.2. ¶解答
  4. 4. Gamer R - 80 Point
    1. 4.1. ¶题意
    2. 4.2. ¶解答
  5. 5. Gamer M - 100 Point
    1. 5.1. ¶题意
    2. 5.2. ¶解答