Skip to content

Commit

Permalink
Fixing experimental callbacks table rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Feb 19, 2024
1 parent 5dda200 commit 28f6acb
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 34 deletions.
7 changes: 7 additions & 0 deletions MythicReactUI/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).

## [0.1.71] - 2024-02-19

### Changed

- Fixed a bug in the new experimental tables where new callbacks would make all entries offset and wrong until refresh
- Updated `graph` browserscript to support `overlay_img` and `overlay_style` for superimposing two icons on top of each other
- Updated list of graph icons to include `diamond` and `skull`
## [0.1.70] - 2024-02-16

### Changed
Expand Down
96 changes: 91 additions & 5 deletions MythicReactUI/src/components/pages/Callbacks/C2PathDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ import CameraAltIcon from '@mui/icons-material/CameraAlt';
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
import {snackActions} from "../../utilities/Snackbar";
import {TaskLabelFlat, getLabelText} from './TaskDisplay';
import {MythicDialog} from "../../MythicComponents/MythicDialog";
import {MythicDialog, MythicViewJSONAsTableDialog} from "../../MythicComponents/MythicDialog";
import {MythicSelectFromListDialog} from "../../MythicComponents/MythicSelectFromListDialog";
import {ManuallyAddEdgeDialog} from "./ManuallyAddEdgeDialog";
import {TaskParametersDialog} from "./TaskParametersDialog";
import {addEdgeMutation, createTaskingMutation, hideCallbackMutation, removeEdgeMutation} from "./CallbackMutations";
import {loadedLinkCommandsQuery} from "./CallbacksGraph";
import {useMutation, gql, useLazyQuery } from '@apollo/client';
import {TaskFromUIButton} from "./TaskFromUIButton";
import {MythicDisplayTextDialog} from "../../MythicComponents/MythicDisplayTextDialog";
import {ResponseDisplayTableDialogTable} from "./ResponseDisplayTableDialogTable";
import SendIcon from '@mui/icons-material/Send';
import {getIconName} from "./ResponseDisplayTable";
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
Expand Down Expand Up @@ -411,6 +417,9 @@ function BrowserscriptNode({data}) {
<div style={{padding: 0, margin: 0, display: "flex", flexDirection: "column"}}>
<Handle type={"source"} position={sourcePosition} />
{data.img}
<div style={{top: 0, right: 0, height: "50%", width: "50%", position: "absolute"}}>
{data.overlay_img}
</div>
<Handle type={"target"} position={targetPosition} />
<Typography style={{textAlign: "center", margin: 0, padding: 0}} >{data.data.label}</Typography>
</div>
Expand Down Expand Up @@ -828,7 +837,7 @@ export const DrawC2PathElementsFlow = ({edges, panel, view_config, theme, contex
position: { x: 0, y: 0 },
type: "agentNode",
height: 50,
width: 100,
width: 50,
parentNode: shouldUseGroups(view_config) ? groupByValue : null,
group: shouldUseGroups(view_config) ? groupByValue : null,
extent: shouldUseGroups(view_config) ? "parent" : null,
Expand Down Expand Up @@ -1633,8 +1642,21 @@ export const DrawBrowserScriptElementsFlowWithProvider = (props) => {
</ReactFlowProvider>
)
}
const DrawBrowserScriptElementsFlow = ({edges, panel, view_config, theme, contextMenu, providedNodes}) => {
const DrawBrowserScriptElementsFlow = ({edges, panel, view_config, theme, contextMenu, providedNodes, task}) => {
const [graphData, setGraphData] = React.useState({nodes: [], edges: [], groups: [], view_config});
const [localContextMenu, setLocalContextMenu] = React.useState(contextMenu);
const [openTaskingButton, setOpenTaskingButton] = React.useState(false);
const [openDictionaryButton, setOpenDictionaryButton] = React.useState(false);
const [openStringButton, setOpenStringButton] = React.useState(false);
const [openTableButton, setOpenTableButton] = React.useState(false);
const [taskingData, setTaskingData] = React.useState({});
const finishedTasking = () => {
setOpenTaskingButton(false);
setOpenDictionaryButton(false);
setOpenStringButton(false);
setOpenTableButton(false);
setTaskingData({});
}
const [nodes, setNodes] = React.useState();
const [edgeFlow, setEdgeFlow] = React.useState([]);
const [openContextMenu, setOpenContextMenu] = React.useState(false);
Expand Down Expand Up @@ -1665,8 +1687,44 @@ const DrawBrowserScriptElementsFlow = ({edges, panel, view_config, theme, contex
top: event.clientY,
left: event.clientX,
});
if(node?.data?.buttons?.length > 0){
setLocalContextMenu([...contextMenu, ...node?.data?.buttons?.map(b => {
let title = b.name;
if( b?.startIcon){
title = <><FontAwesomeIcon icon={getIconName(b?.startIcon)} style={{color: b?.startIconColor || ""}}/> {title}</>;
} else if(b.type === "task"){
title = <><SendIcon fontSize={"sm"} /> {b.name}</>
}
return {
title: title,
onClick: function(node) {
switch(b.type){
case "task":
setTaskingData(b);
setOpenTaskingButton(true);
break;
case "dictionary":
setTaskingData(b);
setOpenDictionaryButton(true);
break;
case "string":
setTaskingData(b);
setOpenStringButton(true);
break;
case "table":
setTaskingData(b);
setOpenTableButton(true);
break;
}
}
}
})]);
} else {
setLocalContextMenu([...contextMenu]);
}

setOpenContextMenu(true);
}, [contextMenu])
}, [contextMenu]);
const onPaneClick = useCallback( () => {
setOpenContextMenu(false);
const updatedEdges = graphData.edges.map( e => {
Expand Down Expand Up @@ -1970,14 +2028,42 @@ const DrawBrowserScriptElementsFlow = ({edges, panel, view_config, theme, contex
</ReactFlow>
{openContextMenu &&
<div style={{...contextMenuCoord, position: "fixed"}} className="context-menu">
{contextMenu.map( (m) => (
{localContextMenu.map( (m) => (
<Button key={m.title} variant={"contained"} className="context-menu-button" onClick={() => {
m.onClick(contextMenuNode.current);
setOpenContextMenu(false);
}}>{m.title}</Button>
))}
</div>
}
{openTaskingButton &&
<TaskFromUIButton ui_feature={taskingData.ui_feature}
callback_id={task.callback_id}
parameters={taskingData.parameters}
openDialog={taskingData?.openDialog || false}
getConfirmation={taskingData?.getConfirmation || false}
acceptText={taskingData?.acceptText || "confirm"}
onTasked={finishedTasking}/>
}
{openDictionaryButton &&
<MythicDialog fullWidth={true} maxWidth="lg" open={openDictionaryButton}
onClose={finishedTasking}
innerDialog={<MythicViewJSONAsTableDialog title={taskingData.title} leftColumn={taskingData.leftColumnTitle}
rightColumn={taskingData.rightColumnTitle} value={taskingData.value} onClose={finishedTasking} />}
/>
}
{openStringButton &&
<MythicDisplayTextDialog fullWidth={true} maxWidth="lg" open={openStringButton} title={taskingData?.title || "Title Here"} value={taskingData?.value || ""}
onClose={finishedTasking}
/>
}
{openTableButton &&
<MythicDialog fullWidth={true} maxWidth="xl" open={openTableButton}
onClose={finishedTasking}
innerDialog={<ResponseDisplayTableDialogTable title={taskingData?.title || "Title Here"}
table={taskingData?.value || {}} callback_id={task.callback_id} onClose={finishedTasking} />}
/>
}
</div>

)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,10 @@ function CallbacksTableMaterialReactTablePreMemo(props){
for(const [key, val] of Object.entries(columnVisibility)){
if(val){
let newKey = columnFields.filter(c => c.key === key);
shown.push(newKey[0].name);
if(newKey.length > 0){
shown.push(newKey[0].name);
}

}
}
localStorage.setItem("callbacks_table_columns", JSON.stringify(shown));
Expand Down Expand Up @@ -576,7 +579,6 @@ function CallbacksTableMaterialReactTablePreMemo(props){
autoResetPageIndex: false,
enableFacetedValues: true,
enablePagination: true,
//enablePagination: false,
//enableRowVirtualization: true,
enableBottomToolbar: false,
enableStickyHeader: true,
Expand Down
19 changes: 13 additions & 6 deletions MythicReactUI/src/components/pages/Callbacks/CallbacksTableRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export const CallbacksTableIDCell = React.memo(({rowData, toggleLock, updateDesc
if(rowData.description !== rowDataStatic.description){
update = true;
}
if(rowData.id !== rowDataStatic.id){
update = true;
}
if(update){
setRowDataStatic(rowData);
}
Expand Down Expand Up @@ -378,12 +381,11 @@ export const CallbacksTableLastCheckinCell = React.memo( ({rowData, cellData}) =
)
});
export const CallbacksTablePayloadTypeCell = React.memo( ({rowData}) => {
const payloadTypeName = React.useRef(rowData.payload.payloadtype.name)
return (
<MythicStyledTooltip title={payloadTypeName.current}>
<MythicStyledTooltip title={rowData?.payload?.payloadtype?.name}>
<img
style={{width: "35px", height: "35px"}}
src={"/static/" + payloadTypeName.current + ".svg"}
src={"/static/" + rowData?.payload?.payloadtype?.name + ".svg"}
/>
</MythicStyledTooltip>
)
Expand Down Expand Up @@ -478,7 +480,7 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
}else if(routes === 0 && hasEgressRoute){
setHasEgressRoute(false);
}
}, [callbackgraphedgesAll]);
}, [callbackgraphedgesAll, localRowData]);
useEffect( () => {
const getEdges = (activeOnly) => {
//update our aggregate of callbackgraphedges for both src and dst that involve us
Expand Down Expand Up @@ -527,7 +529,7 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
setCallbackgraphedges(myActiveEdges);
setCallbackgraphedgesAll(myEdges);

}, [initialCallbackGraphEdges]);
}, [initialCallbackGraphEdges, localRowData]);
useEffect( () => {
//determine if there are any active routes left at all
const activeRoutes = callbackgraphedges.filter( (edge) => {
Expand All @@ -542,6 +544,11 @@ export const CallbacksTableC2Cell = React.memo(({rowData}) => {
setActiveEgress(theme.palette.success.main);
}
}, [callbackgraphedges, theme.palette.success.main, theme.palette.error.main]);
useEffect( () => {
if(rowData.id !== localRowData.id){
setLocalRowData(rowData);
}
}, [rowData]);
return (
<div>
{hasEgressRoute ?
Expand Down Expand Up @@ -592,7 +599,7 @@ export const CallbacksTableOSCell = React.memo( ({rowData, cellData}) => {
default:
return <FontAwesomeIcon icon={faQuestion} size="2x" style={{cursor: "pointer"}} onClick={displayOSInfo} />
}
}, []);
}, [rowData?.payload?.os]);
const displayOSInfo = React.useCallback( () => {
setOpenOSDialog(true);
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
import Inventory2TwoToneIcon from '@mui/icons-material/Inventory2TwoTone';
import {MythicDialog, MythicViewJSONAsTableDialog} from "../../MythicComponents/MythicDialog";
import HelpTwoToneIcon from '@mui/icons-material/HelpTwoTone';
import DiamondIcon from '@mui/icons-material/Diamond';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {faSkullCrossbones} from '@fortawesome/free-solid-svg-icons';

const getIcons = (node) => {
if(node.img === undefined){return null}
const style = {...node?.style, fontSize: 40, margin: "auto"};
if(node.img.startsWith("http")){
return <img alt={node.img} src={node.img} className={"circleImageNode"} />
const getIcons = (img, nodeStyle) => {
if(img === undefined){return null}
const style = {...nodeStyle, fontSize: 30, height: "50%", width: "50%", margin: "auto"};
if(img.startsWith("http")){
return <img alt={img} src={img} className={"circleImageNode"} />
}
switch(node.img){
switch(img){
case "group":
return <GroupsIcon sx={style} />
case "computer":
Expand All @@ -34,6 +37,10 @@ const getIcons = (node) => {
return <Inventory2TwoToneIcon sx={style} />
case "help":
return <HelpTwoToneIcon sx={style} />
case "diamond":
return <DiamondIcon sx={style} />
case "skull":
return <FontAwesomeIcon icon={faSkullCrossbones} style={style} />
default:
return null
}
Expand All @@ -54,20 +61,19 @@ export const ResponseDisplayGraph = ({graph, task, expand}) =>{
scrollContent()
}, []);
const finalGraphNodes = graph?.nodes?.map( n => {
return {...n, img: getIcons(n)}
return {...n, img: getIcons(n?.img, n?.style || {}), overlay_img: getIcons(n?.overlay_img, n?.overlay_style)}
})
const contextMenu = React.useMemo(() => {return [
{
title: 'View All Data',
onClick: function(node) {
console.log(node)
dictionaryData.current = node.data;
setViewAllDataDialogOpen(true);
}
},
}
]}, []);
return (
<div style={{height: expand ? "100%" : "600px", width: "100%", position: "relative"}}>
<div style={{height: expand ? "100%" : "400px", width: "100%", position: "relative"}}>
{viewAllDataDialog &&
<MythicDialog fullWidth={true} maxWidth="lg" open={viewAllDataDialog}
onClose={()=>{setViewAllDataDialogOpen(false);}}
Expand All @@ -80,7 +86,7 @@ export const ResponseDisplayGraph = ({graph, task, expand}) =>{
}
<DrawBrowserScriptElementsFlowWithProvider theme={theme} edges={graph.edges} providedNodes={finalGraphNodes}
view_config={{group_by: graph?.group_by || "", rankDir: "LR",}}
contextMenu={contextMenu}
contextMenu={contextMenu} task={task}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const onCopyToClipboard = (data) => {
snackActions.error("Failed to copy text");
}
}
const getIconName = (iconName) => {
export const getIconName = (iconName) => {
switch(iconName.toLowerCase()){
case "openfolder":
case "folder":
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import jwt_decode from 'jwt-decode';
import {meState} from './cache';

export const mythicVersion = "3.2.18-rc5";
export const mythicUIVersion = "0.1.70";
export const mythicUIVersion = "0.1.71";

let fetchingNewToken = false;

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.00d51b79.css",
"main.js": "/new/static/js/main.d9160a39.js",
"main.js": "/new/static/js/main.67c73a2a.js",
"static/media/[email protected]": "/new/static/media/[email protected]",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.00d51b79.css.map": "/new/static/css/main.00d51b79.css.map",
"main.d9160a39.js.map": "/new/static/js/main.d9160a39.js.map"
"main.67c73a2a.js.map": "/new/static/js/main.67c73a2a.js.map"
},
"entrypoints": [
"static/css/main.00d51b79.css",
"static/js/main.d9160a39.js"
"static/js/main.67c73a2a.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.d9160a39.js"></script><link href="/new/static/css/main.00d51b79.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.67c73a2a.js"></script><link href="/new/static/css/main.00d51b79.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
3 changes: 3 additions & 0 deletions mythic-react-docker/mythic/public/static/js/main.67c73a2a.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions mythic-react-docker/mythic/public/static/js/main.d9160a39.js

This file was deleted.

0 comments on commit 28f6acb

Please sign in to comment.