Microsoft Configuration Manager

Dissecting an SCCM bulk registration token

When looking at feasible processes for installing the Configuration Manager client using the “Co-management settings” feature in Intune with Hybrid Azure AD Join, bulk registration tokens appeared to be the best option. (I haven’t yet given up on PKI certificates, but there are challenges with that.) But since I hadn’t used these before, I figured I would take some time to explore them some to see how they work.

First, I had to get past an installation issue. It appears that somewhere in my many Tech Preview build installs, the bulkregistrationtokentool.exe binary was updated, but the dependent DLLs were not, resulting in this error when trying to run the tool:

Some experimentation showed that the referenced “System.IdentityModel.Tokens.Jwt” DLL file, located in the same folder, was the wrong version, hence the error. But I noticed the same DLL file in the “EasySetupPayload” subfolder was newer. So what happens if I run it from there instead? Works just fine:

So what exactly is the token that was returned? From the assembly (DLL) referenced, and from plenty of experience working with Azure AD, it was obviously a JWT, a base64-encoded and signed JSON web token. Fortunately, those are fairly easy to decode using websites like https://jwt.io:

Fairly basic compared to most JWTs, but easy enough to follow: it’s an SCCM-specific format (not related to Azure AD in any way), with the “exp” time specifying a Unix-style expiration date and time and “nbf” specifying a “not valid before” time. In this case, the site can’t validate the signature of the JWT because it’s been signed by a certificate (key) that isn’t valid on the internet. That’s not a problem, it just means that SCCM is using a key of its own making to do this.

So how do these work? In this implementation, the tool records the token’s unique ID value (as seen above), along with the starting and ending validity information into the site server’s SQL Server database, inserting it into the BulkRegistrationToken table:

What happens if you run the tool again? It adds another row. So you will end up with one row for every still-valid token, and those rows get cleaned off after they expire. So when a client comes along and tries to use the generated token, the SCCM server just needs to verify the signature of the JWT, and then it needs to make sure that the token ID is in the database table and is still valid and enabled. You can see the validation logic in the SQL stored procedure:

Notice that this isn’t doing anything to validate the JWT itself, and the signing of that JWT token. Presumably that is being done prior to this stored procedure being called — at least I hope it is, I didn’t take the time to verify. (That would be a pretty significant security flaw if that happened, but it has happened before…)

So if the JWT is signed, that means that there is a certificate somewhere (with private key attached) that can be retrieved. So how do you figure out where? Well, since the bulk registration tool is a .NET executable, we can pretty easily decompile it using something like ILSpy and look.

So it’s stored in the site server database in the CertificateData table, with CertType=5. That’s the last one we see (no idea what the other two are for, but that’s not important at the moment):

As the code above shows, the certificate details are stored encrypted in the database, so they need to be decrypted. I can tell that is being done by the DecryptWrapper function in the SCCM basesvr.dll file, but how that works behind the scenes is a mystery. Once decrypted, the certificate can then be used to sign a new JWT; that’s what is returned by the bulk tool, with a record of the token written to the database (but not the whole token).

OK, overall this all makes perfect sense. But there’s one other question I needed to answer: What about the 7-day maximum for the expiration date of the token? That’s where the “/lifetime 10080” switch comes in on the command line: you can’t specify anything longer than 7 days, which is 10080 minutes. You could presumably generate a new token every 6-7 days using PowerShell to automate the process (it would be a pain in the rear to recreate what the .NET executable does — not impossible though), then use the Graph API to save that value to the appropriate Intune “Co-management settings” object, saved in a https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations instance. Automate that using a scheduled task and an Azure AD app and secret or cert and you’d be all set.

But that 7-day maximum is a little annoying, especially since I don’t want to spend the time to automate the updating of the value. I can see that the value is being checked in the decompiled tool code:

So this is a client-side check, not something in the site server. So what happens if we change that value? Making a copy of the .exe file and opening it in a hex editor (I prefer HxD), you can search for the constant that is referenced in the code:

We know it is a 32-bit value (the decompiling shows it is of type “int”, which is four bytes), so we can tell HxD to search for any four bytes containing the value 10080, and it only finds one which makes things simple. Let’s make that much bigger. The bytes are in reverse order, so if we put in something like “ff ff ff 7f” we should get a very large positive integer.

Click the save icon, and then open this modified binary in the decompiler to verify that we made the right change:

Cool, int.MaxValue will do nicely. Let’s run it with a value like 525,600 minutes (yes, I now have that song stuck in my head) to see if it works:

Sadly, it still fails, but digging into the referenced Microsoft.ConfigurationManager.CloudBase.AuthorizationToken.TokenIssuer.IssueToken function shows that it only checks that the value is less than 129600, which is 90 days:

So no year-long tokens, but one that is one minute shy of 90 days sounds nice, let’s try that.

Presto, now I’ve deferred my token renewal problem until December 10th, 2023. (I could do an additional modification of the Microsoft.ConfigurationManager.CloudBase DLL to get larger values, but that didn’t seem to be worth the effort. Note that you might need to do some strong-name validation and/or signing bypasses to get this to work — I don’t recall doing these previously on my Server 2022 site server, but it “just worked” even after modifying the signed binary.)

Back to my previous task, figuring out Hybrid Azure AD Join and “Co-Management Settings”…

7 replies »

    • I actually left it as MaxInt, since the other check was looking for 90 days. But the value for that would be 129600, or 0001FA40 in hex. Since the bytes are reversed, you would want to put in 40FA0100 in HxD (or your preferred hex editor).

      Like

  1. Finally!! Thank you for this!! I’ve already automated this process with an Azure client/secret and using Scriptrunner to run the task every 7 days, but it will be better to run it every 90 days only!

    Just an off topic question: Is there any way to do Pre-Provisioning deployment (former White Glove) with HAADJ and Co-Management settings? I cant get this to work, it fails the sccm agent installation. Logs seem to indicate that it cant connect to CMG ad needs an Azure AD user logged in order to work.
    (Yeah i know that Microsoft does not support this scenario, but HAADJ is the best of both worlds and still too good to move away.)

    Like