NCTF2023 逆向题目ezVM 题解

在NCTF2023的题目中出现了一道VM类型的题目,针对该题本人采用Frida对程序进行插桩,利用侧信道攻击的方法爆破出flag。


题目分析


首先分析该程序,是一个64bit的程序,并且加了UPX壳使用 -d 自动脱壳即可。


然后将程序放入IDA中逆向分析。


NCTF2023 逆向题目ezVM 题解


NCTF2023 逆向题目ezVM 题解


比较传统的读取操作码以及操作数,根据不同操作码模拟不同的虚拟指令。


NCTF2023 逆向题目ezVM 题解


在0xFB处程序利用putchar输出回显信息。


NCTF2023 逆向题目ezVM 题解


运行程序后,程序也会提示flag的格式以及长度。


传统思路


传统思路的话肯定是利用IDApython或者手撕出所有模拟的代码,然后进行逆向分析求解。当然这种方法适合逆向功底比较深厚的选手!


侧信道攻击


但是由于这种自设计的虚拟机模拟的局限性以及作者对选手的关爱,加密算法一般都是单字符加密的。


单字符加密的话由于密文空间很小(一般都是从printable的表中枚举),将可能的字符经过正向的加密,然后与密文进行比较来判断是否为正确的字符。所以针对这个题目可以采取爆破的方法,不断枚举flag的每一位字符,然后通过运行结果来判断加密后的单字符是否正确。


NCTF2023 逆向题目ezVM 题解


咱们应该可以理解当flag字符串正确的位数越多的时候,程序在运行时经过Opcode分发那一块的汇编指令的次数也越多,因此可以在Opcode分发的位置进行插桩,从而将程序判断的结果通过插桩的次数来展现出来,通过这种侧信道的方式来将程序的比较结果展现出来。


Frida注入


这里本人采用了Frida这样一款工具,对程序进行一个模拟的插桩。

注入的Frida脚本如下:


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);


其中hook的两个位置分别为opcode分发和putchar的位置。


NCTF2023 逆向题目ezVM 题解


NCTF2023 逆向题目ezVM 题解


测试一下 可以采用如下的命令向进程中注入脚本:

frida -l h00k.js -n ezVM.exe


NCTF2023 逆向题目ezVM 题解


当输入的flag不符合标准flag形式的时候(图中测试字符串长度为43),可以都看到返回结果为341。


NCTF2023 逆向题目ezVM 题解


如果符合格式的话可以看到返回结果已经很大。


dang


NCTF2023 逆向题目ezVM 题解


当第一位是正确字符的时候可以看到返回值更大了。


构建自动化测试脚本


通过刚才的思路可以知道该方法理论是可行的,但是一个个手动尝试时间复杂度也是很难得,所以需要写自动化脚本来代替手工操作。


首先要利用python实现进程的创建(利用subprocess库),

然后使用相关的Frida API实现注入frida脚本。


# -*- coding: UTF-8 -*-
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"
#以`开头是因为flag中极大概率不会出现该字符 所以该字符作为一个检验的标准

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']
# val = int(message['payload'], 16)
# script.post({'type': 'input', 'payload': str(val * 2)})
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
# 44 -6 = 38 5--42
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)
#print('[*] Start attach')
# 加载创建好的javascript脚本
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()


#打印输出结果
# print('Output:', output.strip())
# 打印错误信息(如果有)
# if error:
# print('Error:', error.strip())

#sys.stdin.read()


然后js脚本中利用send函数向主控的python发送数据。


NCTF2023 逆向题目ezVM 题解


运行效果


NCTF2023 逆向题目ezVM 题解


可以看到成功爆破出索引为6位置字符为1 (插桩数增大)。


按照这个思路,跑大概两三个小时?最后可以得到42位正确的flag。


缺失最后一位,再写脚本爆破一下该字符就可以到的最后flag。


import subprocess

# 创建进程并执行命令

flag = 'flag{O1SC_VM_1s_h4rd_to_r3v3rs3_#a78abffaa }'

for i in range(32,128):
process = subprocess.Popen("ezVm.exe",
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
input_data = flag.replace(" ",chr(i))
process.stdin.write(input_data)
#process.stdin.flush() # 刷新输入缓冲区
print(input_data)

# 读取进程的输出
output, error = process.communicate()

# 打印输出结果
if ("Invalid" not in output.strip()):
print('Output:', output.strip())

# 打印错误信息(如果有)
if error:
print('Error:', error.strip())
process.terminate()


执行后得到最后的flag。


NCTF2023 逆向题目ezVM 题解




NCTF2023 逆向题目ezVM 题解


看雪ID:Just_Cracker

https://bbs.kanxue.com/user-home-946278.htm

*本文为看雪论坛优秀文章,由 Just_Cracker 原创,转载请注明来自看雪社区

NCTF2023 逆向题目ezVM 题解

# 往期推荐

1、区块链智能合约逆向-合约创建-调用执行流程分析

2、在Windows平台使用VS2022的MSVC编译LLVM16

3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱

4、为什么在ASLR机制下DLL文件在不同进程中加载的基址相同

5、2022QWB final RDP

6、华为杯研究生国赛 adv_lua


NCTF2023 逆向题目ezVM 题解


NCTF2023 逆向题目ezVM 题解

球分享

NCTF2023 逆向题目ezVM 题解

球点赞

NCTF2023 逆向题目ezVM 题解

球在看

原文始发于微信公众号(看雪学苑):NCTF2023 逆向题目ezVM 题解

版权声明:admin 发表于 2023年12月29日 下午6:04。
转载请注明:NCTF2023 逆向题目ezVM 题解 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...