main
1package mythic
2
3import (
4 "context"
5 "crypto/tls"
6 "encoding/base64"
7 "fmt"
8 "io"
9 "net"
10 "net/http"
11 "net/url"
12 "regexp"
13 "slices"
14 "strings"
15 "unicode/utf8"
16
17 "github.com/machinebox/graphql"
18 "golang.org/x/net/proxy"
19)
20
21type Client struct {
22 client *graphql.Client
23 token string
24 httpClient *http.Client
25 baseURL string
26}
27
28type Callback struct {
29 ID int `json:"id"`
30 DisplayID int `json:"display_id"`
31 User string `json:"user"`
32 Host string `json:"host"`
33 ProcessName string `json:"process_name"`
34 PID int `json:"pid"`
35 Description string `json:"description"`
36 LastCheckin string `json:"last_checkin"`
37 Active bool `json:"active"`
38 Payload struct {
39 PayloadType struct {
40 Name string `json:"name"`
41 } `json:"payloadtype"`
42 } `json:"payload"`
43}
44
45type DetailedCallback struct {
46 // Basic info
47 ID int `json:"id"`
48 DisplayID int `json:"display_id"`
49 User string `json:"user"`
50 Host string `json:"host"`
51 ProcessName string `json:"process_name"`
52 PID int `json:"pid"`
53 Description string `json:"description"`
54 LastCheckin string `json:"last_checkin"`
55 Active bool `json:"active"`
56
57 // Extended system info
58 Architecture string `json:"architecture"`
59 OS string `json:"os"`
60 Domain string `json:"domain"`
61 IntegrityLevel int `json:"integrity_level"`
62 IP string `json:"ip"`
63 ExternalIP string `json:"external_ip"`
64 SleepInfo string `json:"sleep_info"`
65 ExtraInfo string `json:"extra_info"`
66
67 // Payload info
68 RegisteredPayloadID int `json:"registered_payload_id"`
69 AgentCallbackID string `json:"agent_callback_id"`
70 CryptoType string `json:"crypto_type"`
71
72 // Timestamps
73 InitCallback string `json:"init_callback"`
74
75 // Relationships
76 Payload struct {
77 ID int `json:"id"`
78 UUID string `json:"uuid"`
79 Description string `json:"description"`
80 PayloadType struct {
81 Name string `json:"name"`
82 Author string `json:"author"`
83 Note string `json:"note"`
84 } `json:"payloadtype"`
85 } `json:"payload"`
86
87 // Operator info
88 Operator struct {
89 Username string `json:"username"`
90 } `json:"operator"`
91
92 // Commands
93 LoadedCommands []LoadedCommand `json:"loadedcommands"`
94}
95
96type LoadedCommand struct {
97 ID int `json:"id"`
98 Version int `json:"version"`
99 Timestamp string `json:"timestamp"`
100 Command struct {
101 Cmd string `json:"cmd"`
102 Description string `json:"description"`
103 HelpCmd string `json:"help_cmd"`
104 Author string `json:"author"`
105 NeedsAdmin bool `json:"needs_admin"`
106 Attributes interface{} `json:"attributes"`
107 } `json:"command"`
108}
109
110type Task struct {
111 ID int `json:"id"`
112 DisplayID int `json:"display_id"`
113 Command string `json:"command_name"`
114 Params string `json:"params"`
115 OriginalParams string `json:"original_params"`
116 DisplayParams string `json:"display_params"`
117 Status string `json:"status"`
118 Response string `json:"response,omitempty"`
119 CallbackID int `json:"callback_id"`
120 OperatorID int `json:"operator_id"`
121 Timestamp string `json:"timestamp,omitempty"`
122 Completed bool `json:"completed"`
123 AgentTaskID string `json:"agent_task_id"`
124 ParameterGroupName string `json:"parameter_group_name"`
125 TaskingLocation string `json:"tasking_location"`
126 IsInteractiveTask bool `json:"is_interactive_task"`
127 InteractiveTaskType int `json:"interactive_task_type"`
128 ParentTaskID int `json:"parent_task_id"`
129 Operator struct {
130 Username string `json:"username"`
131 } `json:"operator"`
132 Token struct {
133 TokenID int `json:"token_id"`
134 } `json:"token"`
135}
136
137type File struct {
138 ID int `json:"id"`
139 AgentFileID string `json:"agent_file_id"`
140 Filename string `json:"filename_utf8"`
141 FullRemotePath string `json:"full_remote_path_utf8"`
142 Host string `json:"host"`
143 Complete bool `json:"complete"`
144 ChunksReceived int `json:"chunks_received"`
145 TotalChunks int `json:"total_chunks"`
146 Timestamp string `json:"timestamp"`
147 MD5 string `json:"md5"`
148 SHA1 string `json:"sha1"`
149 IsDownloadFromAgent bool `json:"is_download_from_agent"`
150 IsPayload bool `json:"is_payload"`
151 IsScreenshot bool `json:"is_screenshot"`
152 OperatorUsername string `json:"operator_username"`
153 TaskID int `json:"task_id"`
154 TaskDisplayID int `json:"task_display_id"`
155 CallbackID int `json:"callback_id"`
156 CallbackHost string `json:"callback_host"`
157 CallbackUser string `json:"callback_user"`
158}
159
160type Payload struct {
161 ID int `json:"id"`
162 UUID string `json:"uuid"`
163 Description string `json:"description"`
164 BuildPhase string `json:"build_phase"`
165 BuildMessage string `json:"build_message"`
166 BuildStderr string `json:"build_stderr"`
167 CallbackAlert bool `json:"callback_alert"`
168 CreationTime string `json:"creation_time"`
169 AutoGenerated bool `json:"auto_generated"`
170 Deleted bool `json:"deleted"`
171
172 // Nested structs
173 OperatorID int `json:"operator_id"`
174 OperatorUsername string `json:"operator_username"`
175 PayloadTypeID int `json:"payloadtype_id"`
176 PayloadTypeName string `json:"payloadtype_name"`
177
178 // File info
179 FileID int `json:"file_id"`
180 Filename string `json:"filename"`
181 AgentFileID string `json:"agent_file_id"`
182
183 // C2 Profiles
184 C2Profiles []string `json:"c2_profiles"`
185}
186
187func NewClient(apiURL, token string, insecure bool, socksProxy string) *Client {
188 client := graphql.NewClient(apiURL)
189
190 // Create transport with TLS config
191 tr := &http.Transport{
192 TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
193 }
194
195 // Configure SOCKS5 proxy if provided
196 if socksProxy != "" {
197 dialer, err := proxy.SOCKS5("tcp", socksProxy, nil, proxy.Direct)
198 if err == nil {
199 tr.Dial = func(network, addr string) (net.Conn, error) {
200 return dialer.Dial(network, addr)
201 }
202 }
203 }
204
205 httpClient := &http.Client{Transport: tr}
206 client = graphql.NewClient(apiURL, graphql.WithHTTPClient(httpClient))
207
208 // Extract base URL for file downloads (GraphQL URL -> base server URL)
209 parsedURL, _ := url.Parse(apiURL)
210 baseURL := fmt.Sprintf("%s://%s", parsedURL.Scheme, parsedURL.Host)
211
212 return &Client{
213 client: client,
214 token: token,
215 httpClient: httpClient,
216 baseURL: baseURL,
217 }
218}
219
220// isBase64 checks if a string is valid base64 and likely contains binary data
221func isBase64(s string) bool {
222 // Must be at least 4 characters for valid base64
223 if len(s) < 4 {
224 return false
225 }
226
227 // Check if it matches base64 pattern
228 base64Regex := regexp.MustCompile(`^[A-Za-z0-9+/]*={0,2}$`)
229 if !base64Regex.MatchString(s) {
230 return false
231 }
232
233 // Try to decode
234 decoded, err := base64.StdEncoding.DecodeString(s)
235 if err != nil {
236 return false
237 }
238
239 // If decoded content is not valid UTF-8, it's likely binary data
240 if !utf8.Valid(decoded) {
241 return true
242 }
243
244 // If it's valid UTF-8 but the original string looks like base64
245 // and is significantly longer than the decoded version, it's likely base64
246 if float64(len(s)) > float64(len(decoded))*1.2 {
247 return true
248 }
249
250 return false
251}
252
253// decodeResponseText tries to base64 decode response_text, as Mythic typically encodes all responses
254func decodeResponseText(responseText string) string {
255 // First, try to base64 decode - Mythic typically base64 encodes all response_text
256 if decoded, err := base64.StdEncoding.DecodeString(responseText); err == nil {
257 // If decoded is valid UTF-8 text, return it
258 if utf8.Valid(decoded) {
259 return string(decoded)
260 }
261 // If it's binary data, return a placeholder message
262 return fmt.Sprintf("[Binary data: %d bytes]", len(decoded))
263 }
264
265 // If base64 decode fails, return original text
266 return responseText
267}
268
269func (c *Client) GetCallbacks(ctx context.Context) ([]Callback, error) {
270 req := graphql.NewRequest(GetCallbacks)
271
272 req.Header.Set("apitoken", c.token)
273
274 var resp struct {
275 Callback []Callback `json:"callback"`
276 }
277
278 if err := c.client.Run(ctx, req, &resp); err != nil {
279 return nil, fmt.Errorf("failed to get callbacks: %w", err)
280 }
281
282 return resp.Callback, nil
283}
284
285func (c *Client) GetActiveCallbacks(ctx context.Context) ([]Callback, error) {
286 callbacks, err := c.GetCallbacks(ctx)
287 if err != nil {
288 return nil, err
289 }
290
291 var active []Callback
292 for _, callback := range callbacks {
293 if callback.Active {
294 active = append(active, callback)
295 }
296 }
297
298 return active, nil
299}
300
301func (c *Client) GetDetailedCallback(ctx context.Context, callbackID int) (*DetailedCallback, error) {
302 req := graphql.NewRequest(GetDetailedCallback)
303
304 req.Var("callback_id", callbackID)
305 req.Header.Set("apitoken", c.token)
306
307 var resp struct {
308 Callback *DetailedCallback `json:"callback_by_pk"`
309 }
310
311 if err := c.client.Run(ctx, req, &resp); err != nil {
312 return nil, fmt.Errorf("failed to get detailed callback: %w", err)
313 }
314
315 if resp.Callback == nil {
316 return nil, fmt.Errorf("callback %d not found", callbackID)
317 }
318
319 return resp.Callback, nil
320}
321
322func (c *Client) CreateTask(ctx context.Context, callbackID int, command, params string) (*Task, error) {
323 req := graphql.NewRequest(CreateTask)
324
325 req.Var("callback_id", callbackID)
326 req.Var("command", command)
327 req.Var("params", params)
328 req.Var("tasking_location", "command_line")
329 req.Header.Set("apitoken", c.token)
330
331 var resp struct {
332 CreateTask struct {
333 Status string `json:"status"`
334 ID int `json:"id"`
335 DisplayID int `json:"display_id"`
336 Error string `json:"error"`
337 } `json:"createTask"`
338 }
339
340 if err := c.client.Run(ctx, req, &resp); err != nil {
341 return nil, fmt.Errorf("failed to create task: %w", err)
342 }
343
344 if resp.CreateTask.Status != "success" {
345 return nil, fmt.Errorf("task creation failed: %s", resp.CreateTask.Error)
346 }
347
348 return &Task{
349 ID: resp.CreateTask.ID,
350 DisplayID: resp.CreateTask.DisplayID,
351 Command: command,
352 Params: params,
353 Status: "submitted",
354 CallbackID: callbackID,
355 }, nil
356}
357
358func (c *Client) GetTaskResponse(ctx context.Context, taskID int) (*Task, error) {
359 req := graphql.NewRequest(GetTask)
360
361 req.Var("id", taskID)
362 req.Header.Set("apitoken", c.token)
363
364 var resp struct {
365 Task struct {
366 ID int `json:"id"`
367 DisplayID int `json:"display_id"`
368 CommandName string `json:"command_name"`
369 Params string `json:"params"`
370 OriginalParams string `json:"original_params"`
371 DisplayParams string `json:"display_params"`
372 Status string `json:"status"`
373 Completed bool `json:"completed"`
374 Timestamp string `json:"timestamp"`
375 AgentTaskID string `json:"agent_task_id"`
376 ParameterGroupName string `json:"parameter_group_name"`
377 TaskingLocation string `json:"tasking_location"`
378 IsInteractiveTask bool `json:"is_interactive_task"`
379 InteractiveTaskType int `json:"interactive_task_type"`
380 ParentTaskID int `json:"parent_task_id"`
381 Operator struct {
382 Username string `json:"username"`
383 } `json:"operator"`
384 Token struct {
385 TokenID int `json:"token_id"`
386 } `json:"token"`
387 Callback struct {
388 ID int `json:"id"`
389 DisplayID int `json:"display_id"`
390 } `json:"callback"`
391 } `json:"task_by_pk"`
392 }
393
394 if err := c.client.Run(ctx, req, &resp); err != nil {
395 return nil, fmt.Errorf("failed to get task: %w", err)
396 }
397
398
399
400 // Get task responses
401 responseReq := graphql.NewRequest(GetTaskResponses)
402
403 responseReq.Var("task_display_id", resp.Task.DisplayID)
404 responseReq.Header.Set("apitoken", c.token)
405
406 var responseResp struct {
407 Response []struct {
408 ResponseText string `json:"response_text"`
409 Timestamp string `json:"timestamp"`
410 } `json:"response"`
411 }
412
413 if err := c.client.Run(ctx, responseReq, &responseResp); err != nil {
414 return nil, fmt.Errorf("failed to get task response: %w", err)
415 }
416
417 // Concatenate all response chunks in chronological order
418 var responseBuilder strings.Builder
419 for _, resp := range responseResp.Response {
420 decodedText := decodeResponseText(resp.ResponseText)
421 responseBuilder.WriteString(decodedText)
422 }
423 response := responseBuilder.String()
424
425 status := resp.Task.Status
426 if resp.Task.Completed {
427 status = "completed"
428 }
429
430 return &Task{
431 ID: resp.Task.ID,
432 DisplayID: resp.Task.DisplayID,
433 Command: resp.Task.CommandName,
434 Params: resp.Task.Params,
435 OriginalParams: resp.Task.OriginalParams,
436 DisplayParams: resp.Task.DisplayParams,
437 Status: status,
438 Response: response,
439 CallbackID: resp.Task.Callback.ID,
440 Completed: resp.Task.Completed,
441 Timestamp: resp.Task.Timestamp,
442 AgentTaskID: resp.Task.AgentTaskID,
443 ParameterGroupName: resp.Task.ParameterGroupName,
444 TaskingLocation: resp.Task.TaskingLocation,
445 IsInteractiveTask: resp.Task.IsInteractiveTask,
446 InteractiveTaskType: resp.Task.InteractiveTaskType,
447 ParentTaskID: resp.Task.ParentTaskID,
448 Operator: struct {
449 Username string `json:"username"`
450 }{
451 Username: resp.Task.Operator.Username,
452 },
453 Token: struct {
454 TokenID int `json:"token_id"`
455 }{
456 TokenID: resp.Task.Token.TokenID,
457 },
458 }, nil
459}
460
461// GetTasksWithResponsesByIDs efficiently fetches multiple tasks with their responses in a single query
462func (c *Client) GetTasksWithResponsesByIDs(ctx context.Context, taskIDs []int) ([]Task, error) {
463 if len(taskIDs) == 0 {
464 return []Task{}, nil
465 }
466
467 req := graphql.NewRequest(GetTasksWithResponsesByIDs)
468 req.Var("task_ids", taskIDs)
469 req.Header.Set("apitoken", c.token)
470
471 var resp struct {
472 Task []struct {
473 ID int `json:"id"`
474 DisplayID int `json:"display_id"`
475 CommandName string `json:"command_name"`
476 OriginalParams string `json:"original_params"`
477 DisplayParams string `json:"display_params"`
478 Status string `json:"status"`
479 Completed bool `json:"completed"`
480 Timestamp string `json:"timestamp"`
481 Callback struct {
482 ID int `json:"id"`
483 DisplayID int `json:"display_id"`
484 Host string `json:"host"`
485 User string `json:"user"`
486 } `json:"callback"`
487 Responses []struct {
488 ResponseText string `json:"response_text"`
489 Timestamp string `json:"timestamp"`
490 } `json:"responses"`
491 } `json:"task"`
492 }
493
494 if err := c.client.Run(ctx, req, &resp); err != nil {
495 return nil, fmt.Errorf("failed to get tasks with responses: %w", err)
496 }
497
498 tasks := make([]Task, 0, len(resp.Task))
499 for _, task := range resp.Task {
500 // Concatenate all response chunks in chronological order
501 var responseBuilder strings.Builder
502 for _, respChunk := range task.Responses {
503 decodedText := decodeResponseText(respChunk.ResponseText)
504 responseBuilder.WriteString(decodedText)
505 }
506 response := responseBuilder.String()
507
508 status := task.Status
509 if task.Completed {
510 status = "completed"
511 }
512
513 tasks = append(tasks, Task{
514 ID: task.ID,
515 DisplayID: task.DisplayID,
516 Command: task.CommandName,
517 Params: task.DisplayParams,
518 Status: status,
519 Response: response,
520 CallbackID: task.Callback.ID,
521 Completed: task.Completed,
522 Timestamp: task.Timestamp,
523 })
524 }
525
526 return tasks, nil
527}
528
529func (c *Client) GetCallbackTasks(ctx context.Context, callbackID int) ([]Task, error) {
530 req := graphql.NewRequest(GetCallbackTasks)
531
532 req.Var("callback_id", callbackID)
533 req.Header.Set("apitoken", c.token)
534
535 var resp struct {
536 Task []struct {
537 ID int `json:"id"`
538 DisplayID int `json:"display_id"`
539 CommandName string `json:"command_name"`
540 OriginalParams string `json:"original_params"`
541 DisplayParams string `json:"display_params"`
542 Status string `json:"status"`
543 Completed bool `json:"completed"`
544 Timestamp string `json:"timestamp"`
545 Callback struct {
546 ID int `json:"id"`
547 DisplayID int `json:"display_id"`
548 } `json:"callback"`
549 } `json:"task"`
550 }
551
552 if err := c.client.Run(ctx, req, &resp); err != nil {
553 return nil, fmt.Errorf("failed to get callback tasks: %w", err)
554 }
555
556 var tasks []Task
557 for _, t := range resp.Task {
558 status := t.Status
559 if t.Completed {
560 status = "completed"
561 }
562
563 tasks = append(tasks, Task{
564 ID: t.ID,
565 DisplayID: t.DisplayID,
566 Command: t.CommandName,
567 Params: t.DisplayParams,
568 Status: status,
569 CallbackID: t.Callback.ID,
570 Timestamp: t.Timestamp,
571 Completed: t.Completed,
572 })
573 }
574
575 return tasks, nil
576}
577
578func (c *Client) GetDownloadedFiles(ctx context.Context) ([]File, error) {
579 req := graphql.NewRequest(GetDownloadedFiles)
580
581 req.Header.Set("apitoken", c.token)
582
583 var resp struct {
584 Filemeta []struct {
585 ID int `json:"id"`
586 AgentFileID string `json:"agent_file_id"`
587 Filename string `json:"filename_utf8"`
588 FullRemotePath string `json:"full_remote_path_utf8"`
589 Host string `json:"host"`
590 Complete bool `json:"complete"`
591 ChunksReceived int `json:"chunks_received"`
592 TotalChunks int `json:"total_chunks"`
593 Timestamp string `json:"timestamp"`
594 MD5 string `json:"md5"`
595 SHA1 string `json:"sha1"`
596 Operator struct {
597 Username string `json:"username"`
598 } `json:"operator"`
599 Task struct {
600 ID int `json:"id"`
601 DisplayID int `json:"display_id"`
602 Callback struct {
603 ID int `json:"id"`
604 DisplayID int `json:"display_id"`
605 Host string `json:"host"`
606 User string `json:"user"`
607 } `json:"callback"`
608 } `json:"task"`
609 } `json:"filemeta"`
610 }
611
612 if err := c.client.Run(ctx, req, &resp); err != nil {
613 return nil, fmt.Errorf("failed to get downloaded files: %w", err)
614 }
615
616 var files []File
617 for _, f := range resp.Filemeta {
618 files = append(files, File{
619 ID: f.ID,
620 AgentFileID: f.AgentFileID,
621 Filename: f.Filename,
622 FullRemotePath: f.FullRemotePath,
623 Host: f.Host,
624 Complete: f.Complete,
625 ChunksReceived: f.ChunksReceived,
626 TotalChunks: f.TotalChunks,
627 Timestamp: f.Timestamp,
628 MD5: f.MD5,
629 SHA1: f.SHA1,
630 OperatorUsername: f.Operator.Username,
631 TaskID: f.Task.ID,
632 TaskDisplayID: f.Task.DisplayID,
633 CallbackID: f.Task.Callback.ID,
634 CallbackHost: f.Task.Callback.Host,
635 CallbackUser: f.Task.Callback.User,
636 })
637 }
638
639 return files, nil
640}
641
642func (c *Client) GetAllFiles(ctx context.Context) ([]File, error) {
643 req := graphql.NewRequest(GetAllFiles)
644
645 req.Header.Set("apitoken", c.token)
646
647 var resp struct {
648 Filemeta []struct {
649 ID int `json:"id"`
650 AgentFileID string `json:"agent_file_id"`
651 Filename string `json:"filename_utf8"`
652 FullRemotePath string `json:"full_remote_path_utf8"`
653 Host string `json:"host"`
654 Complete bool `json:"complete"`
655 ChunksReceived int `json:"chunks_received"`
656 TotalChunks int `json:"total_chunks"`
657 Timestamp string `json:"timestamp"`
658 MD5 string `json:"md5"`
659 SHA1 string `json:"sha1"`
660 IsDownloadFromAgent bool `json:"is_download_from_agent"`
661 IsPayload bool `json:"is_payload"`
662 IsScreenshot bool `json:"is_screenshot"`
663 Operator struct {
664 Username string `json:"username"`
665 } `json:"operator"`
666 Task struct {
667 ID int `json:"id"`
668 DisplayID int `json:"display_id"`
669 Callback struct {
670 ID int `json:"id"`
671 DisplayID int `json:"display_id"`
672 Host string `json:"host"`
673 User string `json:"user"`
674 } `json:"callback"`
675 } `json:"task"`
676 } `json:"filemeta"`
677 }
678
679 if err := c.client.Run(ctx, req, &resp); err != nil {
680 return nil, fmt.Errorf("failed to get all files: %w", err)
681 }
682
683 var files []File
684 for _, f := range resp.Filemeta {
685 files = append(files, File{
686 ID: f.ID,
687 AgentFileID: f.AgentFileID,
688 Filename: f.Filename,
689 FullRemotePath: f.FullRemotePath,
690 Host: f.Host,
691 Complete: f.Complete,
692 ChunksReceived: f.ChunksReceived,
693 TotalChunks: f.TotalChunks,
694 Timestamp: f.Timestamp,
695 MD5: f.MD5,
696 SHA1: f.SHA1,
697 IsDownloadFromAgent: f.IsDownloadFromAgent,
698 IsPayload: f.IsPayload,
699 IsScreenshot: f.IsScreenshot,
700 OperatorUsername: f.Operator.Username,
701 TaskID: f.Task.ID,
702 TaskDisplayID: f.Task.DisplayID,
703 CallbackID: f.Task.Callback.ID,
704 CallbackHost: f.Task.Callback.Host,
705 CallbackUser: f.Task.Callback.User,
706 })
707 }
708
709 return files, nil
710}
711
712func (c *Client) DownloadFile(ctx context.Context, fileUUID string) ([]byte, error) {
713 downloadURL := fmt.Sprintf("%s/direct/download/%s", c.baseURL, fileUUID)
714
715 req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil)
716 if err != nil {
717 return nil, fmt.Errorf("failed to create download request: %w", err)
718 }
719
720 req.Header.Set("apitoken", c.token)
721
722 resp, err := c.httpClient.Do(req)
723 if err != nil {
724 return nil, fmt.Errorf("failed to download file: %w", err)
725 }
726 defer resp.Body.Close()
727
728 if resp.StatusCode != http.StatusOK {
729 return nil, fmt.Errorf("download failed with status %d: %s", resp.StatusCode, resp.Status)
730 }
731
732 data, err := io.ReadAll(resp.Body)
733 if err != nil {
734 return nil, fmt.Errorf("failed to read file data: %w", err)
735 }
736
737 return data, nil
738}
739
740func (c *Client) GetTasksWithResponses(ctx context.Context, callbackID int, limit int) ([]Task, error) {
741 req := graphql.NewRequest(GetTasksWithResponses)
742
743 if callbackID > 0 {
744 req.Var("callback_id", callbackID)
745 }
746 if limit > 0 {
747 req.Var("limit", limit)
748 }
749 req.Header.Set("apitoken", c.token)
750
751 var resp struct {
752 Task []struct {
753 ID int `json:"id"`
754 DisplayID int `json:"display_id"`
755 CommandName string `json:"command_name"`
756 OriginalParams string `json:"original_params"`
757 DisplayParams string `json:"display_params"`
758 Status string `json:"status"`
759 Completed bool `json:"completed"`
760 Timestamp string `json:"timestamp"`
761 Callback struct {
762 ID int `json:"id"`
763 DisplayID int `json:"display_id"`
764 Host string `json:"host"`
765 User string `json:"user"`
766 } `json:"callback"`
767 Responses []struct {
768 ResponseText string `json:"response_text"`
769 Timestamp string `json:"timestamp"`
770 } `json:"responses"`
771 } `json:"task"`
772 }
773
774 if err := c.client.Run(ctx, req, &resp); err != nil {
775 return nil, fmt.Errorf("failed to get tasks with responses: %w", err)
776 }
777
778 var tasks []Task
779 for _, t := range resp.Task {
780 // Concatenate all response chunks in chronological order
781 var responseBuilder strings.Builder
782 for _, resp := range t.Responses {
783 decodedText := decodeResponseText(resp.ResponseText)
784 responseBuilder.WriteString(decodedText)
785 }
786 response := responseBuilder.String()
787
788 status := t.Status
789 if t.Completed {
790 status = "completed"
791 }
792
793 tasks = append(tasks, Task{
794 ID: t.ID,
795 DisplayID: t.DisplayID,
796 Command: t.CommandName,
797 Params: t.DisplayParams,
798 Status: status,
799 Response: response,
800 CallbackID: t.Callback.ID,
801 Timestamp: t.Timestamp,
802 Completed: t.Completed,
803 })
804 }
805
806 return tasks, nil
807}
808
809func (c *Client) GetAllTasks(ctx context.Context, limit int) ([]Task, error) {
810 req := graphql.NewRequest(GetAllTasks)
811
812 if limit > 0 {
813 req.Var("limit", limit)
814 }
815 req.Header.Set("apitoken", c.token)
816
817 var resp struct {
818 Task []struct {
819 ID int `json:"id"`
820 DisplayID int `json:"display_id"`
821 CommandName string `json:"command_name"`
822 OriginalParams string `json:"original_params"`
823 DisplayParams string `json:"display_params"`
824 Status string `json:"status"`
825 Completed bool `json:"completed"`
826 Timestamp string `json:"timestamp"`
827 Callback struct {
828 ID int `json:"id"`
829 DisplayID int `json:"display_id"`
830 Host string `json:"host"`
831 User string `json:"user"`
832 } `json:"callback"`
833 } `json:"task"`
834 }
835
836 if err := c.client.Run(ctx, req, &resp); err != nil {
837 return nil, fmt.Errorf("failed to get all tasks: %w", err)
838 }
839
840 var tasks []Task
841 for _, t := range resp.Task {
842 status := t.Status
843 if t.Completed {
844 status = "completed"
845 }
846
847 tasks = append(tasks, Task{
848 ID: t.ID,
849 DisplayID: t.DisplayID,
850 Command: t.CommandName,
851 Params: t.DisplayParams,
852 Status: status,
853 CallbackID: t.Callback.ID,
854 Timestamp: t.Timestamp,
855 Completed: t.Completed,
856 })
857 }
858
859 // Reverse to show newest (highest ID) last
860 slices.Reverse(tasks)
861
862 return tasks, nil
863}
864
865func (c *Client) GetAllTasksWithResponses(ctx context.Context, limit int) ([]Task, error) {
866 req := graphql.NewRequest(GetAllTasksWithResponsesFromAllCallbacks)
867
868 if limit > 0 {
869 req.Var("limit", limit)
870 }
871 req.Header.Set("apitoken", c.token)
872
873 var resp struct {
874 Task []struct {
875 ID int `json:"id"`
876 DisplayID int `json:"display_id"`
877 CommandName string `json:"command_name"`
878 OriginalParams string `json:"original_params"`
879 DisplayParams string `json:"display_params"`
880 Status string `json:"status"`
881 Completed bool `json:"completed"`
882 Timestamp string `json:"timestamp"`
883 Callback struct {
884 ID int `json:"id"`
885 DisplayID int `json:"display_id"`
886 Host string `json:"host"`
887 User string `json:"user"`
888 } `json:"callback"`
889 Responses []struct {
890 ResponseText string `json:"response_text"`
891 Timestamp string `json:"timestamp"`
892 } `json:"responses"`
893 } `json:"task"`
894 }
895
896 if err := c.client.Run(ctx, req, &resp); err != nil {
897 return nil, fmt.Errorf("failed to get all tasks with responses: %w", err)
898 }
899
900 var tasks []Task
901 for _, t := range resp.Task {
902 // Concatenate all response chunks in chronological order
903 var responseBuilder strings.Builder
904 for _, resp := range t.Responses {
905 decodedText := decodeResponseText(resp.ResponseText)
906 responseBuilder.WriteString(decodedText)
907 }
908 response := responseBuilder.String()
909
910 status := t.Status
911 if t.Completed {
912 status = "completed"
913 }
914
915 tasks = append(tasks, Task{
916 ID: t.ID,
917 DisplayID: t.DisplayID,
918 Command: t.CommandName,
919 Params: t.DisplayParams,
920 Status: status,
921 Response: response,
922 CallbackID: t.Callback.ID,
923 Timestamp: t.Timestamp,
924 Completed: t.Completed,
925 })
926 }
927
928 return tasks, nil
929}
930
931func (c *Client) GetPayloads(ctx context.Context) ([]Payload, error) {
932 req := graphql.NewRequest(GetPayloads)
933
934 req.Header.Set("apitoken", c.token)
935
936 var resp struct {
937 Payload []struct {
938 ID int `json:"id"`
939 UUID string `json:"uuid"`
940 Description string `json:"description"`
941 BuildPhase string `json:"build_phase"`
942 BuildMessage string `json:"build_message"`
943 BuildStderr string `json:"build_stderr"`
944 CallbackAlert bool `json:"callback_alert"`
945 CreationTime string `json:"creation_time"`
946 AutoGenerated bool `json:"auto_generated"`
947 Deleted bool `json:"deleted"`
948 Operator struct {
949 ID int `json:"id"`
950 Username string `json:"username"`
951 } `json:"operator"`
952 PayloadType struct {
953 ID int `json:"id"`
954 Name string `json:"name"`
955 } `json:"payloadtype"`
956 Filemetum struct {
957 ID int `json:"id"`
958 AgentFileID string `json:"agent_file_id"`
959 Filename string `json:"filename_utf8"`
960 } `json:"filemetum"`
961 PayloadC2Profiles []struct {
962 C2Profile struct {
963 Name string `json:"name"`
964 Running bool `json:"running"`
965 IsP2P bool `json:"is_p2p"`
966 ContainerRunning bool `json:"container_running"`
967 } `json:"c2profile"`
968 } `json:"payloadc2profiles"`
969 } `json:"payload"`
970 }
971
972 if err := c.client.Run(ctx, req, &resp); err != nil {
973 return nil, fmt.Errorf("failed to get payloads: %w", err)
974 }
975
976 var payloads []Payload
977 for _, p := range resp.Payload {
978 // Collect C2 profile names
979 var c2Profiles []string
980 for _, profile := range p.PayloadC2Profiles {
981 c2Profiles = append(c2Profiles, profile.C2Profile.Name)
982 }
983
984 payloads = append(payloads, Payload{
985 ID: p.ID,
986 UUID: p.UUID,
987 Description: p.Description,
988 BuildPhase: p.BuildPhase,
989 BuildMessage: p.BuildMessage,
990 BuildStderr: p.BuildStderr,
991 CallbackAlert: p.CallbackAlert,
992 CreationTime: p.CreationTime,
993 AutoGenerated: p.AutoGenerated,
994 Deleted: p.Deleted,
995 OperatorID: p.Operator.ID,
996 OperatorUsername: p.Operator.Username,
997 PayloadTypeID: p.PayloadType.ID,
998 PayloadTypeName: p.PayloadType.Name,
999 FileID: p.Filemetum.ID,
1000 Filename: p.Filemetum.Filename,
1001 AgentFileID: p.Filemetum.AgentFileID,
1002 C2Profiles: c2Profiles,
1003 })
1004 }
1005
1006 return payloads, nil
1007}