CategoriesIdentity

Simple MIM Sync Run Profiles Scheduler using PowerShell and Task Scheduler – GG EZ

Today I’m going to share one weird trick to schedule MIM Run Profiles using PowerShell. Consultants hate me. Let’s get started.

Script

We are going to create two folders. The first folder is where we are going to store the PowerShell script and configuration. The second folder is where we are going to store our logfiles. I will use C:\MIMRunScheduler and C:\MIMRunScheduler\Log.

param([string]$configfile=$(Read-Host -prompt "Configfile"))

### Load Configuration Data ###
[xml]$maconfig=(Get-Content $configfile)
"" > $maconfig.runcycle.logfile
$sourceFolder="C:\MIMRunScheduler\"
### Functions ###
$line = "---------------------------------------------------------------------------------"
function Write-Output-Banner([string]$msg) {
       Write-Output $line,(" "+$msg),$line
    Write-Output $line,(" "+$msg),$line | Add-Content $sourceFolder"Log\$(Get-Date -f yyyy-MM-dd).log"
}

function Write-Output-Text([string]$msg) {
       Write-Output $msg
    Write-Output $msg | Add-Content $sourceFolder"Log\$(Get-Date -f yyyy-MM-dd).log"
}


### Get Management Agent Data ###
$allMA = @(get-wmiobject -class "MIIS_ManagementAgent" -namespace "root\MicrosoftIdentityIntegrationServer" -computername $maconfig.runcycle.computername)
$numOfExecDone = 0


### Main Script ###
do {
       Write-Output-Banner("Execution #:"+(++$numOfExecDone)+" - Date: "+(date))
       foreach($MANextRun in $maconfig.runcycle.ma) {
             $found = $false;
             
             foreach($MA in $allMA) {   
               if(!$found) {
                    if($MA.Name.Equals($MANextRun.name)) {
                           $found=$true;
                           Write-Output-Banner("MA: "+$MA.Name+" [Type: "+$MA.Type+"]")
                           Write-Output-Text(" - Starting Pre-Script: "+$MANextRun.preScript)
                           invoke-expression $MANextRun.preScript | Out-Null
                           Write-Output-Text(" - Done`n")
                           
                           foreach($profileName in $MANextRun.profilesToRun) {
                                  Write-Output-Text(" - Starting Profile: "+$profileName)
                                  $datetimeBefore = Get-Date;
                                  $result = $MA.Execute($profileName);
                                  $datetimeAfter = Get-Date;
                                  $duration = $datetimeAfter - $datetimeBefore;
                                  Write-Output-Text(" - Done with status: "+$result.ReturnValue+" - Duration: "+$duration.Hours+":"+$duration.Minutes+":"+$duration.Seconds+"`n")
                           }
                           
                           Write-Output-Text(" - Starting Post-Script: "+$MANextRun.postScript)
                           invoke-expression $MANextRun.postScript | Out-Null
                           Write-Output-Text(" - Done`n")
                           Start-Sleep -s $MANextRun.waitSeconds
                    }
               }
             }
             if(!$found) { Write-Output-Text("Not found MA name :"+$MANextRun.name); }
       }

       $continue = ($maconfig.runcycle.cycleWaitSeconds -EQ 0) -OR ($numOfExecDone -lt $maconfig.runcycle.numOfExecs) -OR ($maconfig.runcycle.numOfExecs -EQ -1)
       if($continue) { 
             Write-Output-Banner("Sleeping "+$maconfig.runcycle.cycleWaitSeconds+" seconds")
             Start-Sleep -s $maconfig.runcycle.cycleWaitSeconds
       }
} while($continue)

Save the PowerShell script as C:\MIMRunScheduler\MIMRunCycle.ps1.

The PowerShell script accepts an XML configuration file as a parameter and causes the MIM Synchronization Engine runs to be performed. How the runs are performed is described in the configuration file.

Within the PowerShell script, there is a $sourceFolder variable. This is responsible for the location where the log files will reside.

Script Syntax

.\MIMRunCycle.ps1 [-configFile] <location>

-configFile <location>

The <location> refers to the location of the configuration file. The following example shows how this can be done:

-configfile C:\MIMRunScheduler\FULLConfig.XML

NameValue
Required?True
Accept wildcard characters?False
Accept pipeline input?False
PositionNamed

Configuration

We are going to create two additional scripts called C:\MIMRunScheduler\pre.ps1 and C:\MIMRunScheduler\post.ps1. If you want to perform an action right before or after a run, you can use these two scripts. We are going to leave them empty.

<?xml version="1.0" encoding="UTF-8"?>
<runcycle numOfExecs="0" cycleWaitSeconds="15" computerName=".">
   <MA>
      <name>ADMA</name>
      <preScript>C:\MIMRunScheduler\pre.ps1</preScript>
      <profilesToRun>FIFS</profilesToRun>
      <postScript>C:\MIMRunScheduler\post.ps1</postScript>
      <waitSeconds>5</waitSeconds>
   </MA>
   <MA>
      <name>MIMMA</name>
      <preScript>C:\MIMRunScheduler\pre.ps1</preScript>
      <profilesToRun>FIFS</profilesToRun>
      <postScript>C:\MIMRunScheduler\post.ps1</postScript>
      <waitSeconds>5</waitSeconds>
   </MA>
</runcycle>

Save the configuration file as C:\MIMRunScheduler\FULLConfig.xml.

Configuration Syntax

Each configuration file starts with the XML declaration.

<?xml version="1.0" encoding="UTF-8"?>

After that, run cycles (runcycle in XML) can be defined. A runcycle contains three parameters.

<runcycle numOfExecs="0" cycleWaitSeconds="15" computerName=".">
NameDatatypeDescription
numOfExecsInteger0 = perform the run once
-1 = perform the runs in perpetuity
>0 = define how often the runs should be performed
cycleWaitSecondsIntegerTime between performing run cycles in seconds.
computerNameStringSpecify the computer name in FQDN or in NetBIOS on which MIM is installed. Use the value “.” if the PowerShell script is running on the same server on which MIM is installed.

In a runcycle, multiple MA elements (<MA>) can be defined. In each MA element contains the following elements.

   <MA>
      <name>ADMA</name>
      <preScript>C:\MIMRunScheduler\pre.ps1</preScript>
      <profilesToRun>FIFS</profilesToRun>
      <postScript>C:\MIMRunScheduler\post.ps1</postScript>
      <waitSeconds>5</waitSeconds>
   </MA>
NameDatatypeDescription
nameStringSpecify the name of the Management Agent.
preScriptStringSpecify the location of the PowerShell script to be performed before the run.
profilesToRunStringSpecify the run profile of the selected Management Agent from the name element.
postScriptStringSpecify the location of the PowerShell script to be performed after the run.
waitSecondsIntegerSpecify how many seconds to wait between running the Management Agents.

Task Scheduler

Create a new task in Windows Task Scheduler.

In the General tab, ensure that the task is run by a service account with sufficient rights in MIMSync. Furthermore, pay attention to the Local Security PolicyLocal PoliciesUser Rights Management > Log on as a batch job permission.

Go to the Actions tab.

Press New… to create a new action.

Action:Start a program
Program/script:Powershell.exe “C:\MIMRunScheduler\MIMRunCycle.ps1”
Add arguments (optional):-configfile “C:\MIMRunScheduler\FULLConfig.XML”

Click on OK to save.

Side note: In some environments, the PowerShell execution policy is set to restricted so make sure that’s dealt with first with the system administrator and security officer.

Action:Start a program
Program/script:Powershell.exe -ExecutionPolicy Bypass “C:\MIMRunScheduler\MIMRunCycle.ps1”
Add arguments (optional):-configfile “C:\MIMRunScheduler\FULLConfig.XML”
Disclaimer: Use -ExecutionPolicy Bypass at your own discretion. CISO’s hate this one trick.

Save and run the task.

Conclusion

That’s all folks! I really prefer this method of scheduling MIMSync runs because the script is very lightweight and easy to understand. Hope that you can make use of this. Have a good one.

CategoriesSecurity

Crimediggers | Decrypting Base64 messages using RSA private keys – Encrypted Data: (chuckles) I’m in danger.

A friend of mine was doing an online interactive puzzle/game called Crimediggers developed by the Dutch law enforcement agency, and he asked if I could help him with decrypting some encoded messages.. Hmm, interesting!

He already collected the encoded messages and has obtained the private keys. We are not sure which private key belongs to which encoded message.

Encoded Messages

These are the encoded messages in question:

mLkcoaOGPIdIDUnoWdKd0rca66fMWE/bs0YUJWS9waiPrxJQr6KMbL2le/DYtDDKy+cl8EKj0Q1LL9duUpdhszFGYjC98j2tu38lnb3Im5aevhqqo7mds+ZuE2XNtzEJ2dXdSYzhZvzkT6bKKHfFnMr9+WQzUKG9IQiHxg0eBZRvQJFViTY/3aADE11GDs6S4dbvv4sT8MwQva1rGD/Q+d/kAU+av8pRaTlI1fRdESPtHHljkBev7s5fZRJGki1gtwNiInhmajA23x/oSPBTBPzMIR4K5/OIJ2tRCRDmAUvTETNloh6EpOz+vJR+RkbFv6rZIVaxV3yq1dIKYg54hQ==
W1hkk4XxKglKgRo96MQw0glw+O3WpH2OwSy2I+NbWdQ+dYtdJzR1shwCbacCdzF+DNPa07FWhHUE+An6CD8sRPLRyuXR/p8goehgLG3iPma1gyut0VjHyNDIJsJSu4gvnrEcfiWdEYGUKw+NFeYsvDniyOnH/d3BAli8lM8PDxgZjJ6lQ5EZnjQOGS4l2DZWU1lVXfMukK9/5nAO4h41pSCK8ultYFdEk8sdrf3Td2XVp/EIGI9XCwTAZ8H2+XP/eCO4ecgN3GRjttkdQYUhqvlnCaHpsQyNCGzJQpAOycAj/GDTtQ+EoSfVtEAnmOCZRFk12fy8PL+XnUEI/SwIQg==
UdMsi/STSeUBkVUucCBJWwbUzzF0v8bay0suQg9WvBglwZFoGgAVK48d8AWIhEa1euqkF8O2lv4Sprihlzt+HFNEcf1QADyOTZpK+O9PrtEie9o48TbEaFbxSUImepBzLF2/YxSbKhfGYUNb+4MfOn7r0mLmFeslCNQoH/ZfNW76QZ5/0z0HPZOWCCfAEM6n/L7ERh77h4pemGz8UYLTw12TsBNaNeA4vzqNVTtLnzUpcrFrwJrnBOPjw78Ce/jN/8BNhc+PKwvSQw2yqdAUly6+dUq2W/VEuCK3Q+/ilqtRgu5KZBDlarHgMVeS+Sn0dNleiqB0sPUV5O/PPjGE2A==
UxWL9nhlgZUxdbQDMYs6rDcoqHpSKOPN9c32I2+F4pv3FcxNtUh8TcoF0TBe2MoitglYRsRXIKdVl1MmVQUnb9L+QWSaNlgI/GkI9gXrtUIpS6ZluyJzTzKwwh47BKW7d/3YlrenbL0f8PYy7y2JcZK7gJLcp2AJVNkFAlgKZWdcjjvqdY2GoB7GDtztMSqjM27Zs0DgSv3p6QIqJuWMe3eCQhdMGASBcShItd+wQd1z/3nNvmY5Mk5xC7RahIMi4UZitSgLb6cLKfNmIrre9mDZwnHJxpCtLmgh54pW8ACEpo2qfW/dcAa4dtQebwo7HcpixMzcj7rGon0l+pg5HQ==

From these four messages we can establish a couple of things:

  1. Modern encryption algorithms usually outputs into random bytes and yet this message seems to be formatted in a plain text format. This suggests that some kind of binary-to-text encoding have been applied.
  2. The messages seems to be formatted in Base64 because the content resembles a ASCII string and ends with two == symbols.

Let’s check if the messages are indeed Base64. Using regular expression (RegEx) we can determine that the text could have been a Base64 value. However, even if the message passes the RegEx, this does not mean that the decoded content is valid encrypted content. That’s the beauty of encryption! If we assume that it’s encrypted using modern algorithms, we can’t really know for sure that the file is encrypted because it should look randomized.

^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$

We can confirm that each of the four messages are a full match with the RegEx above. Now we need to convert those Base64 encoded messages back to file.

Why? Because we need the original encrypted file to decrypt using the private keys. We can’t apply the private key directly to the Base64 encoded messages. You’ll get a error like:

RSA operation error
19200:error:0406506C:rsa routines:rsa_ossl_private_decrypt:data greater than mod len:crypto\rsa\rsa_ossl.c:401:

The private key should be applied to the output that was generated during the encryption process and not on the Base64 encoded version of it.

Just some additional information…

There’s a lot of ways to do this but since I’m primarily on Windows, I’ll do this with PowerShell. The following script need two variables: $path and $message. The $path variable is where the output of the file will be exported. The $message variable contains the Base64 encoded message. Both variables will be passed into a FromBase64String function.

The FromBase64String method is designed to process a single string that contains all the data to be decoded. Converts the specified string, which encodes binary data as base-64 digits, to an equivalent 8-bit unsigned integer array.

Just some additional information…
$path = 'C:\Temp\encrypted1.bin'
$message = 'mLkcoaOGPIdIDUnoWdKd0rca66fMWE/bs0YUJWS9waiPrxJQr6KMbL2le/DYtDDKy+cl8EKj0Q1LL9duUpdhszFGYjC98j2tu38lnb3Im5aevhqqo7mds+ZuE2XNtzEJ2dXdSYzhZvzkT6bKKHfFnMr9+WQzUKG9IQiHxg0eBZRvQJFViTY/3aADE11GDs6S4dbvv4sT8MwQva1rGD/Q+d/kAU+av8pRaTlI1fRdESPtHHljkBev7s5fZRJGki1gtwNiInhmajA23x/oSPBTBPzMIR4K5/OIJ2tRCRDmAUvTETNloh6EpOz+vJR+RkbFv6rZIVaxV3yq1dIKYg54hQ=='
$bytes = [Convert]::FromBase64String($message)
[IO.File]::WriteAllBytes($path, $bytes)

We will execute this script four times, each time with a different encoded message. In the end, we will have 4 encrypted files that needs to be decrypted.

Private Keys

Now that we have our encoded messages sorted out, let’s go and take a look at the private keys. The private keys are bundled up in a single file looking like this:

-----BEGIN RSA PRIVATE KEY-----
***
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
***
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
***
-----END RSA PRIVATE KEY-----

This tells us that we are dealing with an RSA private key. We can confirm this by performing the following command in OpenSSL.

openssl rsa –noout –check –in $privateKey | openssl md5

The $privateKey variable is used in the “-in” parameter for the input of the private key. It expects a path to the private key file.

If OpenSSL outputs anything else than “RSA key ok” then the key is invalid. In this specific scenario, the private keys are all valid RSA private keys. I will post the private keys in the second page of this post since there are 10 private keys in the bundle. Now I am going to split up each segment into separate files.

Be sure to include —-BEGIN RSA PRIVATE KEY—- and —–END RSA PRIVATE KEY—– in the text when separating the private keys from the bundle.

Important!

This means that I end up with 10 files looking like this:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA3qyBdDzhyyftmxd/FjRHbgtlRnMh8pcBHJVhUe9FNTRsftrF
+9sX2kSoGKSDpmbD0TMckwj8dpWRfVvA5f7xNcPzVlG7HX8FQeIcQjnTFhkWwuNa
LfGD4IXswc82vyYLdW6B5njEKi4I35CkGuOGRqohUuSeiXTlZ6ZaO5HYXZGJ7yCI
1/UGTl5IFuvdqpymqe55egSXN/XrH3Uym5p5Sqh/xtls51ZRl0hMIJZsMVQLb0nf
TrdXWCLfnlmElso5aA02+gi41Ax9c6AreRltXATDQGgFJR9qNs4SE4olJ95shJra
kBnQayNFGrNkDEX4uW48vJ4c2KzRQF1Ujr2irwIDAQABAoIBAGzPjT7Y1Vlp/IDy
HWcjSthqdyXS8v7RbXijQnhAXOhf4rimfWLSMDi3nhDUq4Rv6TZcLJVpKb1k30CL
yppZV1N3mnxO2gnS3SXRsQ9npE/DGM1JPfL/dvsLVqNqd402sRjEVHPkBa+66Rke
XCLLlf0H+9q+3v26grOApc1AOqOVppbYFez7m06RtFWJ2pz3jwk7t0gHYEaBf710
IVwWcLw9OnkX/xo/dwHoBOHfp+0UJ0W83F11Pp4GiE5DoFoS7FYIKa++RrRonJOB
UpgCzr+8yarlZaztCZk0cUS/ck1G9KkDQ4qE1ZG2Cnsd6TS2JlxEEGavQyjy4/Q1
U2IBk9ECgYEA85pb4NqiuCcM8E0ReRRX1yR9yoURJPsbFo2FTz4GNydefqzIovrN
uE5p47E44Y4dtiemcJ+sAntNiXDXriZkOE0QZe90ynHIdOcGpuvWK5A2LnMc82iS
sMv5vd8bzeWX0I/z9C3A/hzjJVSuKbysU9PHE4jhSjPKR+yVHcaY+CcCgYEA6gF7
3e5MByK6r3ZI7enDHrN4XoqtPh6PbmkGq/22LWFw2SeKD3nFTSIXQt+LopjR2Uwt
hAIwv+mvpLfQz59ud2fFhqkBokcf2pF179E9CCQLtM2vNeG3B/It6UE9QkDmGeGs
5hkfBJkXVJhRJl6aAuJjXE7lIvaxTb20gTFpzjkCgYAX0KRg1B8KT88FAxPNOX7o
6oldriqh6eaZCtbYi2DCLZggXDX1MP3BofDaMK415tXiw/eSlRmU2IcZA64iOWCc
3HPY81MAo6UMQLW2xeGXOq8BVYGkDjohDJ/qvxARzftMiXY5DewLK7nNIX0xWV+j
hlW1BDG6Nlv1MhzV319/CwKBgA+dO7uqS+5p9dUBaopdf8SLqyt7y2WKun1eirBt
RHuwtcv3pHG16WFlNfIjYnjeA5qhR6Q4VTJpEGxIS6me9MsI5JhArLg8ULbP3/O5
nX3ukc1lsBxDTGg+U6vty32hQPlXIqdT48r1wCXH6BpMU7B/lKuzI/z6Qwhx8Rpx
xqKZAoGBAJxwngBgwHMuDToAuypPgVx2Vpt5xbuY+Vwed+iiiV85OD4LzkO+yK08
C7mXTOAMzGWYpcEwx7JoxuV/SW45L5pMvoxVEtrVbwF3qAv/FiDivPW8Y8HNOQQb
/qjtTvKFOlajksvYvfZCZJo7nW/MVJE8qYBbNHtJOhMAHVXzZfeh
-----END RSA PRIVATE KEY-----

Solution

We have established that the messages are Base64 encoded and that they need to be converted back to their original encrypted files. Now we can perform decryption using one of the private keys. The following command is used to perform decryption using OpenSSL and RSA and it requires 3 variables.

openssl rsautl -decrypt -in $encrypted -out $output -inkey $privateKey

The $encrypted variable is used in the “-in” parameter for the input of the encrypted file. It expects a path to the encrypted file.

The $output variable is used in the “-out” parameter for the output for the decrypted file. It expects a path to a decrypted file that will be generated.

The $privateKey variable is used in the “-inkey” parameter for the input of the private key. It expects a path to the private key file.

Remember that we had 4 encrypted files and 10 private keys in total? We have to match the corresponding private key to the encrypted file. If we are dealing with a large amount of encrypted files and private keys I would create a for-loop to try each key with each encrypted file. OpenSSL will generate an error when I use an incorrect private key such as:

Attempting to decrypt an encrypted file with an invalid private key with OpenSSL
RSA operation error
7640:error:0407109F:rsa routines:RSA_padding_check_PKCS1_type_2:pkcs decoding error:crypto\rsa\rsa_pk1.c:251:
7640:error:04065072:rsa routines:rsa_ossl_private_decrypt:padding check failed:crypto\rsa\rsa_ossl.c:491:

When OpenSSL doesn’t generate any errors then it managed to decrypt the encrypted file with the private key.

Successfully decrypted an encrypted file with the corresponding private key with OpenSSL
$encrypted = 'C:\Temp\encrypted1.bin'
$output = 'C:\Temp\output.file'
$privateKey = 'C:\Temp\key6.key'

openssl rsautl -decrypt -in $encrypted -out $output -inkey $privateKey

Upon checking the output.file as specified in the $output variable we can find that it contains a message upon opening in Notepad.

Opening the decrypted file in Notepad results in a message for the player

We got lucky that it’s a text file! Since we don’t have any information regarding the file extension we would have to perform some kind of content analysis or reading the file header to determine what kind of file we are dealing with. And there we have it! There are many different ways to go about this. I just like the simplicity of OpenSSL and PowerShell.