TrustedSec ◆ Hunting Deserialization Vulnerabilities With Claude
June 12, 2025
Hunting Deserialization Vulnerabilities With Claude
Written by James Williams
Artificial Intelligence (AI)
Table of contents
Share
In this post, we are going to look at how we can find zero-days in .NET assemblies using Model Context Protocol (MCP).
Setup
Before we can start vibe hacking, we need an MCP that will allow Claude to disassemble .NET assemblies. Reversing a .NET binary is normally something I would do with dotPEAK; however, this is a Windows-only tool. Luckily for us, ilspycmd exists and can be run on Mac/Linux. The ilspycmd-docker repository provides a Dockerfile for ilspycmd, but the current version on GitHub is a few years out of date and won’t build.
Figure 1 - Build Error for ilspycmd-docker
Luckily, the error message is quite explicit about the problem, and a small change to the Dockerfile will fix the problem.
```hljs bash FROM mcr.microsoft.com/dotnet/sdk:8.0
RUN useradd -m -s /bin/bash ilspy USER ilspy
WORKDIR /home/ilspy
RUN dotnet tool install -g ilspycmd
RUN echo ‘export PATH=”$PATH:/home/ilspy/.dotnet/tools/”’ » /home/ilspy/.bashrc
ENTRYPOINT [ “/bin/bash”, “-l”, “-c” ]
We can build this new image with the following command:
```hljs undefined
docker build -t ilspycmd .
With our Dockerfile updated and our container built, we can build a simple MCP server using Python. We’ll use the same framework as shown in our previous blog that discusses building an MCP server.
```hljs python from mcp.server.fastmcp import FastMCP import subprocess import os
server = FastMCP(“ilspy docker”)
@server.prompt() def setup_prompt() -> str: return “”” You can use the following commands to decompile .NET assemblies, using ilspy: - decompile(file: str, output_folder: str) -> int: Decompile the file at the provided path. The returned value is the success code, with 0 indicating a successful run “””
@server.tool() def run_ilspycmd_docker(exe_path, output_folder) -> int: “”” Run ilspycmd in a Docker container to decompile a DLL
Args:
dll_path (str): Path to the DLL file to decompile
output_folder (str): Folder where decompiled code will be placed
Returns:
tuple: (return_code, stdout, stderr)
"""
# Get absolute paths
input_dir = os.path.abspath(os.path.dirname(exe_path))
output_dir = os.path.abspath(output_folder)
exe_filename = os.path.basename(exe_path)
# Ensure output directory exists
os.makedirs(output_dir, exist_ok=True)
# Create input directory inside container
container_input_dir = "/decompile_in"
container_output_dir = "/decompile_out"
ilspy_cmd_path = "/home/ilspy/.dotnet/tools/ilspycmd"
ilspy_command = f"{ilspy_cmd_path} -p -o {container_output_dir} {container_input_dir}/{exe_filename}"
# Build the Docker command
docker_cmd = [\
"docker", "run", "--rm",\
"-v", f"{input_dir}:{container_input_dir}",\
"-v", f"{output_dir}:{container_output_dir}",\
"ilspycmd",\
ilspy_command\
]
# Run the command
process = subprocess.run(
docker_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return process.returncode
if name == “main”: # Initialize and run the server server.run(transport=’stdio’)
This is a little more complicated than the example in our [previous blog](https://trustedsec.com/blog/teaching-a-new-dog-old-tricks-phishing-with-mcp), but at a higher level, we’ll use some file paths to run a Docker command. Next, we’ll edit the **_claude\_desktop\_config.json_** file and add our new MCP server. It will look something like this:
```hljs json
{
"mcpServers": {
"FS": {
"command": "/Users/james/Library/Python/3.9/bin/uv",
"args": [\
"--directory",\
"/Users/james/Research/MCP/FS",\
"run",\
"FS.py"\
]
},
"brave-search": {
"command": "docker",
"args": [\
"run",\
"-i",\
"--rm",\
"-e",\
"BRAVE_API_KEY",\
"mcp/brave-search"\
],
"env": {
"BRAVE_API_KEY": "try_harder"
}
},
"ilspy": {
"command": "/Users/james/Library/Python/3.9/bin/uv",
"args": [\
"--directory",\
"/Users/james/Research/MCP/ilspy_docker",\
"run",\
"ilspy.py"\
]
}
}
}
After restarting Claude for Desktop, we should see that our MCP server is now available.
Figure 2 - MCP Server Available
Finding Existing Vulnerabilities
Now, let’s see if Claude can find a known vulnerability. In September 2023, Blue-Prints Blog posted about an insecure deserialization in AddinUtil.exe, a .NET binary that ships with Windows by default. This binary has also been added to the LOLBAS project. Note that if you want to follow along at home, you’ll need the MCP servers we created in this blog.
This vulnerability is interesting because the unsafe deserialization happens in a DLL, but the entry point is in an .EXE. First, we’ll see if Claude can find the vulnerable code in the DLL. Let’s start by checking if Claude recognizes the new MCP server.
Figure 3 - Confirming Claude can Decompile .NET Binaries
Next, we tell Claude to decompile and review System.AddIn.dll.
Figure 4 - Telling Claude to Review the DLL
Eventually, Claude comes back with a list of potential vulnerabilities, including an unsafe deserialization call in .NET remoting due to the use of TypeFilterLevel.Full.
Figure 5 - Deserialization Identified in .NET Remoting
This is likely related to the functionality used by AddinProcess.exe, which contains a .NET remoting vulnerability identified by Nettitude.
We want to focus on deserialization flaws, so we adjust our prompt and try again.
Figure 6 - Adjusted Prompt
This also failed to find the known vulnerability. Now, let’s Claude ask which files have been reviewed.
Figure 7 - Claude Listing 14 Reviewed Files
Luckily, Claude will usually tell us the truth when asked, so let’s ask why it has only reviewed 14 files.
Figure 8 - Claude’s Confession
After confirming that we want it to review all of the files, Claude finally identifies the known vulnerability.
Figure 9 - Claude Identifies the Unsafe Deserialize Call
It also recognizes that the cache file is potentially untrusted.
Figure 10 - Claude Recognizing the Cache File is Untrusted
So far, so good. Now we want to see if Claude is effective enough to find the actual exploit path from AddinUtil.exe to the unsafe deserialize call.
We’re going to give Claude a little guidance here and tell it that the DLL is referenced by AddinUtil.exe. We could get Claude to figure this out by itself, but that will be the subject of a future post.
Figure 11 - Telling Claude to Identify Paths to the Unsafe Code
After a little thinking, Claude successfully identifies the possible entry points, including the pipelineroot flag, which is mentioned in the Blue-Prints Blog post as another path to a deserialize call.
Figure 12 - Claude Identifies Entry Points
Claude also correctly identifies the attack path.
Figure 13 - Claude Identifies Attack Path
Time to push our luck and see if Claude can figure out how to build an exploit for this vulnerability. For those who didn’t read the Blue-Prints Blog, AddinUtil.exe expects the cache file to contain a specific series of bytes which precede the data that is deserialized. Let’s see if Claude can figure this out.
Figure 14 - Prompting Claude to Build a Proof-of-Concept Tool
The generated code, which I won’t reproduce in full here, references the fact that we need to create the store file in a format expected by ReadCache.
```hljs lua
Create the AddIns.store file with the format expected by ReadCache print("[+] Creating malicious AddIns.store file")
cache_file_path = os.path.join(output_dir, “AddIns.store”)
with open(cache_file_path, ‘wb’) as f: # Write the format version (int32 = 1) f.write(struct.pack(“<i”, 1)) # Write the payload size (int64) f.write(struct.pack(“<q”, len(payload_data))) Write the payload data f.write(payload_data)
We can get Claude to explain its choice here, just to see if it understands the exploit code.
Figure 15 - Claude Correctly Identifying the Padding Needed
So, it correctly recognizes that we need to pad by 12 bytes but guesses at the purpose of those bytes. They are not used in the decompiled code, so it is impossible to determine their actual purpose.
## Finding New Vulnerabilities
Let’s see if Claude can identify the attack path for the **_pipelineroot_** flag, which wasn’t expanded on in the Blue-Prints Blog.
Figure 16 - Prompting Claude to Generate a Proof of Concept for the pipelineroot Flag
After generating some Python code (included at end of this post), Claude gives a detailed explanation of the vulnerability.
Figure 17 - Claude Explaining the pipelineroot Path
All that’s left to do is check Claude's work. We’ll use a simple **_ysoserial.net_** payload to pop **_calc.exe_** as our Proof-of-Concept payload, which we’ll use with the code generated by Claude.
```hljs python
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c calc -o raw > e:\tools\payload.bin
We can then run the Proof-of-Concept code generated by Claude.
Figure 18 - Running the Proof-of-Concept Code
Running AddinUtil.exe with the generated -pipelineroot flag, perhaps unsurprisingly, didn’t work. Luckily, we can debug this quite easily with Visual Studio and dotPeek. First, we make sure dotPeek has the .EXE and DLL loaded, then we start its symbol server. Next, we decompile AddinUtil.exe using dotPeek and create a project file. We load this file into Visual Studio, then add dotPeek as a symbol server. Finally, we change the project debug options to start the compiled .EXE, so we can pass our pipelineroot flag as an argument.
Figure 19 - Visual Studio Debug Options
Now, we can add a break point to the decompiled code, run the binary, and enjoy step-through debugging.
After a quick step-through, it becomes apparent that Claude had missed a step.
Figure 20 - AddIns.store File Check
Claude has neglected to create the AddIns directory and named the .store file something else, meaning this check failed and BuildAddInCache was never called. A quick update to the generated Python code resulted in a folder structure that passed all the checks and, when executed, popped calc.exe.
Figure 21 - calc.exe Launched
This exploit isn’t as useful as the -AddinRoot flag because we need to drop a complete folder structure to disk. I haven’t seen a public implementation for this attack path yet, which makes it a good candidate for testing with Claude.
Final Thoughts
In this post, we’ve seen how we can use an MCP server to give Claude the ability to analyze .NET assemblies. We’ve used that ability to find a known vulnerability in a Microsoft-signed binary and seen how we need to be explicit when giving Claude instructions (such as telling it to review every file). Finally, we’ve built a working Proof-of-Concept for an attack path mentioned in the original disclosure of this vulnerability, which was left as an exercise for the reader in the Blue-Prints blog.
While we had to give Claude a few hints along the way, this process of analyzing a file and getting close to a working exploit was much faster than what we could do manually. The next step will be to see if it’s possible to do this at scale, but that will be the subject of a future post.
The full Proof-of-Concept code can be found here.
CloseShow Transcript