Contents
Visual Studio项目利用
Visual Studio (IDE) 与 VS Code(文本编辑器)是不同的,前者是集成开发环境,后者则是文本编辑器。在Visual Studio中加载项目时,将在后台执行各种预操作。
1、在编译前加载恶意指令
PreBuildEvent
: 在工程编译前执行任意命令。
cmd /c calc
2、在编译时触发
设置调试命令行
生成事件命令行
三事件都可以利用
自定义生产工具
输出需要填数据 任意数据都可以 没有输出数据不会执行
在编译时加载恶意类型库
利用链:
- Microsoft 的 C++ 编译器(预处理器)通过#import 指令加载类型库引用LoadTypeLib()。
- LoadTypeLib()加载类型库。将做嵌套引用,即创建一个类型库,该类型库引用另一个类型库,该类型库实际上是一个名字对象字符串。
- MkParseDisplayName()分析名字对象字符串,然后通过 IMoniker::BindToObject()绑定 Windows 脚本组件对象。
- Script Component 对象将加载我们的恶意脚本文件,该文件可以托管在任意网站上。
3、在项目打开时触发
使用MSBuild任务可实现打开项目就触发
在.vcxproj文件中添加
在项目打开TypeLib
加载时触发
COMFileReference
: 项目打开TypeLib
加载时触发
True
利用链路:
- 打开项目文件后,Visual Studio 将加载通过 COMFileReference 标记指定的所有类型库。
- Properties 窗口将分析所有类型库中的 HelpstringDLL 属性。
- 我们的恶意 DLL 将通过 LoadLibrary()和DLLGetDocumentation()调用。
suo文件反序列化漏洞
在打开项目后,Visual Studio 会自动在项目根目录生成一个名为 .vs
的文件夹,该文件夹中包含一个特殊的二进制文件 .suo
。
与纯文本的 .sln
或 .csproj
文件不同,.suo
文件具有隐藏属性(在文件资源管理器中,以 .
开头的文件和文件夹默认不显示),并且其内容可读性较差。
当 Visual Studio 关闭时,会将新内容保存到 .suo
文件中,也就是说,恶意代码执行一次即会被覆盖删除,具有极强的隐蔽性。
实践:
使用ysoserial.net以以下命令行生成payload
ysoserial -g ClaimsIdentity -f BinaryFormatter -c calc -o base64 -bgc TypeConfuseDelegate
把payload塞到.sou里
修改了工具VS_Deserialize_Exploit改成读取payload文件
还可以使用SSview.exe直接在VsToolboxService流载入payload只是这个payload需要构造一下
构造好的复制到对应的流
保存流数据使用Decoder—>Apply extra…
投毒实例:
投毒者:https://github.com/0xjiefeng
其下的多个repo使用类似Visual Studio 项目模板
https://github.com/0xjiefeng/CVE-2024-35250-BOF
https://github.com/0xjiefeng/BOF-BypassUAC
https://github.com/0xjiefeng/SspiUacBypass-BOF
现均已无法访问。
使用Structured Storage Viewer工具,打开.suo文件分析数据。在VsToolboxService流中发现明显可疑的反序列化数据。
打开项目时,在加载VSPackage中VsToolboxService流的过程中,会用到BinaryFormatter做反序列化,此时反序列化的对象包含了可执行代码,就会运行这些代码。
保存这段数据做base64解密后得到:
将其中base64加密字段单独提取保存解密后得到:
打开可以看到是释放两个恶意文件,并且将程序加入注册表自启动。
参考:
sln任意代码执行的几种方式(evilsln) | CrackMe.net
拓展:
检测恶意sln的项目:backengineering/CheckEvilSln: A simple python script to check evil Visual Studio projects
import os
import sys
import xml.etree.ElementTree as ET
def check_common(foldername, filename):
tree = ET.parse(os.path.join(foldername, filename))
root = tree.getroot()
namespaces = {'ns': root.tag.split('}')[0].strip('{')}
# Check
for target in root.findall(".//ns:Target[@Name='GetFrameworkPaths']", namespaces):
print(f'Found({filename}) ')
# Check
for com_file_ref in root.findall(".//ns:COMFileReference", namespaces):
include_attr = com_file_ref.get('Include')
if include_attr:
print(f'Found({filename}) ')
# Check
for pre_build_event in root.findall(".//ns:PreBuildEvent", namespaces):
for command in pre_build_event.findall(".//ns:Command", namespaces):
print(f'Found({filename}) PreBuildEvent command: {command.text}')
# Check
for pre_build_event in root.findall(".//ns:PreLinkEvent", namespaces):
for command in pre_build_event.findall(".//ns:Command", namespaces):
print(f'Found({filename}) PreLinkEvent command: {command.text}')
# Check
for pre_build_event in root.findall(".//ns:PostBuildEvent", namespaces):
for command in pre_build_event.findall(".//ns:Command", namespaces):
print(f'Found({filename}) PostBuildEvent command: {command.text}')
# Check m_serializedClaims 检查序列化变量
def check_serialized_claims(foldername, filename):
with open(foldername + "\\" + filename, 'rb') as file:
content = file.read()
if b'm_serializedClaims' in content:
print(f'Found({filename}) m_serializedClaims')
def scan_folder(path):
for foldername, subfolders, filenames in os.walk(path):
for filename in filenames:
if filename.endswith('.vcxproj'):
check_common(foldername, filename)
elif filename.endswith('.suo'):
check_serialized_claims(foldername, filename)
def main():
if len(sys.argv) < 2:
print('Please input the directory you want to check.')
return
path = sys.argv[1]
scan_folder(path)
if __name__ == '__main__':
main()
里面包含了上面所有介绍到的方法的检测
发表回复