Rust BOFs for Cobalt Strike

Overview

Rust BOFs for Cobalt Strike

This took me like 4 days, but I got it working... rust core + alloc for Cobalt Strike BOFs.
This is very much a PoC, but I'd love to see others playing around with it and contributing.

Building

  1. Install mingw
  2. Install nightly rust with the x86_64-pc-windows-gnu and i686-pc-windows-gnu toolchains
  3. Run cargo install cargo-make
  4. Run cargo make
  5. ????
  6. BOFit

Make your own

Edit the entry function in rustbof/src/lib.rs. You can add new args by using the bof_pack function in the aggressor script, just don't change the first two because those are the relocations.

How the fk

I feel like I want to write a blog post about it at some point, but for now, here was the process:

  • How do I compile object files from rust?
    • rustc has an --emit=obj flag that will just emit the object files into the deps folder of the target
  • A BOF is a single object file, not many. Rust compiles each component into its own .o file. How do I combine them?
    • ld has a feature called "relocatable" (-i) which is used for incremental linking
  • The Cobalt Strike loader throws throwing a NullPointerException on beacon_inline_execute. Why?
    • Some decompilation and bytecode debugging led me to find that the CS COFF parser was choking on some symbols in the object. Turns out those symbols were just included as debug (file) info.
    • In my investigation I found that the OBJExecutable and OBJParser classes in cobaltstrike.jar have main functions that take the path to an object file and print a bunch of useful information!
  • How do I remove uneeded symbols in an object file?
    • Well, strip works! strip actually has a --strip-uneeded flag that strips everything not needed for relocations, like debug info!
  • Now the Cobalt Strike BOF loader complains about some undefined symbols like rust_oom and __rust_alloc. At this point I wasn't using alloc at all, but it was still being compiled and then added to the BOF via ld -i. How can I get rid of these symbols?
    • At first I just removed the object file for alloc, since I wasn't using it. Easy.
    • I think at one point I discovered the --gc-sections flag for ld, which allows you to define a root symbol via the -u flag and then it gets rid of any symbols that aren't ever referenced. That also fixes it.
  • OK. At this point we have something that loads up and does not crash. Let's try to import a function from KERNEL32. How do we make rust use __imp_ symbols?
    • We can define the link name via #[link_name = "__imp_KERNEL32$OutputDebugStringA]
  • The issue with the above is that the __imp_ symbols are supposed to be pointers to the import table and not functions themselves, so rust thinks that the symbol is a single pointer to a function and not a double pointer to a function. How the heck do we convince rust that a function pointer is actually a function pointer pointer in a clean way?
    • Well, it turns out we can use unsafe rust to make a pointer into a double pointer, but that's only half of the solution because I don't want to have to say unsafe { make my function pointer a double pointer}(args) every time I want to make a call.
    • It is acutally possible to make rust import symbols via the __imp_ method, but I was only able to get it working on variables and not functions, so it was kind of useless.
  • How can I automatically cast the pointer on call?
    • If you didn't know, rust calls deref when a type is called. So you can wrap the function pointer in a type and then implement core::ops::Deref for that type to cast the pointer to a double pointer on the fly. The result is a successful function call.
  • So now we can import functions, but now what about an allocator? I want to be able to use strings and vectors. That's the nice part of rust core, alloc is easy to implement! The allocator just creates and manages a heap via NTDLL's Rtl*Heap functions. I defined a global allocator that must be initialized to be used. Now I'm getting those undefined symbols from alloc again such as __rust_alloc and rust_oom. Why?
    • It turns out when rust links a binary it creates those symbols and points them to either the system allocator or a custom allocator if one is defined. I need to define them manually.
  • Now I'm getting an undefined symbol that looks like the name of my global allocator. What is happening?
    • BOFs don't use the BSS and the allocator was getting put in the BSS. I just explicitly told rust to put the ALLOCATOR inside the .data section via #[link_section = ".data"]. Fixed.
  • Now that I can allocate memory, let's try using the format! macro in alloc to make an allocated String. It crashes! What gives?
    • This one took me a while to track down. The BOF loader does not relocate things in .data and .rdata, only .text. The COFF parser has a function at pe.OBJExecutable.getRelocations that creates a relocation structure for symbols in .text, but nothing else.
    • The crash was happening in an virtual function table in .rdata that was not getting updated.
    • Regardless, the BOF loader doesn't support type 1 (IMAGE_REL_AMD64_ADDR64), which is what rust was generating for .rdata.
  • How can I get the relocations applied?
    • Write my own bootstrapper to manually apply the relocations before I do anything crazy like vtable references
    • Not that hard, but requires some coordination from the Cobalt Strike client side
    • I used Cobalt Strike's OBJExecutable class to parse the COFF and the Parser class to pack in the extra relocations. This is pretty much what CS does in getRelocations. The rust side gets the info via the BOF arguments and then applies the relocations.
  • How do I know where the beacon is putting the other sections?
    • The BOF loader in beacon is going to choose some place to put the .data and .rdata sections, but we don't know where those are from our code.
    • I ended up adding importable symbols at the beginning of the .text, .rdata, and .data sections via modifying the linker script. If anyone has better ideas here I'd love to hear them.
  • When those symbols were imported as extern static usize variables one of them generates an undefined refptr symbol. How do I stop it from doing that?
    • I ended up importing them as functions and then casting those to usize. A hack, but it gets the job done.
    • After fixups, vtable calls work!
  • On to 32-bit. When you try to load it a bunch of syms are undefined, including a bunch of stuff starting with __. How can we resolve that???
    • 32-bit adds an underscore to a bunch of stuff for some reason I could look up but don't care.
    • I changed the import symbol to be _imp_ instead of __imp_
      • Doing this broke 64 bit so I added a cfg_attr to make the import name have the correct number of underscores depending on the target
    • I added a cfg_attr to the __section_start__ symbols from the linker to use a link name minus one underscore for x86 only.
    • The final symbol is the dreaded chkstk... which I've dealt with at length
  • How can we get chkstk?
    • It is defined in NTDLL, so we can just import NTDLL!_chkstk and define a __chkstk ourselves that calls it

yeah and now both 32 and 64 bit BOFs work.
I haven't tried anything too incredibly fancy yet, but let me know if there are issues.

Versions

I don't know if this will work with future versions of CS or rust. Maybe.
I've been using rust 1.61.0 nightly and CS 4.5.

Known issues

TBD I guess.

TODO

  • Make the bofhelper library more robust
  • Docs, always <3
  • Add some Result action with proper error types

Thanks

You might also like...
A language server for lua written in rust

lua-analyzer lua-analyzer is a lsp server for lua. This is mostly for me to learn the lsp protocol and language analysis so suggestions are helpful. T

Rust library that can be reset if you think it's slow

GoodbyeKT Rust library that can be reset if you think it's slow

Cargo - The Rust package manager

Cargo downloads your Rust project鈥檚 dependencies and compiles your project.

A copypastable guide to implementing simple derive macros in Rust.

A copypastable guide to implementing simple derive macros in Rust. The goal Let's say we have a trait with a getter trait MyTrait {

Rust ABI safe code generator

CGlue offers an easy way to ABI (application binary interface) safety. Just a few annotations and your trait is ready to go!

An example project demonstrating integration with Rust for the ESP32-S2 and ESP32-C3 microcontrollers.

Rust ESP32 Example An example project demonstrating integration with Rust for the ESP32-S2 and ESP32-C3 microcontrollers.

Notion Offical API client library for rust

Notion API client library for rust.

Rust library for program synthesis of string transformations from input-output examples 馃敭

Synox implements program synthesis of string transformations from input-output examples. Perhaps the most well-known use of string program synthesis in end-user programs is the Flash Fill feature in Excel. These string transformations are learned from input-output examples.

Rust for the Windows App SDK

Rust for the Windows App SDK The windows-app crate makes the Windows App SDK (formerly known as Project Reunion) available to Rust developers.

Comments
  • Build fails on 1.66 nightly (Linux)

    Build fails on 1.66 nightly (Linux)

    Builds in Linux seem to require -Zbuild-std which in turn appears to break the linker script:

    [cargo-make] INFO - Running Task: generate-linker-script
    [cargo-make] INFO - Execute Command: "cargo" "build" "-p" "rustbof" "--target" "i686-pc-windows-gnu" "--release" "-Zbuild-std"
      Downloaded unicode-width v0.1.10
      Downloaded compiler_builtins v0.1.79
      Downloaded libc v0.2.131
      Downloaded 3 crates (779.4 KB) in 0.30s
       Compiling compiler_builtins v0.1.79
       Compiling core v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core)
       Compiling libc v0.2.131
       Compiling cc v1.0.73
       Compiling std v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std)
       Compiling windows_i686_gnu v0.33.0
       Compiling paste v1.0.9
       Compiling unwind v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/unwind)
       Compiling rustc-std-workspace-core v1.99.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/rustc-std-workspace-core)
       Compiling alloc v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc)
       Compiling cfg-if v0.1.10
       Compiling cfg-if v1.0.0
       Compiling rustc-demangle v0.1.21
       Compiling rustc-std-workspace-alloc v1.99.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/rustc-std-workspace-alloc)
       Compiling panic_abort v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/panic_abort)
       Compiling panic_unwind v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/panic_unwind)
       Compiling std_detect v0.1.5 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/stdarch/crates/std_detect)
       Compiling hashbrown v0.12.3
       Compiling proc_macro v0.0.0 (/home/user/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/proc_macro)
       Compiling windows-sys v0.33.0
       Compiling bofhelper v0.1.0 (/opt/code/CPP/Cobalt/rust_bof/libs/bofhelper)
       Compiling bofalloc v0.1.0 (/opt/code/CPP/Cobalt/rust_bof/libs/bofalloc)
       Compiling bofentry v0.1.0 (/opt/code/CPP/Cobalt/rust_bof/libs/bofentry)
       Compiling rustbof v0.1.0 (/opt/code/CPP/Cobalt/rust_bof/rustbof)
        Finished release [optimized] target(s) in 16.62s
    [cargo-make] INFO - Running Task: combine-objs
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/alloc-c1376b3f5d68bdf1.o:alloc.982fc090-cgu:(.text+0x894): multiple definition of `__rdl_oom'; target/i686-pc-windows-gnu/release/deps/alloc-74afa7004456a0da.o:alloc.0a9fc5a7-cgu:(.text+0x8a3): first defined here
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/alloc-c1376b3f5d68bdf1.o:alloc.982fc090-cgu:(.text+0x89b): multiple definition of `__rg_oom'; target/i686-pc-windows-gnu/release/deps/alloc-74afa7004456a0da.o:alloc.0a9fc5a7-cgu:(.text+0x8aa): first defined here
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/bofalloc-98ec48f3d7d861fd.o:bofalloc.2293be5f-:(.text+0xa): multiple definition of `__rust_alloc'; target/i686-pc-windows-gnu/release/deps/bofalloc-3814d15b5ff5eacd.o:bofalloc.95ebbabd-:(.text+0x28): first defined here
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/bofalloc-98ec48f3d7d861fd.o:bofalloc.2293be5f-:(.text+0x29): multiple definition of `__rust_dealloc'; target/i686-pc-windows-gnu/release/deps/bofalloc-3814d15b5ff5eacd.o:bofalloc.95ebbabd-:(.text+0x47): first defined here
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/bofalloc-98ec48f3d7d861fd.o:bofalloc.2293be5f-:(.text+0x48): multiple definition of `__rust_realloc'; target/i686-pc-windows-gnu/release/deps/bofalloc-3814d15b5ff5eacd.o:bofalloc.95ebbabd-:(.text+0x66): first defined here
    ...
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/std-9e8e3aeab9a462de.o:std.f1feb203-cgu.0:(.text+0x1d8eb): multiple definition of `rust_begin_unwind'; target/i686-pc-windows-gnu/release/deps/std-44868e07d57d2f0e.o:std.3d4f0bca-cgu.0:(.text+0x19c31): first defined here
    i686-w64-mingw32-ld: target/i686-pc-windows-gnu/release/deps/std-9e8e3aeab9a462de.o:std.f1feb203-cgu.0:(.text+0x1dc87): multiple definition of `rust_panic'; target/i686-pc-windows-gnu/release/deps/std-44868e07d57d2f0e.o:std.3d4f0bca-cgu.0:(.text+0x19fc3): first defined here
    [cargo-make] ERROR - Error while executing command, exit code: 1
    [cargo-make] WARN - Build Failed.
    

    Build environment is Arch, MinGW 12.2.

    opened by sempervictus 4
Owner
wumb0
wumb0
A stupid macro that compiles and executes Rust and spits the output directly into your Rust code

inline-rust This is a stupid macro inspired by inline-python that compiles and executes Rust and spits the output directly into your Rust code. There

William 19 Nov 29, 2022
Learn-rust - An in-depth resource to learn Rust 馃

Learning Rust ?? Hello friend! ?? Welcome to my "Learning Rust" repo, a home for my notes as I'm learning Rust. I'm structuring everything into lesson

Lazar Nikolov 7 Jan 28, 2022
A highly modular Bitcoin Lightning library written in Rust. Its Rust-Lightning, not Rusty's Lightning!

Rust-Lightning is a Bitcoin Lightning library written in Rust. The main crate, lightning, does not handle networking, persistence, or any other I/O. Thus, it is runtime-agnostic, but users must implement basic networking logic, chain interactions, and disk storage. More information is available in the About section.

Lightning Dev Kit 850 Jan 3, 2023
Telegram bot help you to run Rust code in Telegram via Rust playground

RPG_BOT (Rust Playground Bot) Telegram bot help you to run Rust code in Telegram via Rust playground Bot interface The bot supports 3 straightforward

TheAwiteb 8 Dec 6, 2022
`Debug` in rust, but only supports valid rust syntax and outputs nicely formatted using pretty-please

dbg-pls A Debug-like trait for rust that outputs properly formatted code Showcase Take the following code: let code = r#" [ "Hello, World!

Conrad Ludgate 12 Dec 22, 2022
Playing with web dev in Rust. This is a sample Rust microservice that can be deployed on Kubernetes.

Playing with web dev in Rust. This is a sample Rust microservice that can be deployed on Kubernetes.

Andr茅 Gomes 10 Nov 17, 2022
馃悁 Building a federated alternative to reddit in rust

Lemmy A link aggregator / Reddit clone for the fediverse. Join Lemmy 路 Documentation 路 Report Bug 路 Request Feature 路 Releases 路 Code of Conduct About

LemmyNet 7.2k Jan 3, 2023
Applied offensive security with Rust

Black Hat Rust - Early Access Deep dive into offensive security with the Rust programming language Buy the book now! Summary Whether in movies or main

Sylvain Kerkour 2.2k Jan 2, 2023
Rholang runtime in rust

Rholang Runtime A rholang runtime written in Rust.

Jerry.Wang 17 Sep 23, 2022
Easy-to-use optional function arguments for Rust

OptArgs uses const generics to ensure compile-time correctness. I've taken the liberty of expanding and humanizing the macros in the reference examples.

Jonathan Kelley 37 Nov 18, 2022