Skip to content

Commit

Permalink
Finished initial support for scanning MiniDumps for beacons
Browse files Browse the repository at this point in the history
  • Loading branch information
CCob committed Aug 22, 2021
1 parent 6041b1f commit 67a1fec
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 75 deletions.
57 changes: 8 additions & 49 deletions BeaconEye.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
using BeaconEye.Config;
using BeaconEye.Reader;
using Kaitai;
using libyaraNET;
using Mono.Options;
using NtApiDotNet;
using NtApiDotNet.Win32;
using SharpDisasm;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BeaconEye {
class BeaconEye {
Expand Down Expand Up @@ -88,42 +82,7 @@ public static List<int> IndexOfSequence(byte[] buffer, byte[] pattern, int start
}
return positions;
}

static long ReadPointer(NtProcess process, long address) {
if (process.Is64Bit) {
return process.ReadMemory<long>(address);
} else {
return process.ReadMemory<int>(address);
}
}

static List<long> GetHeaps(NtProcess process) {

try {
int numHeaps;
long heapArray;

if (process.Is64Bit) {
numHeaps = process.ReadMemory<int>((long)process.PebAddress + 0xE8);
heapArray = ReadPointer(process, (long)process.PebAddress + 0xF0);
} else {
numHeaps = process.ReadMemory<int>((int)process.PebAddress32 + 0x88);
heapArray = ReadPointer(process, (long)process.PebAddress32 + 0x90);
}

var heaps = new List<long>();
for (int idx = 0; idx < numHeaps; ++idx) {
var heap = ReadPointer(process, heapArray + (idx * (process.Is64Bit ? 8 : 4)));
heaps.Add(heap);
}

return heaps;

} catch (Exception) {
throw new FetchHeapsException();
}
}


static Configuration ProcessHasConfig(ProcessReader process) {

var heaps = process.Heaps;
Expand Down Expand Up @@ -279,7 +238,7 @@ static ScanResult IsBeaconProcess(ProcessReader process, bool monitor) {
Configuration = beaconConfig
};

} catch (FetchHeapsException e) {
} catch (FetchHeapsException) {
return new ScanResult() {
State = ScanState.HeapEnumFailed
};
Expand All @@ -301,10 +260,10 @@ static void Main(string[] args) {
);

OptionSet option_set = new OptionSet()
.Add("v|verbose", "Attach to and monitor beacons found", v => verbose = true)
.Add("m|monitor", "Attach to and monitor beacons found", v => monitor = true)
.Add("f=|filter=", "Filter process list wih names starting with x or file extensions in Minidump mode", v => processFilter = v)
.Add("d=|dump=", "Scan a Minidump for a Cobalt Strike beacon", v => dump = v)
.Add("v|verbose", "Display more verbose output instead of just information on beacons found", v => verbose = true)
.Add("m|monitor", "Attach to and monitor beacons found when scanning live processes", v => monitor = true)
.Add("f=|filter=", "Filter process list with names starting with x (live mode only)", v => processFilter = v)
.Add("d=|dump=", "A folder to use for MiniDump mode to scan for beacons (files with *.dmp or *.mdmp)", v => dump = v)
.Add("h|help", "Display this help", v => showHelp = v != null);

try {
Expand All @@ -330,9 +289,9 @@ static void Main(string[] args) {
}

if (!string.IsNullOrEmpty(dump)) {
procEnum = new MiniDumpProcessEnumerator(dump);
procEnum = new MiniDumpProcessEnumerator(dump, verbose);
} else {
procEnum = new RunningProcessEnumerator();
procEnum = new RunningProcessEnumerator(processFilter);
}

var originalColor = Console.ForegroundColor;
Expand Down
2 changes: 0 additions & 2 deletions BeaconProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
using NtApiDotNet;
using NtApiDotNet.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BeaconEye {
class BeaconProcess {
Expand Down
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,28 @@ BeaconEye scans running processes for active CobaltStrike beacons. When process

## How it works

If a suspected CobaltStrike beacon is found through scanning running processes, BeaconEye attaches itself as a debugger and will begin monitoring beacon activity for C2 traffic (HTTP/HTTPS beacons supported currently).
BeaconEye will scan live processes or MiniDump files for suspected CobaltStrike beacons. In live process mode, BeaconEye optionally attaches itself as a debugger and will begin monitoring beacon activity for C2 traffic (HTTP/HTTPS beacons supported currently).

The AES keys used for encrypting C2 data and mallable profile are decoded on the fly, which enables BeaconEye to extract and decrypt beacon's output when commands are sent via the operator.

A log folder is created per process relative to the current directory where BeaconEye is executed from.
A log folder of activity is created per process relative to the current directory where BeaconEye is executed from.

## Usage

```shell
BeconEye by @_EthicalChaos_
CobaltStrike beacon hunter and command monitoring tool x86_64

-v, --verbose Display more verbose output instead of just
information on beacons found
-m, --monitor Attach to and monitor beacons found when scanning
live processes
-f, --filter=VALUE Filter process list with names starting with x (
live mode only)
-d, --dump=VALUE A folder to use for MiniDump mode to scan for
beacons (files with *.dmp or *.mdmp)
-h, --help Display this help
```

## Features

Expand All @@ -22,6 +39,7 @@ A log folder is created per process relative to the current directory where Beac
* Saves screenshots
* Detects standalone and injected beacons
* Detects beacons masked with built in `sleep_mask`
* Scan running processes or Minidumps offline

## Caveats

Expand All @@ -38,7 +56,7 @@ BeaconEye should be considered **ALPHA**, I'm keen to get feedback on 4.x beacon
* ~~Add command line argument for targeting specific processes~~
* Add command line argument to specify output logging location
* Add support for extracting operator commands
* Support scanning MiniDump files
* ~~Support scanning MiniDump files~~


## References and Thanks
Expand Down
26 changes: 21 additions & 5 deletions Reader/MiniDumpProcessEnumerator.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace BeaconEye.Reader {
class MiniDumpProcessEnumerator : IProcessEnumerator {

public bool Verbose { get; private set; }

readonly string searchPath;

public MiniDumpProcessEnumerator(string searchPath) {
public MiniDumpProcessEnumerator(string searchPath, bool verbose) {
this.searchPath = searchPath;
this.Verbose = verbose;
}

public IEnumerable<ProcessReader> GetProcesses() {
return Directory.EnumerateFiles(searchPath)
.Where(file => file.ToLower().EndsWith("mdmp") || file.ToLower().EndsWith("dmp"))
.Select(p => new MiniDumpReader(p));

var minidumpReaders = new List<ProcessReader>();
var minidumpFiles = Directory.EnumerateFiles(searchPath)
.Where(file => file.ToLower().EndsWith("mdmp") || file.ToLower().EndsWith("dmp"));

foreach(var minidumpFile in minidumpFiles) {
try {
minidumpReaders.Add(new MiniDumpReader(minidumpFile));
}catch(FormatException fe) {
if(Verbose)
Console.WriteLine($"[=] Failed to open minidump {Path.GetFileName(minidumpFile)} with error: {fe.Message}");
}
}

return minidumpReaders;
}
}
}
54 changes: 44 additions & 10 deletions Reader/MiniDumpReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,23 @@ struct PartialTeb64 {
public ulong PebAddress;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PartialTeb32 {
public uint SehFrame;
public uint StackBase;
public uint StackLimit;
public uint SubSystemTib;
public uint FibreData;
public uint DataSlot;
public uint TebAddress;
public uint EnvironmentPointer;
public uint ProcessId;
public uint ThreadId;
public uint ActiveRPCHandle;
public uint ThreadLocalStorageAddr;
public uint PebAddress;
}

Stream miniDumpStream;
BinaryReader miniDumpReader;
List<MiniDumpThread> threads = new List<MiniDumpThread>();
Expand Down Expand Up @@ -228,7 +245,6 @@ public MiniDumpReader(string fileName) : this(new FileStream(fileName, FileMode.
public MiniDumpReader(Stream source) {

miniDumpStream = source;
var br = new BinaryReader(miniDumpStream);

if (!source.CanSeek) {
throw new ArgumentException("Only seekable streams supported");
Expand All @@ -238,6 +254,15 @@ public MiniDumpReader(Stream source) {
source.Seek(0, SeekOrigin.Begin);

var hdr = ReadStruct<Header>();

if(hdr.Signature != 0x504d444d) {
throw new FormatException("Input stream doesn't appear to be a Minidump");
}

if( ((ulong)hdr.Flags | (ulong)MinidumpType.MiniDumpWithFullMemoryInfo) == 0) {
throw new FormatException("Only full Minidump types supported");
}

var directories = new List<Directory>();
source.Seek(hdr.StreamsDirectoryRva, SeekOrigin.Begin);

Expand All @@ -251,33 +276,42 @@ public MiniDumpReader(Stream source) {

if (dir.StreamType == StreamType.ThreadListStream) {

var threadCount = br.ReadInt32();
var threadCount = miniDumpReader.ReadInt32();
for (int idx = 0; idx < threadCount; ++idx) {
threads.Add(ReadStruct<MiniDumpThread>());
}

} else if (dir.StreamType == StreamType.Memory64ListStream) {

var memoryRangeCount = br.ReadUInt64();
memoryFullRVA = br.ReadUInt64();
var memoryRangeCount = miniDumpReader.ReadUInt64();
memoryFullRVA = miniDumpReader.ReadUInt64();
for (uint idx = 0; idx < memoryRangeCount; ++idx) {
memoryInfoFull.Add(ReadStruct<MemoryDescriptorFull>());
}

} else if(dir.StreamType == StreamType.ModuleListStream) {
} else if (dir.StreamType == StreamType.ModuleListStream) {

var moduleCount = br.ReadInt32();
while(moduleCount-- > 0) {
var moduleCount = miniDumpReader.ReadInt32();
while (moduleCount-- > 0) {
modules.Add(ReadStruct<Module>());
}
}else if(dir.StreamType == StreamType.SystemInfoStream) {
} else if (dir.StreamType == StreamType.SystemInfoStream) {
systemInfo = ReadStruct<SystemInfo>();
}
}

processName = Path.GetFileName(ReadMinidumpString(modules[0].ModuleNameRva));
is64 = systemInfo.ProcessorArchitecture == 9;
pebAddress = ReadMemory<PartialTeb64>(threads[0].Teb).PebAddress;
is64 = systemInfo.ProcessorArchitecture == 9;

if (is64) {
var teb = ReadMemory<PartialTeb64>(threads[0].Teb);
processId = (int)teb.ProcessId;
pebAddress = teb.PebAddress;
} else {
var teb = ReadMemory<PartialTeb32>(threads[0].Teb);
processId = (int)teb.ProcessId;
pebAddress = teb.PebAddress;
}
}

string ReadMinidumpString(long rva) {
Expand Down
6 changes: 1 addition & 5 deletions Reader/ProcessReader.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace BeaconEye {
public abstract class ProcessReader {
Expand Down
9 changes: 8 additions & 1 deletion Reader/RunningProcessEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

namespace BeaconEye.Reader {
class RunningProcessEnumerator : IProcessEnumerator {

string filter;

public RunningProcessEnumerator(string filter) {
this.filter = filter;
}

public IEnumerable<ProcessReader> GetProcesses() {
return NtProcess.GetProcesses(ProcessAccessRights.AllAccess)
.Where(p => p.ExitNtStatus == NtStatus.STATUS_PENDING)
.Where(p => p.ExitNtStatus == NtStatus.STATUS_PENDING && (string.IsNullOrEmpty(filter) ? p.Name.Length > 0 : p.Name.ToLower().StartsWith(filter.ToLower())) )
.Select(p => new NtProcessReader(p));
}
}
Expand Down

0 comments on commit 67a1fec

Please sign in to comment.