Skip to content
This repository has been archived by the owner on Oct 20, 2023. It is now read-only.

Commit

Permalink
Add ability to view cobalt strike raw log files (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
GoldingAustin authored Aug 23, 2023
1 parent 6a0b031 commit 51e1fff
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 19 deletions.
11 changes: 11 additions & 0 deletions applications/client/src/store/campaign/campaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,4 +244,15 @@ export class CampaignStore extends ExtendedModel(() => ({
direction: direction as SortDirection,
};
}

@computed get parsers(): string[] {
if (this.id)
return (
(this.appStore?.graphqlStore.campaigns
.get(this.id)
?.parsers?.map((parser) => parser.parserName)
.filter(Boolean) as string[]) || []
);
return [];
}
}
15 changes: 15 additions & 0 deletions applications/client/src/store/graphql/RootStore.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export enum RootStoreBaseQueries {
queryHosts = 'queryHosts',
queryImages = 'queryImages',
queryLinks = 'queryLinks',
queryLogFilesByBeaconId = 'queryLogFilesByBeaconId',
queryLogs = 'queryLogs',
queryLogsByBeaconId = 'queryLogsByBeaconId',
queryNonHidableEntities = 'queryNonHidableEntities',
Expand Down Expand Up @@ -633,6 +634,20 @@ export class RootStoreBase extends ExtendedModel(
!!clean
);
}
// Get logs from beacon sorted by time. The goal is to be able to re-create the full log for that beacon.
@modelAction queryLogFilesByBeaconId(
variables: { beaconId: string; campaignId: string },
_?: any,
options: QueryOptions = {},
clean?: boolean
) {
return this.query<{ logFilesByBeaconId: StringModel[] }>(
`query logFilesByBeaconId($beaconId: String!, $campaignId: String!) { logFilesByBeaconId(beaconId: $beaconId, campaignId: $campaignId) }`,
variables,
options,
!!clean
);
}
// Get log entries by ids
@modelAction queryLogs(
variables: { beaconId?: string; campaignId: string; hostId?: string },
Expand Down
1 change: 1 addition & 0 deletions applications/client/src/store/util/cleaned-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const hostQuery = hostModelPrimitives.cobaltStrikeServer.beaconIds
export const campaignsQuery = campaignModelPrimitives
.lastOpenedBy((user) => user)
.creator((user) => user)
.parsers((parser) => parser.parserName)
.toString();

export const rawLogOutputQuery = logEntryModelPrimitives;
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {} from '@blueprintjs/core';
import { Button, ButtonGroup, Divider, NonIdealState, Spinner } from '@blueprintjs/core';
import { Copy16, UpToTop16 } from '@carbon/icons-react';
import { Copy16, UpToTop16, Document16 } from '@carbon/icons-react';
import { css } from '@emotion/react';
import type { DialogExProps } from '@redeye/client/components';
import { CarbonIcon, DialogEx } from '@redeye/client/components';
Expand All @@ -26,6 +25,7 @@ export const RawLogsDialog = observer<RawLogsViewerProps>(({ ...props }) => {
const scrollTopTargetRef = useRef<HTMLDivElement>(null);

const state = createState({
showLogFile: false,
get isOpen(): boolean {
return (
!!(this.beacon && store.router.queryParams['raw-logs']) ||
Expand All @@ -46,6 +46,7 @@ export const RawLogsDialog = observer<RawLogsViewerProps>(({ ...props }) => {
return this.command?.input?.current?.id;
},
onClose() {
this.showLogFile = false;
store.router.updateQueryParams({ queryParams: { 'raw-logs': undefined, 'raw-command': undefined } });
},
handleCopyText(logsByBeaconId) {
Expand Down Expand Up @@ -79,6 +80,23 @@ export const RawLogsDialog = observer<RawLogsViewerProps>(({ ...props }) => {
{ enabled: state.isOpen }
);

const {
data: { logFilesByBeaconId } = {},
isLoading: isLoadingFiles,
isError: isErrorFiles,
} = useQuery(
['rawLogFile', store.campaign.id, state.commandInputId],
async () =>
await store.graphqlStore.queryLogFilesByBeaconId(
{
campaignId: store.campaign.id!,
beaconId: state.beacon?.id!,
},
selectFromLogEntry().toString()
),
{ enabled: state.isOpen && state.showLogFile && store.campaign.parsers.includes('cobalt-strike-parser') }
);

if (!state.command && !state.beacon) return null;

return (
Expand Down Expand Up @@ -106,6 +124,16 @@ export const RawLogsDialog = observer<RawLogsViewerProps>(({ ...props }) => {
</Txt>
</PanelHeader>
<ButtonGroup>
{store.campaign.parsers.includes('cobalt-strike-parser') ? (
<Button
cy-test="view-log-file"
text="View Log File"
rightIcon={<CarbonIcon icon={Document16} />}
active={state.showLogFile}
onClick={() => state.update('showLogFile', !state.showLogFile)}
minimal
/>
) : null}
<Button
cy-test="scroll-to-top"
text="Scroll To Top"
Expand All @@ -127,21 +155,29 @@ export const RawLogsDialog = observer<RawLogsViewerProps>(({ ...props }) => {
}
>
<div ref={scrollTopTargetRef} />
{isLoading && <Spinner css={messagePaddingStyles} />}
{isError && <NonIdealState title="Unable to fetch Logs" icon="error" css={messagePaddingStyles} />}
{(isLoading || (state.showLogFile && isLoadingFiles)) && <Spinner css={messagePaddingStyles} />}
{(isError || (state.showLogFile && isErrorFiles)) && (
<NonIdealState title="Unable to fetch Logs" icon="error" css={messagePaddingStyles} />
)}
<div cy-test="log" css={outputScrollWrapperStyle}>
<div css={outputOverflowWrapperStyle}>
{logsByBeaconId
?.filter((logEntry) => !!getTextFromLog(logEntry))
.map((logEntry) => {
const isScrollTarget = state.commandLogEntryIds?.includes?.(logEntry.id);
const Component = isScrollTarget ? ScrollTargetPre : 'pre';
return (
<Component key={logEntry.id} cy-test="log-entry" css={[preStyles]}>
{getTextFromLog(logEntry)}
</Component>
);
})}
{state.showLogFile
? logFilesByBeaconId?.map((file) => (
<pre cy-test="log-entry" css={[preStyles]}>
{file}
</pre>
))
: logsByBeaconId
?.filter((logEntry) => !!getTextFromLog(logEntry))
.map((logEntry) => {
const isScrollTarget = state.commandLogEntryIds?.includes?.(logEntry.id);
const Component = isScrollTarget ? ScrollTargetPre : 'pre';
return (
<Component key={logEntry.id} cy-test="log-entry" css={[preStyles]}>
{getTextFromLog(logEntry)}
</Component>
);
})}
</div>
</div>
</DialogEx>
Expand Down
3 changes: 3 additions & 0 deletions applications/server/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@ type Query {
"""Get all links"""
links(campaignId: String!, hidden: Boolean = false): [Link]

"""Get log files from beacon"""
logFilesByBeaconId(beaconId: String!, campaignId: String!): [String!]!

"""Get log entries by ids"""
logs(beaconId: String, campaignId: String!, hostId: String): [LogEntry]

Expand Down
4 changes: 2 additions & 2 deletions applications/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ const castConfig = (cliArgs: cliArgs): ConfigDefinition => {
overrides.production = false;
overrides.databaseMode = DatabaseMode.DEV_PERSIST;
}
if (cliArgs.port) overrides.port = cliArgs.port;
if (cliArgs.port) overrides.port = parseInt(`${cliArgs.port}`, 10);
if (cliArgs.redTeam) overrides.redTeam = true;
if (cliArgs.childProcesses) overrides.maxParserSubprocesses = cliArgs.childProcesses;
if (cliArgs.childProcesses) overrides.maxParserSubprocesses = parseInt(`${cliArgs.childProcesses}`, 10);
if (cliArgs.password) overrides.password = cliArgs.password;
if (cliArgs.parsers) {
if (cliArgs.parsers === true) {
Expand Down
2 changes: 1 addition & 1 deletion applications/server/src/store/command-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class CommandTypeCountResolvers {
hidden: boolean = false
): Promise<CommandTypeCount[]> {
const em = await connectToProjectEmOrFail(campaignId, ctx);
const commands = await em.find(Command, beaconHidden(hidden), { populate: false });
const commands = await em.find(Command, beaconHidden(hidden), { populate: ['commandGroups'] });
const countObj = commands.reduce<Record<string, CountObjItem>>((acc, current) => {
if (acc[current.inputText]) {
acc[current.inputText] = {
Expand Down
23 changes: 23 additions & 0 deletions applications/server/src/store/log-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connectToProjectEmOrFail } from './utils/project-db';
import { RelationPath } from './utils/relation-path';
import type { Relation } from './utils/relation-path';
import type { GraphQLContext } from '../types';
import { readFileSync } from 'fs-extra';

@Resolver(LogEntry)
export class LogResolvers {
Expand Down Expand Up @@ -56,4 +57,26 @@ export class LogResolvers {

return logs;
}

@Authorized()
@Query(() => [String], {
description: 'Get log files from beacon',
})
async logFilesByBeaconId(
@Ctx() ctx: GraphQLContext,
@Arg('campaignId', () => String) campaignId: string,
@Arg('beaconId', () => String) beaconId: string
): Promise<string[]> {
const em = await connectToProjectEmOrFail(campaignId, ctx);
const logs: LogEntry[] = await em.createQueryBuilder(LogEntry).where({ beacon: beaconId });
const files: string[] = [];
const seenFiles = new Set<string>();
for (const log of logs) {
if (log.filepath && !seenFiles.has(log.filepath)) {
files.push(readFileSync(log.filepath, 'utf8'));
seenFiles.add(log.filepath);
}
}
return files;
}
}
4 changes: 3 additions & 1 deletion applications/server/src/store/operator-resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export class OperatorResolvers {
@RelationPath() relationPaths: Relation<Operator>
): Promise<Operator[]> {
const em = await connectToProjectEmOrFail(campaignId, ctx);
const operators = await em.find(Operator, !hidden ? { beacons: { hidden } } : {}, { populate: relationPaths });
const operators = await em.find(Operator, !hidden ? { beacons: { hidden } } : {}, {
populate: [...relationPaths, 'beacons'],
});
for (const operator of operators) {
await operator.beacons.init({ populate: false, where: defaultHidden(hidden) });
await operator.commands.init({ populate: false, where: beaconHidden(hidden) });
Expand Down

0 comments on commit 51e1fff

Please sign in to comment.