diff --git a/AdaptixServer/core/connector/connector.go b/AdaptixServer/core/connector/connector.go index 2f8d613d..b219bdf4 100644 --- a/AdaptixServer/core/connector/connector.go +++ b/AdaptixServer/core/connector/connector.go @@ -36,7 +36,9 @@ type Teamserver interface { TsDownloadClose(fileId string, reason int) error TsDownloadSync(fileId string) (string, []byte, error) TsDownloadDelete(fileId string) error + TsDownloadChangeState(fileId string, username string, command string) error + TsAgentBrowserDisks(agentId string, username string) error } type TsConnector struct { @@ -92,6 +94,7 @@ func NewTsConnector(ts Teamserver, p profile.TsProfile) (*TsConnector, error) { connector.Engine.POST(p.Endpoint+"/agent/settag", token.ValidateAccessToken(), default404Middleware(), connector.TcAgentSetTag) connector.Engine.POST(p.Endpoint+"/browser/download", token.ValidateAccessToken(), default404Middleware(), connector.TcBrowserDownload) + connector.Engine.POST(p.Endpoint+"/browser/disks", token.ValidateAccessToken(), default404Middleware(), connector.TcBrowserDisks) connector.Engine.NoRoute(default404Middleware(), func(c *gin.Context) { _ = c.Error(errors.New("NoRoute")) }) diff --git a/AdaptixServer/core/connector/tc_browsers.go b/AdaptixServer/core/connector/tc_browsers.go index 96f8aa2f..4624651a 100644 --- a/AdaptixServer/core/connector/tc_browsers.go +++ b/AdaptixServer/core/connector/tc_browsers.go @@ -76,3 +76,45 @@ func (tc *TsConnector) TcBrowserDownload(ctx *gin.Context) { ctx.JSON(http.StatusOK, answer) } + +/// + +type DisksAction struct { + AgentId string `json:"agent_id"` +} + +func (tc *TsConnector) TcBrowserDisks(ctx *gin.Context) { + var ( + disksAction DisksAction + username string + ok bool + err error + answer gin.H + ) + + err = ctx.ShouldBindJSON(&disksAction) + if err != nil { + _ = ctx.Error(errors.New("invalid action")) + return + } + + value, exists := ctx.Get("username") + if !exists { + ctx.JSON(http.StatusOK, gin.H{"message": "Server error: username not found in context", "ok": false}) + return + } + + username, ok = value.(string) + if !ok { + ctx.JSON(http.StatusOK, gin.H{"message": "Server error: invalid username type in context", "ok": false}) + return + } + + err = tc.teamserver.TsAgentBrowserDisks(disksAction.AgentId, username) + if err != nil { + answer = gin.H{"message": err.Error(), "ok": false} + } else { + answer = gin.H{"message": "Wait...", "ok": true} + } + ctx.JSON(http.StatusOK, answer) +} diff --git a/AdaptixServer/core/extender/ex_agent.go b/AdaptixServer/core/extender/ex_agent.go index 570418d9..6e497a8c 100644 --- a/AdaptixServer/core/extender/ex_agent.go +++ b/AdaptixServer/core/extender/ex_agent.go @@ -61,3 +61,15 @@ func (ex *AdaptixExtender) ExAgentDownloadChangeState(agentName string, agentObj return nil, errors.New("module not found") } } + +func (ex *AdaptixExtender) ExAgentBrowserDisks(agentName string, agentObject []byte) ([]byte, error) { + var module *ModuleExtender + + value, ok := ex.agentModules.Get(agentName) + if ok { + module = value.(*ModuleExtender) + return module.AgentBrowserDisks(agentObject) + } else { + return nil, errors.New("module not found") + } +} diff --git a/AdaptixServer/core/extender/extender.go b/AdaptixServer/core/extender/extender.go index 30826779..8b84ebc5 100644 --- a/AdaptixServer/core/extender/extender.go +++ b/AdaptixServer/core/extender/extender.go @@ -141,6 +141,10 @@ func (ex *AdaptixExtender) ValidPlugin(info ModuleInfo, object plugin.Symbol) er if !ok { return errors.New("method AgentDownloadChangeState not found") } + _, ok = reflect.TypeOf(object).MethodByName("AgentBrowserDisks") + if !ok { + return errors.New("method AgentBrowserDisks not found") + } return nil } diff --git a/AdaptixServer/core/extender/utils.go b/AdaptixServer/core/extender/utils.go index 5cd0c9a0..02d9992b 100644 --- a/AdaptixServer/core/extender/utils.go +++ b/AdaptixServer/core/extender/utils.go @@ -49,7 +49,9 @@ type AgentFunctions interface { AgentProcessData(agentObject []byte, packedData []byte) ([]byte, error) AgentPackData(agentObject []byte, dataTasks [][]byte) ([]byte, error) AgentCommand(agentObject []byte, args map[string]any) ([]byte, string, error) + AgentDownloadChangeState(agentObject []byte, newState int, fileId string) ([]byte, error) + AgentBrowserDisks(agentObject []byte) ([]byte, error) } type ModuleExtender struct { diff --git a/AdaptixServer/core/server/ts_browser.go b/AdaptixServer/core/server/ts_browser.go new file mode 100644 index 00000000..9bd1f822 --- /dev/null +++ b/AdaptixServer/core/server/ts_browser.go @@ -0,0 +1,97 @@ +package server + +import ( + "AdaptixServer/core/utils/krypt" + "bytes" + "encoding/json" + "fmt" + "time" +) + +/// AGENT + +func (ts *Teamserver) TsAgentBrowserDisks(agentId string, username string) error { + var ( + err error + agentObject bytes.Buffer + agent *Agent + taskData TaskData + data []byte + ) + + value, ok := ts.agents.Get(agentId) + if ok { + + agent, _ = value.(*Agent) + _ = json.NewEncoder(&agentObject).Encode(agent.Data) + + data, err = ts.Extender.ExAgentBrowserDisks(agent.Data.Name, agentObject.Bytes()) + if err != nil { + return err + } + + err = json.Unmarshal(data, &taskData) + if err != nil { + return err + } + + if taskData.TaskId == "" { + taskData.TaskId, _ = krypt.GenerateUID(8) + } + taskData.AgentId = agentId + taskData.User = username + taskData.StartDate = time.Now().Unix() + + agent.TasksQueue.Put(taskData) + + } else { + return fmt.Errorf("agent '%v' does not exist", agentId) + } + + return nil +} + +/// SYNC + +func (ts *Teamserver) TsClientBrowserDisks(jsonTask string, jsonDrives string) { + var ( + agent *Agent + task TaskData + taskData TaskData + value any + ok bool + err error + ) + + err = json.Unmarshal([]byte(jsonTask), &taskData) + if err != nil { + return + } + + value, ok = ts.agents.Get(taskData.AgentId) + if ok { + agent = value.(*Agent) + } else { + return + } + + value, ok = agent.Tasks.Get(taskData.TaskId) + if ok { + task = value.(TaskData) + } else { + return + } + + if task.Type != TYPE_BROWSER { + return + } + + agent.Tasks.Delete(taskData.TaskId) + + if taskData.MessageType != CONSOLE_OUT_ERROR && taskData.MessageType != CONSOLE_OUT_LOCAL_ERROR { + taskData.Message = "Status: OK" + } + + packet := CreateSpBrowserDisks(taskData, jsonDrives) + ts.TsSyncClient(task.User, packet) +} diff --git a/AdaptixServer/core/server/ts_syncpacket.go b/AdaptixServer/core/server/ts_syncpacket.go index ac88f3d8..78034307 100644 --- a/AdaptixServer/core/server/ts_syncpacket.go +++ b/AdaptixServer/core/server/ts_syncpacket.go @@ -32,6 +32,8 @@ const ( TYPE_DOWNLOAD_CREATE = 0x51 TYPE_DOWNLOAD_UPDATE = 0x52 TYPE_DOWNLOAD_DELETE = 0x53 + + TYPE_BROWSER_DISKS = 0x61 ) /// SYNC @@ -274,3 +276,18 @@ func CreateSpDownloadDelete(fileId string) SyncPackerDownloadDelete { FileId: fileId, } } + +/// BROWSER + +func CreateSpBrowserDisks(taskData TaskData, data string) SyncPacketBrowserDisks { + return SyncPacketBrowserDisks{ + store: STORE_LOG, + SpType: TYPE_BROWSER_DISKS, + + AgentId: taskData.AgentId, + Time: time.Now().UTC().Unix(), + MessageType: taskData.MessageType, + Message: taskData.Message, + Data: data, + } +} diff --git a/AdaptixServer/core/server/ts_tasks.go b/AdaptixServer/core/server/ts_tasks.go index b9d19e59..0b36d614 100644 --- a/AdaptixServer/core/server/ts_tasks.go +++ b/AdaptixServer/core/server/ts_tasks.go @@ -93,19 +93,18 @@ func (ts *Teamserver) TsTaskUpdate(agentId string, taskObject []byte) { } value, ok = agent.Tasks.GetDelete(taskData.TaskId) - if ok { - task = value.(TaskData) - task.Data = []byte("") - task.FinishDate = taskData.FinishDate - task.MessageType = taskData.MessageType - task.Message = taskData.Message - task.ClearText = taskData.ClearText - task.Completed = taskData.Completed - } else { - task = taskData - logs.Error("TsTaskUpdate: task %v not found", taskData.TaskId) + if !ok { + return } + task = value.(TaskData) + task.Data = []byte("") + task.FinishDate = taskData.FinishDate + task.MessageType = taskData.MessageType + task.Message = taskData.Message + task.ClearText = taskData.ClearText + task.Completed = taskData.Completed + if task.Completed { agent.ClosedTasks.Put(task.TaskId, task) } else { diff --git a/AdaptixServer/core/server/utils.go b/AdaptixServer/core/server/utils.go index c010a1c0..a631e158 100644 --- a/AdaptixServer/core/server/utils.go +++ b/AdaptixServer/core/server/utils.go @@ -125,24 +125,24 @@ type DownloadData struct { } type ListingFileData struct { - IsDir bool `json:"l_is_dir"` - Size int64 `json:"l_size"` - Date int64 `json:"l_date"` - Filename string `json:"l_filename"` + IsDir bool `json:"b_is_dir"` + Size int64 `json:"b_size"` + Date int64 `json:"b_date"` + Filename string `json:"b_filename"` } type ListingProcessData struct { - Pid uint `json:"l_pid"` - Ppid uint `json:"l_ppid"` - SessionId uint `json:"l_session_id"` - Arch string `json:"l_arch"` - Context string `json:"l_context"` - ProcessName string `json:"l_process_name"` + Pid uint `json:"b_pid"` + Ppid uint `json:"b_ppid"` + SessionId uint `json:"b_session_id"` + Arch string `json:"b_arch"` + Context string `json:"b_context"` + ProcessName string `json:"b_process_name"` } type ListingDrivesData struct { - Name string `json:"l_name"` - Type string `json:"l_type"` + Name string `json:"b_name"` + Type string `json:"b_type"` } // SyncPacket @@ -341,3 +341,16 @@ type SyncPackerDownloadDelete struct { FileId string `json:"d_file_id"` } + +/// BROWSER + +type SyncPacketBrowserDisks struct { + store string + SpType int `json:"type"` + + AgentId string `json:"b_agent_id"` + Time int64 `json:"b_time"` + MessageType int `json:"b_msg_type"` + Message string `json:"b_message"` + Data string `json:"b_data"` +} diff --git a/AdaptixServer/extenders/agent_beacon/pl_agent.go b/AdaptixServer/extenders/agent_beacon/pl_agent.go index 1ff47737..8eb62d42 100644 --- a/AdaptixServer/extenders/agent_beacon/pl_agent.go +++ b/AdaptixServer/extenders/agent_beacon/pl_agent.go @@ -414,6 +414,7 @@ func ProcessTasksResult(ts Teamserver, agentData AgentData, taskData TaskData, p case COMMAND_DISKS: result := packer.ParseInt8() + var drives []ListingDrivesData if result == 0 { errorCode := packer.ParseInt32() @@ -421,10 +422,7 @@ func ProcessTasksResult(ts Teamserver, agentData AgentData, taskData TaskData, p task.MessageType = MESSAGE_ERROR } else { - drivesCount := int(packer.ParseInt32()) - var drives []ListingDrivesData - for i := 0; i < drivesCount; i++ { var driveData ListingDrivesData driveCode := packer.ParseInt8() @@ -454,6 +452,9 @@ func ProcessTasksResult(ts Teamserver, agentData AgentData, taskData TaskData, p task.Message = "List of mounted drives:" task.ClearText = OutputText } + + SyncBrowserDisks(ts, task, drives) + break case COMMAND_DOWNLOAD: @@ -693,6 +694,8 @@ func ProcessTasksResult(ts Teamserver, agentData AgentData, taskData TaskData, p } } +/// BROWSERS + func BrowserDownloadChangeState(fid string, newState int) ([]byte, error) { fileId, err := strconv.ParseInt(fid, 16, 64) if err != nil { @@ -703,3 +706,8 @@ func BrowserDownloadChangeState(fid string, newState int) ([]byte, error) { return PackArray(array) } + +func BrowserDisks() ([]byte, error) { + array := []interface{}{COMMAND_DISKS} + return PackArray(array) +} diff --git a/AdaptixServer/extenders/agent_beacon/pl_main.go b/AdaptixServer/extenders/agent_beacon/pl_main.go index 37ed12dc..8cccc78b 100644 --- a/AdaptixServer/extenders/agent_beacon/pl_main.go +++ b/AdaptixServer/extenders/agent_beacon/pl_main.go @@ -45,6 +45,8 @@ type Teamserver interface { TsDownloadAdd(agentId string, fileId string, fileName string, fileSize int) error TsDownloadUpdate(fileId string, state int, data []byte) error TsDownloadClose(fileId string, reason int) error + + TsClientBrowserDisks(jsonTask string, jsonDrives string) } type ModuleExtender struct { @@ -108,24 +110,24 @@ type TaskData struct { } type ListingFileData struct { - IsDir bool `json:"l_is_dir"` - Size uint64 `json:"l_size"` - Date uint64 `json:"l_date"` - Filename string `json:"l_filename"` + IsDir bool `json:"b_is_dir"` + Size uint64 `json:"b_size"` + Date uint64 `json:"b_date"` + Filename string `json:"b_filename"` } type ListingProcessData struct { - Pid uint `json:"l_pid"` - Ppid uint `json:"l_ppid"` - SessionId uint `json:"l_session_id"` - Arch string `json:"l_arch"` - Context string `json:"l_context"` - ProcessName string `json:"l_process_name"` + Pid uint `json:"b_pid"` + Ppid uint `json:"b_ppid"` + SessionId uint `json:"b_session_id"` + Arch string `json:"b_arch"` + Context string `json:"b_context"` + ProcessName string `json:"b_process_name"` } type ListingDrivesData struct { - Name string `json:"l_name"` - Type string `json:"l_type"` + Name string `json:"b_name"` + Type string `json:"b_type"` } var ModuleObject ModuleExtender @@ -312,7 +314,7 @@ func (m *ModuleExtender) AgentDownloadChangeState(agentObject []byte, newState i } taskData = TaskData{ - Type: JOB, + Type: BROWSER, Data: packData, Sync: false, } @@ -324,3 +326,60 @@ func (m *ModuleExtender) AgentDownloadChangeState(agentObject []byte, newState i return buffer.Bytes(), nil } + +func (m *ModuleExtender) AgentBrowserDisks(agentObject []byte) ([]byte, error) { + var ( + packData []byte + agentData AgentData + taskData TaskData + buffer bytes.Buffer + err error + ) + err = json.Unmarshal(agentObject, &agentData) + if err != nil { + return nil, err + } + + packData, err = BrowserDisks() + if err != nil { + return nil, err + } + + taskData = TaskData{ + Type: BROWSER, + Data: packData, + Sync: false, + } + + err = json.NewEncoder(&buffer).Encode(taskData) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} + +/// SYNC + +func SyncBrowserDisks(ts Teamserver, task TaskData, drivesSlice []ListingDrivesData) { + var ( + jsonDrives string + jsonTask string + jsonData []byte + err error + ) + + jsonData, err = json.Marshal(drivesSlice) + if err != nil { + return + } + jsonDrives = string(jsonData) + + jsonData, err = json.Marshal(task) + if err != nil { + return + } + jsonTask = string(jsonData) + + ts.TsClientBrowserDisks(jsonTask, jsonDrives) +} diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index e36d954f..d8d59313 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -47,6 +47,7 @@ SET( HEADERS Headers/Client/LastTickWorker.h Headers/Agent/Commander.h Headers/UI/Widgets/DownloadsWidget.h + Headers/UI/Widgets/BrowserFilesWidget.h ) add_executable(AdaptixClient @@ -81,6 +82,7 @@ add_executable(AdaptixClient Source/Client/LastTickWorker.cpp Source/Agent/Commander.cpp Source/UI/Widgets/DownloadWidget.cpp + Source/UI/Widgets/BrowserFilesWidget.cpp ) target_link_libraries(AdaptixClient diff --git a/Client/Headers/Agent/Agent.h b/Client/Headers/Agent/Agent.h index 02977053..d5983c9a 100644 --- a/Client/Headers/Agent/Agent.h +++ b/Client/Headers/Agent/Agent.h @@ -5,15 +5,17 @@ #include #include #include +#include #include class ConsoleWidget; +class BrowserFilesWidget; class AdaptixWidget; class Agent { public: - AdaptixWidget* mainWidget = nullptr; + AdaptixWidget* adaptixWidget = nullptr; AgentData data = {0}; @@ -33,12 +35,15 @@ public: TableWidgetItemAgent* item_Last; TableWidgetItemAgent* item_Sleep; - ConsoleWidget* Console = nullptr; + ConsoleWidget* Console = nullptr; + BrowserFilesWidget* FileBrowser = nullptr; explicit Agent(QJsonObject jsonObjAgentData, Commander* commander, AdaptixWidget* w ); ~Agent(); void Update(QJsonObject jsonObjAgentData); + + QString BrowserDisks(); }; #endif //ADAPTIXCLIENT_AGENT_H diff --git a/Client/Headers/Client/Requestor.h b/Client/Headers/Client/Requestor.h index 42137739..54db6d43 100644 --- a/Client/Headers/Client/Requestor.h +++ b/Client/Headers/Client/Requestor.h @@ -30,5 +30,7 @@ bool HttpReqAgentSetTag( QString agentId, QString tag, AuthProfile profile, QStr bool HttpReqBrowserDownload( QString action, QString fileId, AuthProfile profile, QString* message, bool* ok ); +bool HttpReqBrowserDisks( QString agentId, AuthProfile profile, QString* message, bool* ok ); + #endif //ADAPTIXCLIENT_REQUESTOR_H diff --git a/Client/Headers/UI/Widgets/AdaptixWidget.h b/Client/Headers/UI/Widgets/AdaptixWidget.h index 7177ada1..5a9a3c9b 100644 --- a/Client/Headers/UI/Widgets/AdaptixWidget.h +++ b/Client/Headers/UI/Widgets/AdaptixWidget.h @@ -83,6 +83,7 @@ public slots: void LoadDownloadsUI(); void OnReconnect(); void LoadConsoleUI(QString AgentId); + void LoadFileBrowserUI(QString AgentId); }; #endif //ADAPTIXCLIENT_ADAPTIXWIDGET_H \ No newline at end of file diff --git a/Client/Headers/UI/Widgets/BrowserFilesWidget.h b/Client/Headers/UI/Widgets/BrowserFilesWidget.h new file mode 100644 index 00000000..3eff04bc --- /dev/null +++ b/Client/Headers/UI/Widgets/BrowserFilesWidget.h @@ -0,0 +1,48 @@ +#ifndef ADAPTIXCLIENT_BROWSERFILESWIDGET_H +#define ADAPTIXCLIENT_BROWSERFILESWIDGET_H + +#include +#include + +typedef struct BrowserFileData +{ + QString Path; + QString Type; + QString Modified; + QVector Files; +} BrowserFileData; + +class BrowserFilesWidget : public QWidget +{ + QGridLayout* mainGridLayout = nullptr; + QGridLayout* listGridLayout = nullptr; + QTreeWidget* treeBrowserWidget = nullptr; + QWidget* listBrowserWidget = nullptr; + QTableWidget* tableWidget = nullptr; + QLabel* statusLabel = nullptr; + QSplitter* splitter = nullptr; + QLineEdit* pathInput = nullptr; + QPushButton* buttonHomeDir = nullptr; + QPushButton* buttonReload = nullptr; + QPushButton* buttonUpload = nullptr; + QPushButton* buttonDisks = nullptr; + QPushButton* buttonCd = nullptr; + QFrame* line_1 = nullptr; + QFrame* line_2 = nullptr; + + Agent* agent; + + void TreeAddData(BrowserFileData data); + void createUI(); + +public: + BrowserFilesWidget(Agent* a); + ~BrowserFilesWidget(); + + void SetDisks(qint64 time, int msgType, QString message, QString data); + +public slots: + void onDisks(); +}; + +#endif //ADAPTIXCLIENT_BROWSERFILESWIDGET_H \ No newline at end of file diff --git a/Client/Headers/UI/Widgets/ConsoleWidget.h b/Client/Headers/UI/Widgets/ConsoleWidget.h index b6c93e8c..8002a6a2 100644 --- a/Client/Headers/UI/Widgets/ConsoleWidget.h +++ b/Client/Headers/UI/Widgets/ConsoleWidget.h @@ -43,4 +43,4 @@ public slots: void processInput(); }; -#endif //ADAPTIXCLIENT_CONSOLEWIDGET_H +#endif //ADAPTIXCLIENT_CONSOLEWIDGET_H \ No newline at end of file diff --git a/Client/Headers/UI/Widgets/SessionsTableWidget.h b/Client/Headers/UI/Widgets/SessionsTableWidget.h index 6aa928af..36f89083 100644 --- a/Client/Headers/UI/Widgets/SessionsTableWidget.h +++ b/Client/Headers/UI/Widgets/SessionsTableWidget.h @@ -59,6 +59,7 @@ public slots: void handleSessionsTableMenu(const QPoint &pos ); void actionConsoleOpen(); + void actionFileBrowserOpen(); void actionAgentTag(); void actionAgentHide(); void actionAgentRemove(); diff --git a/Client/Headers/Utils/FileSystem.h b/Client/Headers/Utils/FileSystem.h index 6b059143..6b7e21f8 100644 --- a/Client/Headers/Utils/FileSystem.h +++ b/Client/Headers/Utils/FileSystem.h @@ -8,4 +8,6 @@ QString ReadFileString(const QString &filePath, bool* result); +QString GetRootPathWindows(const QString& path); + #endif //ADAPTIXCLIENT_FILESYSTEM_H diff --git a/Client/Headers/main.h b/Client/Headers/main.h index ca7433f0..1aa5643d 100644 --- a/Client/Headers/main.h +++ b/Client/Headers/main.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,8 @@ #define TYPE_DOWNLOAD_UPDATE 0x52 #define TYPE_DOWNLOAD_DELETE 0x53 +#define TYPE_BROWSER_DISK 0x61 + ////////// #define DOWNLOAD_STATE_RUNNING 0x1 diff --git a/Client/Resources/Resources.qrc b/Client/Resources/Resources.qrc index 0f56ebc7..ba49b5b7 100755 --- a/Client/Resources/Resources.qrc +++ b/Client/Resources/Resources.qrc @@ -32,11 +32,12 @@ icons/close_dp64.png icons/double_arrow_down_64dp.png icons/autorenew_64dp.png - icons/reply_64dp.png + icons/folder_64dp.png icons/upload_file_64dp.png icons/arrow_right_alt_64dp.png icons/storage_64dp.png + icons/filesystem/ssd_64dp.png diff --git a/Client/Resources/icons/arrow_right_alt_64dp.png b/Client/Resources/icons/arrow_right_alt_64dp.png new file mode 100644 index 00000000..4995360b Binary files /dev/null and b/Client/Resources/icons/arrow_right_alt_64dp.png differ diff --git a/Client/Resources/icons/autorenew_64dp.png b/Client/Resources/icons/autorenew_64dp.png new file mode 100644 index 00000000..3f631835 Binary files /dev/null and b/Client/Resources/icons/autorenew_64dp.png differ diff --git a/Client/Resources/icons/folder_64dp.png b/Client/Resources/icons/folder_64dp.png new file mode 100644 index 00000000..698c2656 Binary files /dev/null and b/Client/Resources/icons/folder_64dp.png differ diff --git a/Client/Resources/icons/folder_open_64dp.png b/Client/Resources/icons/folder_open_64dp.png new file mode 100644 index 00000000..bfaedaef Binary files /dev/null and b/Client/Resources/icons/folder_open_64dp.png differ diff --git a/Client/Resources/icons/reply_64dp.png b/Client/Resources/icons/reply_64dp.png new file mode 100644 index 00000000..e1e01ae5 Binary files /dev/null and b/Client/Resources/icons/reply_64dp.png differ diff --git a/Client/Resources/icons/storage_64dp.png b/Client/Resources/icons/storage_64dp.png new file mode 100644 index 00000000..b7ef070a Binary files /dev/null and b/Client/Resources/icons/storage_64dp.png differ diff --git a/Client/Resources/icons/upload_file_64dp.png b/Client/Resources/icons/upload_file_64dp.png new file mode 100644 index 00000000..4b5bafa1 Binary files /dev/null and b/Client/Resources/icons/upload_file_64dp.png differ diff --git a/Client/Source/Agent/Agent.cpp b/Client/Source/Agent/Agent.cpp index 2ff966b5..30921e83 100644 --- a/Client/Source/Agent/Agent.cpp +++ b/Client/Source/Agent/Agent.cpp @@ -1,8 +1,9 @@ #include +#include Agent::Agent(QJsonObject jsonObjAgentData, Commander* commander, AdaptixWidget* w) { - mainWidget = w; + adaptixWidget = w; data.Id = jsonObjAgentData["a_id"].toString(); data.Name = jsonObjAgentData["a_name"].toString(); @@ -53,9 +54,12 @@ Agent::Agent(QJsonObject jsonObjAgentData, Commander* commander, AdaptixWidget* item_Sleep = new TableWidgetItemAgent( sleep, this ); item_Pid = new TableWidgetItemAgent( data.Pid, this ); - Console = new ConsoleWidget(this, commander ); + Console = new ConsoleWidget(this, commander ); + FileBrowser = new BrowserFilesWidget(this); } +Agent::~Agent() = default; + void Agent::Update(QJsonObject jsonObjAgentData) { data.Sleep = jsonObjAgentData["a_sleep"].toDouble(); @@ -75,4 +79,14 @@ void Agent::Update(QJsonObject jsonObjAgentData) item_Sleep->setText(sleep); } -Agent::~Agent() = default; +QString Agent::BrowserDisks() +{ + QString message = QString(); + bool ok = false; + bool result = HttpReqBrowserDisks( data.Id, *(adaptixWidget->GetProfile()), &message, &ok); + if (!result) + return "JWT error"; + + return message; +} + diff --git a/Client/Source/Client/ProcessSyncPacket.cpp b/Client/Source/Client/ProcessSyncPacket.cpp index 1c39a6c5..83b02ca1 100644 --- a/Client/Source/Client/ProcessSyncPacket.cpp +++ b/Client/Source/Client/ProcessSyncPacket.cpp @@ -316,13 +316,32 @@ bool AdaptixWidget::isValidSyncPacket(QJsonObject jsonObj) } return true; } - if(spType == TYPE_DOWNLOAD_DELETE ) { + if( spType == TYPE_DOWNLOAD_DELETE ) { if (!jsonObj.contains("d_file_id") || !jsonObj["d_file_id"].isString()) { return false; } return true; } + if( spType == TYPE_BROWSER_DISK ) { + if (!jsonObj.contains("b_agent_id") || !jsonObj["b_agent_id"].isString()) { + return false; + } + if (!jsonObj.contains("b_time") || !jsonObj["b_time"].isDouble()) { + return false; + } + if (!jsonObj.contains("b_msg_type") || !jsonObj["b_msg_type"].isDouble()) { + return false; + } + if (!jsonObj.contains("b_message") || !jsonObj["b_message"].isString()) { + return false; + } + if (!jsonObj.contains("b_data") || !jsonObj["b_data"].isString()) { + return false; + } + return true; + } + return false; } @@ -574,4 +593,18 @@ void AdaptixWidget::processSyncPacket(QJsonObject jsonObj) return; } + + if( spType == TYPE_BROWSER_DISK ) + { + QString agentId = jsonObj["b_agent_id"].toString(); + qint64 time = jsonObj["b_time"].toDouble(); + int msgType = jsonObj["b_msg_type"].toDouble(); + QString message = jsonObj["b_message"].toString(); + QString data = jsonObj["b_data"].toString(); + + if (Agents.contains(agentId)) + Agents[agentId]->FileBrowser->SetDisks(time, msgType, message, data); + + return; + } } diff --git a/Client/Source/Client/Requestor.cpp b/Client/Source/Client/Requestor.cpp index ef21d591..8e837943 100644 --- a/Client/Source/Client/Requestor.cpp +++ b/Client/Source/Client/Requestor.cpp @@ -190,4 +190,20 @@ bool HttpReqBrowserDownload( QString action, QString fileId, AuthProfile profile return true; } return false; +} + +bool HttpReqBrowserDisks( QString agentId, AuthProfile profile, QString* message, bool* ok ) +{ + QJsonObject dataJson; + dataJson["agent_id"] = agentId; + QByteArray jsonData = QJsonDocument(dataJson).toJson(); + + QString sUrl = profile.GetURL() + "/browser/disks"; + QJsonObject jsonObject = HttpReq(sUrl, jsonData, profile.GetAccessToken()); + if ( jsonObject.contains("message") && jsonObject.contains("ok") ) { + *message = jsonObject["message"].toString(); + *ok = jsonObject["ok"].toBool(); + return true; + } + return false; } \ No newline at end of file diff --git a/Client/Source/UI/Widgets/AdaptixWidget.cpp b/Client/Source/UI/Widgets/AdaptixWidget.cpp index 01603d05..edd67907 100644 --- a/Client/Source/UI/Widgets/AdaptixWidget.cpp +++ b/Client/Source/UI/Widgets/AdaptixWidget.cpp @@ -294,6 +294,15 @@ void AdaptixWidget::LoadConsoleUI(QString AgentId) this->AddTab(Agents[AgentId]->Console, text); } +void AdaptixWidget::LoadFileBrowserUI(QString AgentId) +{ + if( !Agents.contains(AgentId) ) + return; + + auto text = QString("Files [%1]").arg( AgentId ); + this->AddTab(Agents[AgentId]->FileBrowser, text); +} + void AdaptixWidget::ChannelClose() { QIcon onReconnectButton = RecolorIcon(QIcon(":/icons/unlink"), COLOR_ChiliPepper); diff --git a/Client/Source/UI/Widgets/BrowserFilesWidget.cpp b/Client/Source/UI/Widgets/BrowserFilesWidget.cpp new file mode 100644 index 00000000..bfef3ba2 --- /dev/null +++ b/Client/Source/UI/Widgets/BrowserFilesWidget.cpp @@ -0,0 +1,163 @@ +#include +#include + +BrowserFilesWidget::BrowserFilesWidget(Agent* a) +{ + agent = a; + this->createUI(); + + connect( buttonDisks, &QPushButton::clicked, this, &BrowserFilesWidget::onDisks); +} + +BrowserFilesWidget::~BrowserFilesWidget() = default; + +void BrowserFilesWidget::createUI() +{ + buttonReload = new QPushButton(QIcon(":/icons/reload"), "", this); + buttonReload->setIconSize( QSize( 24,24 )); + buttonReload->setFixedSize(37, 28); + buttonReload->setToolTip("Reload"); + + buttonHomeDir = new QPushButton( QIcon(":/icons/folder"), "", this); + buttonHomeDir->setIconSize( QSize( 24,24 )); + buttonHomeDir->setFixedSize(37, 28); + buttonHomeDir->setToolTip("Up folder"); + + pathInput = new QLineEdit(this); + + buttonCd = new QPushButton(QIcon(":/icons/arrow_right"), "", this); + buttonCd->setIconSize( QSize( 24,24 )); + buttonCd->setFixedSize(37, 28); + + line_1 = new QFrame(this); + line_1->setFrameShape(QFrame::VLine); + line_1->setMinimumHeight(25); + + buttonDisks = new QPushButton(QIcon(":/icons/storage"), "", this); + buttonDisks->setIconSize( QSize( 24,24 )); + buttonDisks->setFixedSize(37, 28); + buttonDisks->setToolTip("Disks list"); + + buttonUpload = new QPushButton(QIcon(":/icons/upload"), "", this); + buttonUpload->setIconSize( QSize( 24,24 )); + buttonUpload->setFixedSize(37, 28); + buttonUpload->setToolTip("Upload File"); + + line_2 = new QFrame(this); + line_2->setFrameShape(QFrame::VLine); + line_2->setMinimumHeight(25); + + statusLabel = new QLabel(this); + statusLabel->setText("Status: "); + + tableWidget = new QTableWidget(this ); + tableWidget->setColumnCount(3); + tableWidget->setContextMenuPolicy( Qt::CustomContextMenu ); + tableWidget->setAutoFillBackground( false ); + tableWidget->setShowGrid( false ); + tableWidget->setSortingEnabled( false ); + tableWidget->setWordWrap( true ); + tableWidget->setCornerButtonEnabled( true ); + tableWidget->setSelectionBehavior( QAbstractItemView::SelectRows ); + tableWidget->setFocusPolicy( Qt::NoFocus ); + tableWidget->setAlternatingRowColors( true ); + tableWidget->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch ); + tableWidget->horizontalHeader()->setCascadingSectionResizes( true ); + tableWidget->horizontalHeader()->setHighlightSections( false ); + tableWidget->verticalHeader()->setVisible( false ); + + tableWidget->setHorizontalHeaderItem( 0, new QTableWidgetItem( "Name" ) ); + tableWidget->setHorizontalHeaderItem( 1, new QTableWidgetItem( "Size" ) ); + tableWidget->setHorizontalHeaderItem( 2, new QTableWidgetItem( "Last Modified" ) ); + + listGridLayout = new QGridLayout(this); + listGridLayout->setContentsMargins(5, 4, 1, 1); + listGridLayout->setVerticalSpacing(4); + listGridLayout->setHorizontalSpacing(8); + + listGridLayout->addWidget( buttonReload, 0, 0, 1, 1 ); + listGridLayout->addWidget( buttonHomeDir, 0, 1, 1, 1 ); + listGridLayout->addWidget( pathInput, 0, 2, 1, 5 ); + listGridLayout->addWidget( buttonCd, 0, 7, 1, 1 ); + listGridLayout->addWidget( line_1, 0, 8, 1, 1 ); + listGridLayout->addWidget( buttonUpload, 0, 9, 1, 1 ); + listGridLayout->addWidget( buttonDisks, 0, 10, 1, 1 ); + listGridLayout->addWidget( line_2, 0, 11, 1, 1 ); + listGridLayout->addWidget( statusLabel, 0, 12, 1, 1 ); + listGridLayout->addWidget( tableWidget, 1, 0, 1, 13 ); + + listBrowserWidget = new QWidget(this); + listBrowserWidget->setLayout(listGridLayout); + + treeBrowserWidget = new QTreeWidget(); + treeBrowserWidget->setSortingEnabled(false); + treeBrowserWidget->headerItem()->setText( 0, "Directory Tree" ); + + splitter = new QSplitter( this ); + splitter->setOrientation( Qt::Horizontal ); + splitter->addWidget( treeBrowserWidget ); + splitter->addWidget( listBrowserWidget ); + splitter->setSizes( QList() << 1 << 250 ); + + mainGridLayout = new QGridLayout(this ); + mainGridLayout->setContentsMargins(0, 0, 0, 0 ); + mainGridLayout->addWidget(splitter, 0, 0, 1, 1 ); + + this->setLayout(mainGridLayout); +} + +void BrowserFilesWidget::SetDisks(qint64 time, int msgType, QString message, QString data) +{ + QString sTime = UnixTimestampGlobalToStringLocal(time); + QString status = ""; + if( msgType == CONSOLE_OUT_LOCAL_ERROR || msgType == CONSOLE_OUT_ERROR ) { + status = TextColorHtml(message, COLOR_Berry) + " >> " + sTime; + } + else { + status = TextColorHtml(message, COLOR_NeonGreen) + " >> " + sTime; + } + + statusLabel->setText(status); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(data.toUtf8()); + if (!jsonDoc.isArray()) + return; + + QJsonArray jsonArray = jsonDoc.array(); + for (const QJsonValue& value : jsonArray) { + QJsonObject jsonObject = value.toObject(); + BrowserFileData fileData; + fileData.Path = jsonObject["b_name"].toString(); + fileData.Type = "Disk"; + fileData.Modified = jsonObject["b_type"].toString(); + this->TreeAddData(fileData); + } +} + +void BrowserFilesWidget::TreeAddData(BrowserFileData data) +{ + QString rootPath = GetRootPathWindows(data.Path); + QTreeWidgetItem* rootItem = nullptr; + for (int i = 0; i < treeBrowserWidget->topLevelItemCount(); ++i) { + auto item = treeBrowserWidget->topLevelItem(i); + if (item->text(0) == rootPath) { + rootItem = item; + break; + } + } + + if (!rootItem) { + rootItem = new QTreeWidgetItem(treeBrowserWidget); + rootItem->setText(0, rootPath); + rootItem->setIcon(0, QIcon(":/icons/ssd")); + treeBrowserWidget->addTopLevelItem(rootItem); + } +} + +/// SLOTS + +void BrowserFilesWidget::onDisks() +{ + QString status = agent->BrowserDisks(); + statusLabel->setText(status); +} \ No newline at end of file diff --git a/Client/Source/UI/Widgets/ConsoleWidget.cpp b/Client/Source/UI/Widgets/ConsoleWidget.cpp index 2f20e7cc..6d8227c5 100644 --- a/Client/Source/UI/Widgets/ConsoleWidget.cpp +++ b/Client/Source/UI/Widgets/ConsoleWidget.cpp @@ -156,7 +156,7 @@ void ConsoleWidget::processInput() else { QString message = QString(); bool ok = false; - bool result = HttpReqAgentCommand(agent->data.Name, agent->data.Id, commandLine, cmdResult.message, *(agent->mainWidget->GetProfile()), &message, &ok); + bool result = HttpReqAgentCommand(agent->data.Name, agent->data.Id, commandLine, cmdResult.message, *(agent->adaptixWidget->GetProfile()), &message, &ok); if( !result ) { MessageError("JWT error"); return; diff --git a/Client/Source/UI/Widgets/SessionsTableWidget.cpp b/Client/Source/UI/Widgets/SessionsTableWidget.cpp index cef9519d..0ab47ef4 100644 --- a/Client/Source/UI/Widgets/SessionsTableWidget.cpp +++ b/Client/Source/UI/Widgets/SessionsTableWidget.cpp @@ -76,6 +76,7 @@ void SessionsTableWidget::Clear() Agent* agent = adaptixWidget->Agents[agentId]; adaptixWidget->Agents.remove(agentId); delete agent->Console; + delete agent->FileBrowser; delete agent; } @@ -131,6 +132,7 @@ void SessionsTableWidget::RemoveAgentItem(QString agentId) Agent* agent = adaptixWidget->Agents[agentId]; adaptixWidget->Agents.remove(agentId); delete agent->Console; + delete agent->FileBrowser; delete agent; for( int rowIndex = 0 ; rowIndex < tableWidget->rowCount() ; rowIndex++ ) { @@ -142,6 +144,7 @@ void SessionsTableWidget::RemoveAgentItem(QString agentId) } + /// SLOTS void SessionsTableWidget::handleTableDoubleClicked(const QModelIndex &index) @@ -157,12 +160,24 @@ void SessionsTableWidget::handleSessionsTableMenu(const QPoint &pos ) if ( ! tableWidget->itemAt(pos) ) return; + auto ctxMenu = QMenu(); + + auto agentMenu = new QMenu("Agent", &ctxMenu); + agentMenu->addAction("Command"); + agentMenu->addAction("File Browser", this, &SessionsTableWidget::actionFileBrowserOpen); + agentMenu->addAction("Process Browser"); + agentMenu->addAction("Exit"); + auto sep1 = new QAction(); sep1->setSeparator( true ); - auto ctxMenu = QMenu(); + auto sep2 = new QAction(); + sep2->setSeparator( true ); + ctxMenu.addAction( "Console", this, &SessionsTableWidget::actionConsoleOpen); ctxMenu.addAction( sep1 ); + ctxMenu.addMenu(agentMenu); + ctxMenu.addAction( sep2 ); ctxMenu.addAction( "Tag", this, &SessionsTableWidget::actionAgentTag); ctxMenu.addAction( "Hide", this, &SessionsTableWidget::actionAgentHide); ctxMenu.addAction( "Remove", this, &SessionsTableWidget::actionAgentRemove); @@ -181,6 +196,17 @@ void SessionsTableWidget::actionConsoleOpen() } } +void SessionsTableWidget::actionFileBrowserOpen() +{ + auto adaptixWidget = qobject_cast( mainWidget ); + for( int rowIndex = 0 ; rowIndex < tableWidget->rowCount() ; rowIndex++ ) { + if ( tableWidget->item(rowIndex, 0)->isSelected() ) { + auto agentId = tableWidget->item( rowIndex, ColumnAgentID )->text(); + adaptixWidget->LoadFileBrowserUI(agentId); + } + } +} + void SessionsTableWidget::actionAgentTag() { auto adaptixWidget = qobject_cast( mainWidget ); @@ -237,4 +263,4 @@ void SessionsTableWidget::actionAgentRemove() return; } } -} \ No newline at end of file +} diff --git a/Client/Source/Utils/FileSystem.cpp b/Client/Source/Utils/FileSystem.cpp index 8ca6b691..442f7a91 100644 --- a/Client/Source/Utils/FileSystem.cpp +++ b/Client/Source/Utils/FileSystem.cpp @@ -14,4 +14,21 @@ QString ReadFileString(const QString &filePath, bool* result) *result = true; return content; +} + +QString GetRootPathWindows(const QString& path) +{ + if (path.startsWith("\\\\")) { + int secondSlash = path.indexOf('\\', 2); + if (secondSlash != -1) { + return path.left(secondSlash); + } + } + + int firstSlash = path.indexOf('\\'); + if (firstSlash != -1) { + return path.left(firstSlash); + } + + return path; } \ No newline at end of file