参赛WP · 2022年6月26日

2022 CISCN(华中赛区)WriteUp

一、战队信息

名称:NYNUSEC战队

排名:第五名

image-20220626161332547

二、解题情况

image-20220626160954468

三、解题过程

注:以下所有内容为比赛题目答案及解析,不涉及其他方面,仅做解题方法分享

Web

FakeUpload1

首先随便上传一个图片上去

image-20220625171346657

然后可以发现查看图片的路径是http://10.75.1.71:58002/reader.php?filename=pics/GIFINFO.jpg

可以发现是明显的读文件,所以直接读flag.php

view-source:http://10.75.1.71:58002/reader.php?filename=flag.php

image-20220625171510564

得到flag

flag{ff96523b1124e8645bf4ff223b537f23}

Misc

PNGCracker3

打开压缩包就一个文件ctf.png,打开文件提示出错了

image-20220625181110945

于是丢到010 Editor

发现前面是png文件,后面是zip

image-20220625181512338

于是直接丢到foremost里,分离出png和一个压缩包

image-20220625181656656

png文件打开提示错误

image-20220625181737391

于是尝试修复宽高

得到宽高为 1243,885

image-20220625181813948

import os
import binascii
import struct

crcbp = open("ctf.png", "rb").read()    #打开图片
for i in range(2000):
    for j in range(2000):
        data = crcbp[12:16] + \
            struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
        crc32 = binascii.crc32(data) & 0xffffffff
        if(crc32 == 0x46a4fdc1):    #图片当前CRC
            print(i, j)
            print('hex:', hex(i), hex(j))

用010修改高度为885

image-20220625182220010

得到一个password

image-20220625182240328

正好拿去给压缩包当解压密码

cr4ckthisP4ssworD!@#

然后得到了

image-20220625182409277

丢到Stegsolve.jar

然后发现RedGreenBlue通道存在隐写

image-20220625182838658

提取出来得到flag

image-20220625182915775

flag{3bfdf2004fb06399b58998e597781fca}

ZIPCracker2[三血]

打开压缩包,发现俩文件都需要密码

image-20220625183157784

但是什么信息都没给,于是盲猜伪加密

ZipCenOp.jar直接去除伪加密image-20220625183251165

image-20220625183339880

打开piazzolla.zip

image-20220625183419806

摆明了要明文攻击

于是尝试用WinRarpiazzolla.jpg压缩

image-20220625183644527

然后用ARCHPR来明文攻击

image-20220625115505227

得到解压密码:crQ2#!

打开flag.txt得到flag

image-20220625115519877

flag{310fbb0e6f812d2588689257afa6437d}

xpxp1

根据附件大小为512mb,且格式为raw格式,推断出其为内存取证题。首先利用Volatility内存取证工具对可能包含flag的文件进行搜索、分析进程、分析历史命令等信息,并根据线索按图索骥,得到以下数据。

使用cmdline参数得到:

image-20220625191806264

用grep搜一下flag相关的文件

同理再搜一下letter相关的文件

image-20220625191852882

一共得到了egg1egg2egg3egg4egg5flagData.zipexpletter_PixelReplacement.png8个文件

将文件提取出来

image-20220625191926673

egg1

Do you know what the Chinese meaning of ankle is? flag is that.
Remember to convert the answer to a 32-bit lowercase MD5 value.

翻译一下

你知道脚踝的中文意思是什么吗?标志就是这样。
请记住将答案转换为 32 位小写 MD5 值。

egg2

听说对图片,有一种加密方式,叫做图像像素置换加密。置换加密算法是一种最简单的加密算法,原理就是将字母表中的字符替换成另一个字母表中的字符。那么像素置换加密是怎么样的呢。好像有一个工具集成了这个加密。但是我忘记了。据说放到邮箱草稿里面了。

egg3

egg3

egg4

你知道异或加密吗。异或是对两个运算元的一种逻辑分析类型,符号为XOR或EOR。据说在电脑某处放了部分代码,内容是对一个文件进行异或,但是有个letter不知到放到哪里了,好像是某个彩蛋吧。你能解出来吗。

egg5

According to Homer's epic, the hero Achilles is the precious son of the mortal Polus and the beautiful fairy Thetis.
It is said that her mother Tethys carried him upside down into the Styx river when he was just born, so that he could be invulnerable. 
Unfortunately, due to the rapid flow of the Ming River, his mother didn't dare to let go of his heel.
The heel held by his mother was accidentally exposed outside the water, so the heel was the most vulnerable place, leaving the only "dead hole" in his body, so he buried the disaster. 
When he grew up, Achilles fought bravely. When he went to attack the city of Troy (the story of Trojan horse slaughtering the city), the brave Achilles singled out the Trojan general Hector, killed him and dragged his body to demonstrate. 
But later, after conquering Troy, Achilles was attacked by an arrow by Hector's brother-in-law Paris and hit his ankle - the hero fell to the ground and died at the moment of shaking.
ankle, ankle, I love ankle.The password is ??k1eAn???

翻译一下

根据荷马史诗,英雄阿喀琉斯是凡人波卢斯和美丽仙女忒提斯的宝贝儿子。
据说,在他刚出生的时候,她的母亲特提斯就将他倒挂在冥河中,这样他就可以刀枪不入了。
可惜,明河水流急促,他的母亲不敢放开他的脚后跟。
妈妈握着的脚后跟不小心暴露在了水里,所以脚后跟是最脆弱的地方,留下了他身上唯一的“死穴”,于是埋下了祸患。
长大后,阿喀琉斯勇敢地战斗。当他去攻打特洛伊城(特洛伊木马屠城的故事)时,勇敢的阿喀琉斯单挑特洛伊将军赫克托尔,将他杀死并拖着他的尸体进行示威。
但后来,在征服特洛伊后,阿喀琉斯被赫克托耳的姐夫帕里斯一箭射中脚踝——英雄倒在地上,在颤抖的瞬间死亡。
脚踝,脚踝,我爱脚踝。密码是??k1eAn???

exp

f = open('./flag.zip', 'rb').read()
new = open('./fffflllaag.dat', 'ab')

letter = ''
secret = int(letter,16)
print(secret)
for i in f:
    n = int(i) ^ secret
    new.write(int(n).to_bytes(1, 'big'))

letter_PixelReplacement.png

letter_PixelReplacement

image-20220626161904940

分析脚本代码,结合egg4的提示可以发现dat文件是由flag文件异或后生成的。

但是目前letter未知,根据egg2egg3像素置换15的提示推断其使用了像素置换加密。

而替换后的文件就是letter_PixelReplacement.png

现在想要解密,就需要找到一个工具,根据egg2所说好像有一个工具集成了这个加密。但是我忘记了。据说放到邮箱草稿里面了。

需要找到邮箱草稿,But,找了半天并没有找到,干脆去google一下,搜了一下关键词还真找到了。

https://github.com/ffffffff0x/BerylEnigmaV2

QQ图片20220625192000

通过工具像素置换解密得到letter='A'

image-20220626162618084

于是写脚本将flag.zip取出

f = open('./fffflllaag.dat', 'rb').read()
a = bytearray()

letter = 'A'
secret = int(letter, 16)
for i in f:
    n = int(i) ^ secret
    a.append(n)
print(a)
with open('flag.zip', 'wb') as f:
    f.write(a)

打开压缩包后发现需要密码,根据egg5结尾的提示

The password is ??k1eAn???

猜测规律password应该是:Ank1eAnk1e

打开压缩包得到

The answer to egg1 is : You are the only weakness in my body
This is also the answer to flag

image-20220626162901812

再根据egg1的提示,将You are the only weakness in my body取md5后全小写就是flag

image-20220626163113603

flag{47155018947fbed1987313fe2d02e0bb}

数据流中的秘密3[二血]

拿到题目,是个流量包。

file

打开大致看一遍,有一堆安卓相关的东东,如sdcardscrcpy、手机型号等。

于是右键解析为ADB CS,在2号数据流里面发现一个rar包,被拆成了0x10000+0x10000+0xb575三个包,然后前两个包又有两个分包。

file

数据流前4字节是固定标识DATA,4~8字节是小端序数据长度,8字节以后是数据。

提取五条数据流合并得到完整的rar文件,发现需要密码。

file

看14号数据流发现00 00 00 01 67头,结合scrcpy可猜测是h264视频数据。

file

从文件头开始提取出文件(因为h264容错性很牛逼,所以没完全正确提取也问题不大)。打开发现是四张图片来回滚动。

file

拼合得到一个二维码。

file

用工具扫码得到一串文本695c630e-523c-4098-8ff8-0bac8f8b22d7,用来当密码打开压缩包,发现确实是压缩包密码。

解压得到一个图片和一个几乎空的.git索引。

图片文件尾有一段base32

file

写个脚本解码。

a = "KRUGKIDXNBSWK3BA4KAI3YUARXRIBDHCQGROFANE4KA2HYUARXRIDIXCQGROFAEN4KAI3YUARTRIDIPCQCG6FAEM4KAI3YUBUHRIBDHCQGROFANC4KAIZYUBULRIBDPCQGROFAEM4KAI3YUBULRIDIPCQCG6FAEM4KA2FYUARXRIDJHCQCG6FANB4KA2HYUARTRIDJHCQGROFAEN4KAI3YUARXRIDIXCQGROFANE4KA2JYUARXRIDJHCQGROFAEM4KA2JYUBUTRIDIPCQCG6FANB4KAI3YUBUTRIDIXCQGSOFAND4KA2DYUARXRIDJHCQCGOFAEN4KA2FYUBUHRIDIXCQCGOFANC4KAIZYUBULRIDIPCQGROFAEN4KAI3YUBUHRIBDPCQCG6FANB4KAIZYUARXRIDIXCQCG6FANB4KAI3YUARTRIDIXCQGROFANE4KAIZYUBUHRIDIXCQGROFANC4KA2HYUARTRIDIXCQCGOFANB4KAI3YUBUTRIDIPCQCGOFANB4KAIZYUBULRIDIPCQCG6FANC4KAI3YUARXRIDIXCQGROFANC4KA2FYUBUTRIDIPCQCG6FANC4KA2DYUBUPRIDJHCQCG6FAND4KAIZYUBULRIBDPCQCGOFAEN4KA2FYUARXRIBDHCQGROFANC4KAI3YUARTRIDIXCQCG6FAEM4KAI3YUARXRIDIXCQGQ6FAEN4KA2HYUBUTRIDIXCQGR6FANC4KA2FYUBUTRIDIPCQCGOFANC4KA2DYUBULRIBDPCQGROFANDOR2XE3TTFQQG433UNBUW4ZZANFZSAZLWMVZCA3TFO4XA===="
from base64 import b32decode
print(b32decode(a).decode())

file

得到如下文本:

The wheel ‍‍‌⁢⁤⁣‍⁢⁢‍‍‌⁡‍‌‍⁡‌⁢⁢‌⁢‍⁢‌‍⁢⁡‍‌⁢‍⁤‍⁡⁣‌⁤⁢‍‍‍⁢⁢⁤⁤‍⁤⁢‌⁤⁤⁡‍⁡‍⁤⁢⁤⁣⁡‍⁤‌‍⁢⁡⁢‌⁢‌⁢⁡⁢‍‍⁡‍‍⁡‌‍⁢‍⁡‍‌⁢⁢⁤‌⁡⁢⁢⁢⁣‌⁢‌⁡‍⁤⁡‌⁡‌⁢⁡‍⁢‍‍⁢⁢⁢⁢⁤⁡‍⁢⁡⁣⁤‍⁣‌⁢‍‌‍⁢‍‌⁢⁢‍‌⁢‍‌‍‍⁢⁡‍⁣⁤⁢⁣⁢⁢⁤⁡‌⁢⁡⁢‍⁢⁣turns, nothing is ever new.

其中含有不可见字符。

去看.git索引,有个指向github.com/KuroLabs/stegcloak的源,还有个密码just4fun

file

打开stegcloak发现是个文本隐写工具,用上面的密码解上面的含不可见字符的文本,得到flag。

file

flag{487745369bbcd1e2d663b9fd136d01d0}

Crypto

NumberGame3

拿到题目,发现三组n, e, c

n1:12671827609071157026977398418260127577729239910356059636353714138256023623770344437013038456629652805253619484243190436122472172086809006270535958920503788271745182898308583012315393657937467583278528574109842696210193482837553369816110424840884683667932711439417044144625891738594098963618068866281205254024287936360981926173192169919836661589685119695804443529730259703940744061684219737502099455504322939948562185702662485642366411258841082322583213825076942399375712892608077960687636100621655314604756871227708407963698548718981737143081639214928707030543449473132959887760171345393471397998907576088643495456531
e1:65537
c1:5268497051283009363591890965286255308367378505062739645805302950184343652292967525985407935922935972883557494557593439711003227737116083417992112594428400382187113609935251268634230537282408994938066541612999550555591607744019286392765549844400176442415480559773688439693874264657925123598756193286897112566420847480601040372338338442932524410598834393630019038536173336696498743879160879377504894526001205060753543289059104874467150194596404490638065573974570258671195173327475871936431769234701590572816592485898568463143587137721883610069616008902637316459660001435171054741347142470208082183171637233299493273737
n2:18090800828995898324812976370950614944724424095669490324214928162454640462382724191043785592350299626782376411935499259428970532102686361824967300649916495702138825182857737210486173137998811993244590794690070307872074705348982970060304389842338043432383690934814892283936018142382990267868341375956549210694354065317328612440672169232803362481090661368782599819926970968509827001203936933692777821117679448168400620234261164018167404541446201828349880887526076468982840569645753428057937172715073817332736878737709704495317549386111938639861221307607948775421897063976457107356574428602380790814162110473018856344871
e2:4097
c2:2326267610355516153575986453727161366266816656017644910981028690283132055217271939475840618294311986463011398892570340626131158223217558335139831985973737748812636360601010312490160903427322848411507157238373313053959092326875136396134997877757316339153327290508806645882428114647041522287934007579220769189583249469879165078254248922442084985860374461188259818592181294686890335242981199427715392978546977718475462727987012437677290341463732660152302257234030751774759466703002189003437204934438026047163828083902584763527752033035438078609950665211243112982373167722458975172667665849715372158378299319548194854914
n3:14016899139767071357961567514373780608355222973882916699129907806456201886114368147540489514960479836424236595826190295819765979835270500889626994048655508134450908075698567925938340322498944878806273261377551132596295484579752118097281084614987064680928168918147910522922020462762688924459558896249968804885885853885632349539590507675397376494346489972596290270168847103345561743327300964196811506510943971437325302822974593782292850499524055338033832053610217461760698628614971171144300450574522839157187874548994036357212297166759231255765155759405207408315314182166142015547345744054533749334516820850300569790673
e3:1048577
c3:1507157402302225700443994264641838312753363380677759942918832857396550216927941389943122383728949792984913155517202501504817319345830153748955731880333992875210194306712098593166605310784068299411946792264365247471197716329666415403718297430110977954951479772565341847358286252098930408452594561104228639615640815799731581302607522977457874347224189202268831547055389518214072278766864028489294466057175201908756749666131546163372443691718757198229262989973810951064160488114367967684657242385568733678188829354802025582496625272334309487028498614869964712744826603931510547381997149345221530469380732265014466170524

尝试gcd一下几个n,发现n1n2n2n3有公约数,所以那肯定三个np就能出来了,然后反推q就能正常解rsa了。

n1=12671827609071157026977398418260127577729239910356059636353714138256023623770344437013038456629652805253619484243190436122472172086809006270535958920503788271745182898308583012315393657937467583278528574109842696210193482837553369816110424840884683667932711439417044144625891738594098963618068866281205254024287936360981926173192169919836661589685119695804443529730259703940744061684219737502099455504322939948562185702662485642366411258841082322583213825076942399375712892608077960687636100621655314604756871227708407963698548718981737143081639214928707030543449473132959887760171345393471397998907576088643495456531
e1=65537
c1=5268497051283009363591890965286255308367378505062739645805302950184343652292967525985407935922935972883557494557593439711003227737116083417992112594428400382187113609935251268634230537282408994938066541612999550555591607744019286392765549844400176442415480559773688439693874264657925123598756193286897112566420847480601040372338338442932524410598834393630019038536173336696498743879160879377504894526001205060753543289059104874467150194596404490638065573974570258671195173327475871936431769234701590572816592485898568463143587137721883610069616008902637316459660001435171054741347142470208082183171637233299493273737
n2=18090800828995898324812976370950614944724424095669490324214928162454640462382724191043785592350299626782376411935499259428970532102686361824967300649916495702138825182857737210486173137998811993244590794690070307872074705348982970060304389842338043432383690934814892283936018142382990267868341375956549210694354065317328612440672169232803362481090661368782599819926970968509827001203936933692777821117679448168400620234261164018167404541446201828349880887526076468982840569645753428057937172715073817332736878737709704495317549386111938639861221307607948775421897063976457107356574428602380790814162110473018856344871
e2=4097
c2=2326267610355516153575986453727161366266816656017644910981028690283132055217271939475840618294311986463011398892570340626131158223217558335139831985973737748812636360601010312490160903427322848411507157238373313053959092326875136396134997877757316339153327290508806645882428114647041522287934007579220769189583249469879165078254248922442084985860374461188259818592181294686890335242981199427715392978546977718475462727987012437677290341463732660152302257234030751774759466703002189003437204934438026047163828083902584763527752033035438078609950665211243112982373167722458975172667665849715372158378299319548194854914
n3=14016899139767071357961567514373780608355222973882916699129907806456201886114368147540489514960479836424236595826190295819765979835270500889626994048655508134450908075698567925938340322498944878806273261377551132596295484579752118097281084614987064680928168918147910522922020462762688924459558896249968804885885853885632349539590507675397376494346489972596290270168847103345561743327300964196811506510943971437325302822974593782292850499524055338033832053610217461760698628614971171144300450574522839157187874548994036357212297166759231255765155759405207408315314182166142015547345744054533749334516820850300569790673
e3=1048577
c3=1507157402302225700443994264641838312753363380677759942918832857396550216927941389943122383728949792984913155517202501504817319345830153748955731880333992875210194306712098593166605310784068299411946792264365247471197716329666415403718297430110977954951479772565341847358286252098930408452594561104228639615640815799731581302607522977457874347224189202268831547055389518214072278766864028489294466057175201908756749666131546163372443691718757198229262989973810951064160488114367967684657242385568733678188829354802025582496625272334309487028498614869964712744826603931510547381997149345221530469380732265014466170524

from gmpy2 import gcd, invert, powmod
from Crypto.Util.number import long_to_bytes

p = gcd(n1, n2)
# print p
q = n1 / p
# print q

phi = (p - 1) * (q - 1)
d = invert(e1, phi)
print long_to_bytes(powmod(c1, d, n1))

q = n2 / p
phi = (p - 1) * (q - 1)
d = invert(e2, phi)
print long_to_bytes(powmod(c2, d, n2))

p = gcd(n2, n3)
# print p
q = n3 / p
phi = (p - 1) * (q - 1)
d = invert(e3, phi)
print long_to_bytes(powmod(c3, d, n3))
# flag{fb72574404901f5a37f88431b42b4872}

flag{fb72574404901f5a37f88431b42b4872}

Reverse

crackme2_apk1

jadx打开,很容易看出调用链check -> encodecheck判断flag长度和头尾,然后encode后和目标数组比较就完事。

file

然后看encode函数,是个按字节加密的,没有前后影响的情况,那就直接每字节爆破就完事。

file

package com.example.hooktest.hook

import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.log.loggerI
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit

@InjectYukiHookWithXposed
class Main : IYukiHookXposedInit {
    override fun onHook() = YukiHookAPI.encase {
        loadApp("com.example.CrackMe2") {
            "$packageName.MainActivity".hook {
                injectMember {
                    method {
                        name = "onCreate"
                    }
                    afterHook {
                        val target = charArrayOf(
                            '\u00cd',
                            'R',
                            't',
                            'z',
                            30.toChar(),
                            '\b',
                            '\b',
                            '\u00e0',
                            'W',
                            ';',
                            24.toChar(),
                            '\u0099',
                            '\u00af',
                            '=',
                            29.toChar(),
                            '\u0094',
                            21.toChar(),
                            '%',
                            'g',
                            '[',
                            'd',
                            'S',
                            31.toChar(),
                            ';',
                            '\u00dc',
                            '\u00a2',
                            'F',
                            '6',
                            '\u00d3',
                            '\u00fd',
                            '\u00be',
                            '3'
                        )
                        val encode = method {
                            name="encode"
                        }.get(instance)
                        var flag = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
                        var i = 0
                        while (i < 32) {
                            val rst = (encode.call(String(flag), "happygame") as String).toCharArray()
                            if (rst[i] == target[i]) {
                                loggerI(msg = flag[i].toString())
                                i++
                                continue
                            } else {
                                flag[i] = (flag[i] + 1).toByte()
                            }
                        }
                        loggerI(msg = String(flag))
                    }
                }
            }
        }
    }
}

file

flag{2fd3d38b20b7bae1f6ed0d70a7df345e}

meikyu2[一血]

打开题目的main.py,看着感觉像是读取地图然后输入线路走迷宫的题。

file

其中地图标识分别有START, END, WALL, ROAD,尝试读取地图,发现大小是10201,所以应该是101*101的图。

with open('data', 'rb') as f:
    data = f.read()
map_ = list(data)
cipher = list(b'suta-to')
print(len(map_))  # 101 * 101

file

尝试按101*101输出,可以猜测START=83, END=69, WALL=35, ROAD=32

with open('data', 'rb') as f:
    data = f.read()
map_ = list(data)
cipher = list(b'suta-to')
print(len(map_))  # 101 * 101
for i, ch in enumerate(map_):
    if i % 101 == 0:
        print()
    map_[i] = ch ^ cipher[i % len(cipher)]
    print(map_[i], end=' ')
print()
# # START 83, END 69, WALL 35, ROAD 32

file

那就写个dfs去搜路径就完事。

def dfs(x, y, lx, ly):
    if map_[x * 101 + y] == 69:
        return True, ""
    if x + 1 < 101 and map_[(x + 1) * 101 + y] != 35 and x + 1 != lx:
        rst, road = dfs(x+1, y, x, y)
        if rst:
            return True, "下"+road
    if x - 1 >= 0 and map_[(x - 1) * 101 + y] != 35 and x - 1 != lx:
        rst, road = dfs(x-1, y, x, y)
        if rst:
            return True, "上"+road
    if y + 1 < 101 and map_[x * 101 + y + 1] != 35 and y + 1 != ly:
        rst, road = dfs(x, y+1, x, y)
        if rst:
            return True, "右"+road
    if y - 1 >= 0 and map_[x * 101 + y - 1] != 35 and y - 1 != ly:
        rst, road = dfs(x, y-1, x, y)
        if rst:
            return True, "左"+road
    return False, ""

with open('data', 'rb') as f:
    data = f.read()
map_ = list(data)
cipher = list(b'suta-to')
print(len(map_))  # 101 * 101
for i, ch in enumerate(map_):
    if i % 101 == 0:
        print()
    map_[i] = ch ^ cipher[i % len(cipher)]
    print(map_[i], end=' ')
print()
# # START 83, END 69, WALL 35, ROAD 32
print(dfs(1, 0, 0, 0))

file

然后现在就是路径格式应该是长啥样的问题了,先去看了下mylib.pyd文件,看了好久没找到什么头绪,后来在util.dll里面的run_decoded_map_0函数发现了解析路径的逻辑。

file

那就把上面的路径用wsad替换一下,然后md5一波(main.py文件里面有写flag格式)交一下看看,结果真就是flag。

from hashlib import md5

def dfs(x, y, lx, ly):
    if map_[x * 101 + y] == 69:
        return True, ""
    if x + 1 < 101 and map_[(x + 1) * 101 + y] != 35 and x + 1 != lx:
        rst, road = dfs(x+1, y, x, y)
        if rst:
            return True, "s"+road
    if x - 1 >= 0 and map_[(x - 1) * 101 + y] != 35 and x - 1 != lx:
        rst, road = dfs(x-1, y, x, y)
        if rst:
            return True, "w"+road
    if y + 1 < 101 and map_[x * 101 + y + 1] != 35 and y + 1 != ly:
        rst, road = dfs(x, y+1, x, y)
        if rst:
            return True, "d"+road
    if y - 1 >= 0 and map_[x * 101 + y - 1] != 35 and y - 1 != ly:
        rst, road = dfs(x, y-1, x, y)
        if rst:
            return True, "a"+road
    return False, ""

with open('data', 'rb') as f:
    data = f.read()
map_ = list(data)
cipher = list(b'suta-to')
print(len(map_))  # 101 * 101
for i, ch in enumerate(map_):
    if i % 101 == 0:
        print()
    map_[i] = ch ^ cipher[i % len(cipher)]
    print(map_[i], end=' ')
print()
# # START 83, END 69, WALL 35, ROAD 32
print(dfs(1, 0, 0, 0))
print(f"flag{{{md5(dfs(1, 0, 0, 0)[1].encode()).hexdigest()}}}")

file

flag{3f48672b213770d9de5c3d50369840a3}