frida测信道爆破

clev1L Lv3

squ1rrelCTF/camelcamelcamel

ocmal逆向,不会

动调找到比较,验证为逐字节比较,可以尝试测信道(flag长度为40,没找到cmp的时候和40进行比较的地方,不然可以通过更改40的值来逐位爆破,这种方法最快)

pintools爆破脚本如下,爆破要好几十分钟

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
import sys
import subprocess
import argparse
import re
from tqdm import tqdm
from string import printable
print(printable)
#给定的密码范围3
printable="?}_0123456789sabcdefghijklmnopqrtuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

#编译好的pintools插桩计数器
PIN = "/home/mjw/pin-external-3.31-98869-gfa6f126a8-gcc-linux/pin"
INSCOUNT32 = "/home/mjw/pin-external-3.31-98869-gfa6f126a8-gcc-linux/source/tools/ManualExamples/obj-ia32/inscount0.so"
INSCOUNT64 = "/home/mjw/pin-external-3.31-98869-gfa6f126a8-gcc-linux/source/tools/ManualExamples/obj-intel64/inscount0.so"


def pin(file,passwd):
try:
command = "echo " + passwd + " | " + PIN + " -t " + INSCOUNT + " -- "+ file + " ; cat inscount.out"
output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
except:
print("Unexpected error:", sys.exc_info()[0])
raise
#匹配指令数,并且返回
output = re.findall(r"Count ([\w.-]+)", output.decode())
return int(''.join(output))

INSCOUNT=INSCOUNT64
file="/home/mjw/Desktop/camelcamelcamel"
base=""
for i in range(0x28):
counts=[0]*len(printable)
for j in tqdm(range(len(printable))):
try_data = (base+printable[j]).ljust(0x28, "0")
count=pin(file,try_data)
print(try_data,count)
counts[j]=count
maxcount=max(counts)
idx=counts.index(maxcount)
base+=printable[idx]

所以尝试用frida在0x3F6E0插桩,统计cmp的次数,来确定正确的字节实现爆破

实现代码如下

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
import frida
import subprocess
import time
from tqdm import tqdm

# 所有可能的字符(flag 的可能字符集)
printable = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ}!#$%&'()*+,-./:;<=>?@[\\]^`|"

# 通过输入 try_data,返回对应触发的 count 次数和程序输出
def get_count(try_data):
# 启动目标程序
process_name = "/home/mjw/Desktop/camelcamelcamel"
process = subprocess.Popen(
process_name,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)

# 稍微等待目标程序初始化
time.sleep(0.1)

# 用 frida attach 上目标进程
session = frida.attach(process.pid)

# JavaScript 脚本用于 hook 目标地址的函数,统计调用次数
hook_script = """
var count = 0; // 初始化调用次数

rpc.exports = {
hooknative: function () {
let base = Module.findBaseAddress("camelcamelcamel"); // 查找模块基址
let addr = base.add(0x3f6e0); // 偏移地址 = 函数地址

Interceptor.attach(addr, {
onEnter: function (args) {
count++; // 每次调用时 +1
send(count); // 发送 count 给 Python
}
});
}
};
"""

# 创建并加载 frida 脚本
script = session.create_script(hook_script)

# 用于接收 frida 发送的 count 值
def on_message(message, data):
if message['type'] == 'send':
global result
result = message['payload'] # 更新全局变量 result

# 绑定消息回调函数
script.on("message", on_message)
script.load()

# 调用脚本中的 hooknative 方法,启动 hook
script.exports_sync.hooknative()

# 向程序输入待测试的数据
process.stdin.write(try_data.encode('utf-8'))
process.stdin.flush()

# 等待程序输出结果
output, error = process.communicate()

# 解码输出结果
s = output.decode('gbk')

# 终止子进程
process.terminate()

# 返回 hook 次数 和 程序输出
return result, s


# 开始爆破 flag,从空串开始,最多爆破到 40 位
base = ""
for i in tqdm(range(len(base), 40)):
found = False # 当前位是否找到正确字符
for j in printable:
try_data = (base + j).ljust(0x28, "0") # 填充长度为 0x28(40),不足补0
count = -1

# 若未成功获取 count,就不断重试(防止 frida 异常)
while count == -1:
try:
count, output = get_count(try_data)
except:
pass # 忽略异常,继续尝试

# 打印本轮测试信息
print(try_data, count, i, output)

# 如果输出为 Correct!,表示整个 flag 成功
if "In" not in output:
print("flag:", try_data)
exit()

# 如果 count 值符合预期,则认为该字符正确,进入下一位
if count >= i + 2:
found = True
base += j # 添加该字符
print(base)
break

# 如果该位未找到任何匹配字符,终止程序
if not found:
print("not found!")
exit()

正常跑完代码只需要3min左右,但frida.attach可能会卡死或者出错找不到,可以手动添加已经爆破掉的字节到base,再重新运行python代码

或者将计数代码封装到incount.py

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
import frida
import subprocess
import time
import argparse

# =================== 处理命令行参数 ===================
parser = argparse.ArgumentParser(description="Frida hook runner")
parser.add_argument("process_name", help="Path to the target binary")
parser.add_argument("offset", help="Hex offset (e.g. 0x3f6e0)")
parser.add_argument("try_data", help="Input data for stdin")
args = parser.parse_args()

process_name = args.process_name
offset = int(args.offset, 16)
try_data = args.try_data

# =================== 启动目标进程 ===================
process = subprocess.Popen(
process_name,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
time.sleep(0.1)

# =================== 附加 frida 会话 ===================
session = frida.attach(process.pid)

# =================== JS hook 脚本 ===================
hook_script = """
var count = 0;
var attached = false;

rpc.exports = {
hooknative: function (modname, offsetHex) {
if (attached) return;
attached = true;

var base = Module.findBaseAddress(modname);
if (base === null) {
send("error: module not found");
return;
}

var addr = base.add(ptr(offsetHex));
Interceptor.attach(addr, {
onEnter: function (args) {
count++;
send(count);
}
});
}
};
"""

# =================== 加载脚本并绑定回调 ===================
script = session.create_script(hook_script)

def on_message(message, data):
if message['type'] == 'send':
global result
result = message['payload']
elif message['type'] == 'error':
print("JS error:", message['stack'])

script.on("message", on_message)
script.load()

# =================== 调用 hooknative ===================
script.exports_sync.hooknative(process_name, hex(offset))

# =================== 写入数据,读取输出 ===================
process.stdin.write(try_data.encode('utf-8'))
process.stdin.flush()
output, error = process.communicate()
s = output.decode('gbk', errors='ignore')

# =================== 清理和输出结果 ===================
process.terminate()
print(result, s)

然后exp.py不断启动该py代码,不会卡死,但还会出错,概率比前面低,并且多耗了一倍的时间,大约要6min

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
import subprocess
from tqdm import tqdm

printable="_{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\]^`|"
base=""
for i in tqdm(range(len(base),40)):
for j in printable:
# 启动进程
count=-2
while count==-2:
try:
try_data=(base+j).ljust(40,"0")
process = subprocess.Popen(['python3', 'incount.py',"/home/mjw/Desktop/camelcamelcamel",'0x3f6e0',try_data], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = process.communicate()
temp=stdout.split(" ")
count=int(temp[0])
output=temp[1]
except:pass
if "In" not in output:
print("flag:",try_data)
exit()
if count>=i+2:
base+=j
print(base)
break

不清楚为啥会count有时候会统计出错,导致找不到,汗流浃背了

  • Title: frida测信道爆破
  • Author: clev1L
  • Created at : 2025-04-07 14:07:22
  • Updated at : 2025-04-07 14:35:43
  • Link: https://github.com/clev1l/2025/04/07/frida测信道爆破/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
frida测信道爆破