在NCTF2023的题目中出现了一道VM类型的题目,针对该题本人采用Frida对程序进行插桩 ,利用侧信道攻击 的方法爆破出flag。
首先分析该程序,是一个64bit的程序,并且加了UPX壳使用 -d 自动脱壳即可。 然后将程序放入IDA中逆向分析
比较传统的读取操作码以及操作数,根据不同操作码模拟不同的虚拟指令。 在0xFB处程序利用putchar输出回显信息 运行程序后,程序也会提示flag的格式以及长度
传统思路的话肯定是利用IDApython或者手撕出所有模拟的代码,然后进行逆向分析求解。当然这种方法适合逆向功底比较深厚的选手!
但是由于这种自设计的虚拟机模拟的局限性以及作者对选手的关爱,加密算法一般都是单字符加密 的。 单字符加密的话由于密文空间很小(一般都是从printable的表中枚举),将可能的字符经过正向的加密,然后与密文进行比较来判断是否为正确的字符。所以针对这个题目可以采取爆破的方法,不断枚举flag的每一位字符,然后通过运行结果来判断加密后的单字符是否正确。 咱们应该可以理解当flag字符串正确的位数越多的时候,程序在运行时经过Opcode分发那一块的汇编指令的次数也越多,因此可以在Opcode分发的位置进行插桩,从而将程序判断的结果通过插桩的次数来展现出来,通过这种侧信道的方式来将程序的比较结果展现出来。
这里本人采用了Frida这样一款工具,对程序进行一个模拟的插桩。 注入的Frida脚本如下
其中hook的两个位置分别为opcode分发和putchar的位置
测试一下 可以采用如下的命令向进程中注入脚本
当输入的flag不符合标准flag形式的时候(图中测试字符串长度为43),可以都看到返回结果为341 如果符合格式的话可以看到返回结果已经很大
dang 当第一位是正确字符的时候可以看到返回值更大了
通过刚才的思路可以知道该方法理论是可行的,但是一个个手动尝试时间复杂度也是很难得,所以需要写自动化脚本来代替手工操作。 首先要利用python实现进程的创建(利用subprocess库) 然后使用相关的Frida API实现注入frida脚本
然后js脚本中利用send函数向主控的python发送数据
可以看到成功爆破出索引为6位置字符为1 (插桩数增大) 按照这个思路 跑大概两三个小时? 最后可以得到42位正确的flag 缺失最后一位 再写脚本爆破一下该字符就可以到的最后flag
执行后得到最后的flag
var number
=
0
function main()
{
var base
=
Module.findBaseAddress(
"ezVM.exe"
)
/
/
获取目标进程的基地址
/
/
console.log(
"inject success!!!"
)
/
/
console.log(
"base:"
,base)
if
(base){
Interceptor.attach(base.add(
0x1044
), {
onEnter: function(args) {
/
/
console.log(
"number"
,number)
number
+
=
1
/
/
进行插桩 每当程序运行到这里 number
+
=
1
}
});
Interceptor.attach(base.add(
0x0113f
), {
onEnter: function(args) {
console.log(
"end!"
,number)
/
/
send(number)
/
/
当程序执行结束后把结果发送个消息处理函数
}
});
}
}
setImmediate(main);
var number
=
0
function main()
{
var base
=
Module.findBaseAddress(
"ezVM.exe"
)
/
/
获取目标进程的基地址
/
/
console.log(
"inject success!!!"
)
/
/
console.log(
"base:"
,base)
if
(base){
Interceptor.attach(base.add(
0x1044
), {
onEnter: function(args) {
/
/
console.log(
"number"
,number)
number
+
=
1
/
/
进行插桩 每当程序运行到这里 number
+
=
1
}
});
Interceptor.attach(base.add(
0x0113f
), {
onEnter: function(args) {
console.log(
"end!"
,number)
/
/
send(number)
/
/
当程序执行结束后把结果发送个消息处理函数
}
});
}
}
setImmediate(main);
frida
-
l h00k.js
-
n ezVM.exe
frida
-
l h00k.js
-
n ezVM.exe
import
subprocess
import
win32api
import
win32con
def
start_suspended_process(proc_name):
creation_flags
=
0x14
process
=
subprocess.Popen(proc_name, creationflags
=
creation_flags)
print
(
"子进程已启动并挂起"
)
return
process.pid
import
ctypes
def
resume_process(pid):
try
:
kernel32
=
ctypes.WinDLL(
'kernel32'
, use_last_error
=
True
)
kernel32.DebugActiveProcess(pid)
print
(f
"进程 {pid} 已恢复."
)
except
OSError as e:
print
(f
"恢复进程时发生错误: {str(e)}"
)
printable
=
"`!\"#$%&'()*+,-./:;<=>?@[\]^_{|}~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
import
frida, sys
number
=
102741
number
=
103833
new_number
=
0
def
is_right():
global
new_number,number
if
new_number > number:
number
=
new_number
return
True
else
:
return
False
def
on_message(message, data):
global
new_number
if
message[
'type'
]
=
=
'send'
:
print
(
"[*] {0}"
.
format
(message[
'payload'
]))
new_number
=
message[
'payload'
]
elif
message[
'type'
]
=
=
"error"
:
print
(message[
"description"
])
print
(message[
"stack"
])
print
(message[
"fileName"
],
"line:"
,message[
"lineNumber"
],
"colum:"
,message[
"columnNumber"
])
else
:
print
(message)
pass
jscode
=
open
(
"h00k.js"
,
"rb"
).read().decode()
import
subprocess
flag
=
"flag{O"
for
index
in
range
(
len
(flag),
44
):
for
i
in
printable:
process
=
subprocess.Popen(
"ezVm.exe"
,
stdin
=
subprocess.PIPE,
stdout
=
subprocess.PIPE,
stderr
=
subprocess.PIPE,
universal_newlines
=
True
)
tmp_flag
=
(flag
+
i).ljust(
43
,
"A"
)
+
"}"
print
(tmp_flag)
print
(
"try index:"
,index ,
"chr :"
,i)
session
=
frida.attach(
"ezVM.exe"
)
script
=
session.create_script(jscode)
script.on(
'message'
, on_message)
script.load()
process.stdin.write(tmp_flag)
output, error
=
process.communicate()
if
(i
=
=
'`'
):
number
=
new_number
elif
(is_right()
=
=
True
):
flag
+
=
i
print
(flag)
break
process.terminate()
import
subprocess
import
win32api
import
win32con
def
start_suspended_process(proc_name):
creation_flags
=
0x14
process
=
subprocess.Popen(proc_name, creationflags
=
creation_flags)
print
(
"子进程已启动并挂起"
)
return
process.pid
import
ctypes
def
resume_process(pid):
try
:
kernel32
=
ctypes.WinDLL(
'kernel32'
, use_last_error
=
True
)
kernel32.DebugActiveProcess(pid)
print
(f
"进程 {pid} 已恢复."
)
except
OSError as e:
print
(f
"恢复进程时发生错误: {str(e)}"
)
printable
=
"`!\"#$%&'()*+,-./:;<=>?@[\]^_{|}~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
import
frida, sys
number
=
102741
number
=
103833
new_number
=
0
def
is_right():
global
new_number,number
if
new_number > number:
number
=
new_number
return
True
else
:
return
False
def
on_message(message, data):
global
new_number
if
message[
'type'
]
=
=
'send'
:
print
(
"[*] {0}"
.
format
(message[
'payload'
]))
new_number
=
message[
'payload'
]
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-12-26 16:23
被Just_Cracker编辑
,原因: 增加内容
上传的附件: