基于pintool的侧信道爆破实现

​ vm 单字节加密 侧信道爆破 pintool安装……
题目来源第八届“强网”拟态防御国际精英挑战赛_hyperjump

这里我是根据这个师傅的文章来安装pintool的:欢迎回来(。・∀・)ノ

同时我看了swdd的文章[原创]基于 Frida 对单字节加密验证程序侧信道爆破-CTF对抗-看雪论坛-安全社区|非营利性质技术交流社区

以及butterfly师傅的文章:(。・ω・。)泥嚎


VM类型题,flag长度为24,这里加密一次存入byte_5420[n23_1]后进行比较,是单字节加密

image-20251112205324919

此时就可以用到pintool来进行侧信道爆破。


这里我看到了有的师傅用时间来爆破(时序侧信道爆破),不过爆破的结果不稳定,会出现错误,我就没有用这种方法去尝试,不过后续准备试试。


Intel Pin:动态二进制插桩,在不修改源代码、不重新编译程序的情况下,对二进制可执行文件进行运行时插桩,以便分析程序的执行过程。在侧信道研究方面,统计指令数量、分支次数、缓存访问等特征。

思路是通过 PinTool 收集程序在不同输入下执行的指令数,通过判断差异来爆破。

指定 Pin 可执行文件和计数工具 inscount0.s,该 .so 会统计程序执行的总指令数并输出到 inscount.out。

注意限制flag{},不然最后爆破最后一位会出现错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
import re
import string
import sys
import os
import subprocess
from pwn import *

TARGET_BINARY = os.path.abspath('./hyperjump')
FLAG_LEN = 24
# 固定前缀和后缀
FLAG_PREFIX = "flag{"
FLAG_SUFFIX = "}"

START_INDEX = len(FLAG_PREFIX)

PIN_ROOT = '/home/kali/Desktop/pintools/pin-external-4.0-linux'
PIN_TOOL_PATH = os.path.abspath(os.path.join(PIN_ROOT, 'source/tools/ManualExamples/obj-intel64/inscount0.so'))

PIN_CMD_LIST = [os.path.join(PIN_ROOT, 'pin'), '-t', PIN_TOOL_PATH, '--', TARGET_BINARY]

CHARSET = string.ascii_lowercase + string.digits + "_@{}"

context.log_level = 'info'

dates = ['inscount.out', 'inscount.txt', 'pin.out']
for name in candidates:
path = os.path.join(workdir if workdir else '.', name)
if os.path.exists(path):
try:
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
data = f.read()
m = re.search(r'(\d+)\s+instructions', data)
if m:
return int(m.group(1)), data
m2 = re.search(r'(\d+)', data)
if m2:
return int(m2.group(1)), data
except Exception:
pass
return None, None


def get_inscount(payload: bytes) -> int:
IO_TIMEOUT = 5
log.info(f"Testing payload: {payload.decode('latin-1')[:START_INDEX+1]}...")

try:
proc = subprocess.run(
PIN_CMD_LIST,
input=payload.decode('latin-1') + '\n',
capture_output=True,
timeout=IO_TIMEOUT,
text=True,
cwd='.'
)

stdout = proc.stdout or ""
stderr = proc.stderr or ""
exitcode = proc.returncode

sys.stderr.write(f"\n--- Pin Tool 完整输出 (Exit Code {exitcode}): ---\n")
if stdout.strip():
sys.stderr.write("[stdout]\n" + stdout.strip() + "\n")
if stderr.strip():
sys.stderr.write("[stderr]\n" + stderr.strip() + "\n")
sys.stderr.write("--------------------------------------------------\n")

combined = stdout + "\n" + stderr

match_re = re.search(r'(\d+)\s+instructions', combined, re.IGNORECASE)
if match_re:
return int(match_re.group(1))

lines = combined.strip().splitlines()
for line in reversed(lines):
s = line.strip()
if s.isdigit():
return int(s)

file_count, file_content = read_inscount_outfile(workdir='.')
if file_count is not None:
try:
os.remove('inscount.out')
except Exception:
pass
return file_count

low = combined.lower()
if exitcode is not None and exitcode != 0:
if any(k in low for k in ['nope', 'try again', 'wrong', 'incorrect']):
return 0
if stderr.strip():
return 0

if exitcode == 0 and any(k in low for k in ['flag', 'correct', 'congrats', 'success']):
return 10**9

log.warning("无法从 Pin 输出或文件中解析到指令数(stdout/stderr/file 均无数字)。")
return 0

except subprocess.TimeoutExpired as te:
sys.stderr.write(f"[TIMEOUT] Pin 运行超时: {te}\n")
return 0
except FileNotFoundError as fe:
sys.stderr.write(f"[FATAL] 找不到 pin 可执行文件或路径错误: {fe}\n")
return 0
except Exception as e:
sys.stderr.write(f"[FATAL ERROR] get_inscount 发生错误: {e}\n")
return 0


def main():
# 初始化 known_flag,并把 prefix/suffix 固定上去
if FLAG_LEN < len(FLAG_PREFIX) + len(FLAG_SUFFIX):
log.critical("FLAG_LEN 太短,不能容纳 prefix 和 suffix")
return

known_flag = list("A" * FLAG_LEN)
# 固定前缀
for i, ch in enumerate(FLAG_PREFIX):
known_flag[i] = ch
# 固定后缀(最后一位)
known_flag[-1] = FLAG_SUFFIX

fixed_positions = set(range(0, len(FLAG_PREFIX))) | {FLAG_LEN - 1}
try_positions = [i for i in range(0, FLAG_LEN) if i not in fixed_positions and i >= START_INDEX]

log.info(f"开始爆破,长度 {FLAG_LEN},字符集大小 {len(CHARSET)},从索引 {START_INDEX} 开始。")
log.info(f"固定前缀: '{FLAG_PREFIX}',固定后缀: '{FLAG_SUFFIX}'。将尝试位置: {try_positions}")

if not os.path.exists(TARGET_BINARY):
log.critical(f"目标文件不存在: {TARGET_BINARY}")
return
if not os.path.exists(PIN_TOOL_PATH):
log.critical(f"Pin Tool 不存在: {PIN_TOOL_PATH}")
return
if not os.path.exists(PIN_CMD_LIST[0]):
log.critical(f"Pin 可执行文件不存在: {PIN_CMD_LIST[0]}")
return

for i in try_positions:
log.info(f"\n>>>> 正在爆破第 {i} 位...")

max_count = 0
best_char = None

for char in CHARSET:
current_test = known_flag[:]
current_test[i] = char
payload = "".join(current_test).encode('latin-1')

count = get_inscount(payload)

if count == 0:
continue

if count > max_count:
max_count = count
best_char = char
log.info(f"发现新的最大值: 字符 '{best_char}' -> {max_count} 条指令")

if best_char:
known_flag[i] = best_char
log.success(f"第 {i} 位确定为: '{best_char}'")
log.success(f"当前 Flag: {''.join(known_flag)}")
else:
log.warning(f"第 {i} 位爆破失败。所有字符返回 0。")
break

# 最后确保后缀是 '}'
known_flag[-1] = FLAG_SUFFIX

log.success(f"\n--- 爆破完成 ---")
log.success(f"最终 Flag: {''.join(known_flag)}")


if __name__ == '__main__':
main()

image-20251112211945106

flag{m4z3d_vm_jump5__42}


基于pintool的侧信道爆破实现
https://j1nxem-o.github.io/2025/11/12/基于pintool的侧信道爆破实现/
作者
J1NXEM
发布于
2025年11月12日
更新于
2025年11月12日
许可协议