Skip to content

Commit

Permalink
update last checkin processing for linked callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Aug 6, 2024
1 parent 46be2e2 commit e0eab27
Show file tree
Hide file tree
Showing 17 changed files with 146 additions and 19 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.0-rc16] - 2024-08-06

### Changed

- Updated the last checkin time for linked agents to match that of the egress agent
- this includes matching "Streaming Now" displays

## [3.3.0-rc15] - 2024-08-05

### Changed
Expand Down
6 changes: 6 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.16] - 2024-08-06

### Changed

- Fixed issue in showing egress pathing in UI for 2+ linked nodes

## [0.2.15] - 2024-08-06

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,13 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
//look at all of the edges in myEdges and see if there are any edges that share a source/destination in props.callbackgraphedges that are _not_ in myEdges so far
const newEdges = initialCallbackGraphEdges?.reduce( (prev, edge) => {
//looking to see if we should add 'edge' to our list of relevant edges
if(prev.includes(edge)){return [...prev]}
const alreadyIncludes = prev.filter( (e) => e.id === edge.id);
if(alreadyIncludes.length > 0){
return [...prev]
}
//look through all of the previous edges we know about and see if there's a matching source/destination id with the new edge
const matching = prev.filter( (e) => {
if(e.source.id === edge.source.id || e.source.id === edge.destination.id || e.destination.id === edge.source.id ){
if(e.source.id === edge.source.id || e.source.id === edge.destination.id || e.destination.id === edge.source.id || e.destination.id === edge.destination.id ){
if(activeOnly){
if(edge.end_timestamp === null) { return true}
else{return false}
Expand All @@ -435,12 +438,12 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
return false;
});
if(matching.length > 0){
return [...prev, edge];
return [...prev, {...edge}];
}else{
return [...prev];
}
}, [...myEdges]) || [];
foundMore = newEdges.length > myEdges;
foundMore = newEdges.length > myEdges.length;
myEdges = [...newEdges];
}
return myEdges;
Expand All @@ -453,6 +456,7 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
}, [initialCallbackGraphEdges, localRowData]);
useEffect( () => {
//determine if there are any active routes left at all
//console.log(localRowData.display_id, callbackgraphedges)
const activeRoutes = callbackgraphedges.filter( (edge) => {
if(!edge.c2profile.is_p2p && edge.end_timestamp === null){
return true;
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {snackActions} from './components/utilities/Snackbar';
import jwt_decode from 'jwt-decode';
import {meState} from './cache';

export const mythicUIVersion = "0.2.15";
export const mythicUIVersion = "0.2.16";

let fetchingNewToken = false;

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.0-rc15
3.3.0-rc16
2 changes: 1 addition & 1 deletion mythic-docker/src/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.0-rc15
3.3.0-rc16
7 changes: 6 additions & 1 deletion mythic-docker/src/grpc/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,16 @@ func (t *pushC2Server) GetChannelTimeout() time.Duration {
return t.channelSendTimeout
}

func Initialize() {
var pushC2StreamingConnectNotification chan int
var pushC2StreamingDisconnectNotification chan int

func Initialize(connectNotification chan int, disconnectNotification chan int) {
// need to open a port to accept gRPC connections
var (
connectString string
)
pushC2StreamingConnectNotification = connectNotification
pushC2StreamingDisconnectNotification = disconnectNotification
// initialize the clients
TranslationContainerServer.clients = make(map[string]*grpcTranslationContainerClientConnections)
TranslationContainerServer.connectionTimeout = connectionTimeoutSeconds * time.Second
Expand Down
8 changes: 8 additions & 0 deletions mythic-docker/src/grpc/push_c2_stream_one_to_many.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ func updatePushC2OneToManyLastCheckinDisconnectTimestamp(c2ProfileName string, t
if err != nil {
logging.LogError(err, "Failed to update callback edge when push c2 disconnected")
}
select {
case pushC2StreamingDisconnectNotification <- callbackID:
default:
}
}
// now that the c2 disconnected, flush all the cached data about the connections
if trackingID == "" {
Expand Down Expand Up @@ -376,4 +380,8 @@ func updatePushC2OneToManyLastCheckinConnectTimestamp(fromMythicResponse RabbitM
logging.LogInfo("Added new callbackgraph edge in pushC2 one to many", "c2", c2ProfileId, "callback", callbackId)
}
}
select {
case pushC2StreamingConnectNotification <- callbackId:
default:
}
}
9 changes: 8 additions & 1 deletion mythic-docker/src/grpc/push_c2_stream_one_to_one.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,10 @@ func updatePushC2LastCheckinDisconnectTimestamp(callbackId int, c2ProfileName st
logging.LogError(err, "Failed to update callback edge when push c2 disconnected")
}
}

select {
case pushC2StreamingDisconnectNotification <- callbackId:
default:
}
}
func updatePushC2LastCheckinConnectTimestamp(callbackId int, c2ProfileName string, operationId int) {
c2ProfileId := -1
Expand Down Expand Up @@ -343,4 +346,8 @@ func updatePushC2LastCheckinConnectTimestamp(callbackId int, c2ProfileName strin
logging.LogInfo("Added new callbackgraph edge in pushC2", "c2", c2ProfileId, "callback", callbackId)
}
}
select {
case pushC2StreamingConnectNotification <- callbackId:
default:
}
}
6 changes: 5 additions & 1 deletion mythic-docker/src/rabbitmq/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func (r *rabbitMQConnection) startListeners() {
go checkContainerStatus()
}

var pushC2StreamingConnectNotification = make(chan int, 100)
var pushC2StreamingDisconnectNotification = make(chan int, 100)

func Initialize() {
RabbitMQConnection.channelMutexMap = make(map[string]*channelMutex)
go invalidateAllSpectatorAPITokens()
Expand All @@ -97,14 +100,15 @@ func Initialize() {
submittedTasksAwaitingFetching.Initialize()
updatePushC2CallbackTime()
// initialize gRPC
grpc.Initialize()
grpc.Initialize(pushC2StreamingConnectNotification, pushC2StreamingDisconnectNotification)
// start listening for eventing messages
go listenForEvents()
go initializeEventGroupCronSchedulesOnStart()
// start listening for new messages from push c2 profiles, needs gRPC initialized first
go processAgentMessageFromPushC2()
go interceptProxyDataToAgentForPushC2()
go checkIfActiveCallbacksAreAliveForever()
go listenForPushConnectDisconnectMessages()
go func() {
// wait 20s for things to stabilize a bit, then send a startup message
time.Sleep(time.Second * 30)
Expand Down
4 changes: 4 additions & 0 deletions mythic-docker/src/rabbitmq/util_agent_message_push_c2.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func sendMessageToDirectPushC2(callbackID int, message map[string]interface{}, u
}

}
func isCallbackStreaming(callbackID int) bool {
_, _, _, _, _, _, err := grpc.PushC2Server.GetPushC2ClientInfo(callbackID)
return err == nil
}

type interceptProxyToAgentMessage struct {
MessagesToAgent chan proxyToAgentMessage
Expand Down
84 changes: 83 additions & 1 deletion mythic-docker/src/rabbitmq/util_callback_graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
databaseStructs "github.com/its-a-feature/Mythic/database/structs"
"github.com/its-a-feature/Mythic/logging"
"github.com/its-a-feature/Mythic/utils"
"github.com/jmoiron/sqlx"
"sync"
"time"
)
Expand Down Expand Up @@ -112,6 +113,79 @@ func (c *bfsCache) Add(sourceId int, destinationId int, bfsPath []cbGraphAdjMatr
}
}

func (g *cbGraph) getAllChildIDs(callbackId int) []int {
g.lock.RLock()
defer g.lock.RUnlock()
visitedIDs := map[int]bool{}
needToVisitIDs := []int{callbackId}
callbackIDsToUpdate := []int{callbackId}
for len(needToVisitIDs) > 0 {
// get the next id we're going to check
currentId := needToVisitIDs[0]
// remove this id from the list of ids still to visit
needToVisitIDs = needToVisitIDs[1:]
// get the immediate children from this id
immediateChildren, exists := g.adjMatrix[currentId]
if !exists {
// this callback has no children, continue
continue
}
for i, _ := range immediateChildren {
// check if we've already visited this id, if so, move on
if _, visited := visitedIDs[immediateChildren[i].SourceId]; !visited {
// mark that we've visited this id
visitedIDs[immediateChildren[i].SourceId] = true
if !isCallbackStreaming(immediateChildren[i].SourceId) {
// add this id as an id for the next iteration to check its children
needToVisitIDs = append(needToVisitIDs, immediateChildren[i].SourceId)
callbackIDsToUpdate = append(callbackIDsToUpdate, immediateChildren[i].SourceId)
}

}
if _, visited := visitedIDs[immediateChildren[i].DestinationId]; !visited {
// mark that we've visited this id
visitedIDs[immediateChildren[i].DestinationId] = true
if !isCallbackStreaming(immediateChildren[i].DestinationId) {
// add this id as an id for the next iteration to check its children
needToVisitIDs = append(needToVisitIDs, immediateChildren[i].DestinationId)
callbackIDsToUpdate = append(callbackIDsToUpdate, immediateChildren[i].DestinationId)
}
}
}
}
return callbackIDsToUpdate
}
func updateTimes(updatedTime time.Time, callbackIDs []int) {
query, args, err := sqlx.Named(`UPDATE callback SET last_checkin=:last_checkin WHERE id IN (:ids)`,
map[string]interface{}{"last_checkin": updatedTime, "ids": callbackIDs})
if err != nil {
logging.LogError(err, "Failed to make named statement for updating last checkin of callback ids")
return
}
query, args, err = sqlx.In(query, args...)
if err != nil {
logging.LogError(err, "Failed to do sqlx.In for updating last checkin of callback ids")
return
}
query = database.DB.Rebind(query)
_, err = database.DB.Exec(query, args...)
if err != nil {
logging.LogError(err, "Failed to update callback time when push one-to-many c2 disconnected")
return
}
}
func listenForPushConnectDisconnectMessages() {
for {
select {
case connectCallbackId := <-pushC2StreamingConnectNotification:
callbackIDs := callbackGraph.getAllChildIDs(connectCallbackId)
go updateTimes(time.UnixMicro(0), callbackIDs)
case disconnectCallbackId := <-pushC2StreamingDisconnectNotification:
callbackIDs := callbackGraph.getAllChildIDs(disconnectCallbackId)
go updateTimes(time.Now().UTC(), callbackIDs)
}
}
}
func (g *cbGraph) Initialize() {
edges := []databaseStructs.Callbackgraphedge{}
g.adjMatrix = make(map[int][]cbGraphAdjMatrixEntry, 0)
Expand Down Expand Up @@ -140,7 +214,6 @@ func (g *cbGraph) Initialize() {
}
func (g *cbGraph) Add(source databaseStructs.Callback, destination databaseStructs.Callback, c2profileName string) {
g.lock.Lock()
defer g.lock.Unlock()
if _, ok := g.adjMatrix[source.ID]; !ok {
// add it
g.adjMatrix[source.ID] = append(g.adjMatrix[source.ID], cbGraphAdjMatrixEntry{
Expand All @@ -155,6 +228,13 @@ func (g *cbGraph) Add(source databaseStructs.Callback, destination databaseStruc
for _, dest := range g.adjMatrix[source.ID] {
if dest.DestinationId == destination.ID && dest.C2ProfileName == c2profileName {
//logging.LogDebug("Found existing p2p connection, not adding new one to memory")
updateTime := time.Now().UTC()
if isCallbackStreaming(source.ID) {
updateTime = time.UnixMicro(0)
}
g.lock.Unlock()
callbackIDs := g.getAllChildIDs(source.ID)
go updateTimes(updateTime, callbackIDs)
return
}
}
Expand All @@ -166,6 +246,7 @@ func (g *cbGraph) Add(source databaseStructs.Callback, destination databaseStruc
C2ProfileName: c2profileName,
})
}
g.lock.Unlock()
return
}
func (g *cbGraph) AddByAgentIds(source string, destination string, c2profileName string) {
Expand Down Expand Up @@ -263,6 +344,7 @@ func (g *cbGraph) Remove(sourceId int, destinationId int, c2profileName string)
g.adjMatrix[sourceId][len(g.adjMatrix[sourceId])-1] = cbGraphAdjMatrixEntry{}
g.adjMatrix[sourceId] = g.adjMatrix[sourceId][:len(g.adjMatrix[sourceId])-1]
//g.adjMatrix[sourceId] = append(g.adjMatrix[sourceId][:foundIndex], g.adjMatrix[sourceId][foundIndex:]...)
logging.LogDebug("removing cached BFSCache", "sourceID", sourceId, "destinationID", destinationId)
BFSCache.Remove(sourceId, destinationId, c2profileName)
}
}
Expand Down
6 changes: 3 additions & 3 deletions mythic-react-docker/mythic/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/new/static/css/main.7e143bf2.css",
"main.js": "/new/static/js/main.0f2358b3.js",
"main.js": "/new/static/js/main.badaa141.js",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.7e143bf2.css.map": "/new/static/css/main.7e143bf2.css.map",
"main.0f2358b3.js.map": "/new/static/js/main.0f2358b3.js.map"
"main.badaa141.js.map": "/new/static/js/main.badaa141.js.map"
},
"entrypoints": [
"static/css/main.7e143bf2.css",
"static/js/main.0f2358b3.js"
"static/js/main.badaa141.js"
]
}
2 changes: 1 addition & 1 deletion mythic-react-docker/mythic/public/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.0f2358b3.js"></script><link href="/new/static/css/main.7e143bf2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.badaa141.js"></script><link href="/new/static/css/main.7e143bf2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit e0eab27

Please sign in to comment.