Hacking Application Compatibility Toolkit 5.5 Logs

Disclaimer: The information in this article is for educational purposes only, provided "AS IS" with no warranties, and confers no rights. If you use it, you are actually hacking your ACT implementation, and therefore will be completely on your own, in an unsupported state. USE AT YOUR OWN RISK.

After writing the needed disclaimer, today I would like to post about my findings trying to “trick” ACT 5.5 data in order to feed applications into the DB with actual DCP deployment.

One of the features some customers request would like to see in ACT would be the ability to import their already existing software inventory data into ACT database to check for compatibility, instead of having to deploy DCP (Data Collection Packages) around to gather the “same” information they already have in the inventory.

I intentionally quoted “same”. DCP inventory actually gathers information (“indicators” as it calls in XML logs it generates) about applications from many places that traditional inventories may not look at, such as AppPaths, path environment variable or file extension handlers, to name a few.

Inside AppIDs

The problem with “injecting” inventory data into ACT is the application IDs used to check gathered information against ACT web service. As covered by Chris Jackson at https://technet.microsoft.com/en-us/magazine/2009.06.act.aspx and https://blogs.msdn.com/cjacks/archive/2010/01/06/windows-7-vender-compatibility-data-now-available-in-act-5-5.aspx, applications are tied to compatibility data with a unique Application ID, generated using the name, version, vendor, and language (NVVL) of the application.

Looking at the XML files consumed by the “ACT Log Processing Service”, they already contain those kind-of-magic AppIDs that allow ACM (Application Compatibility Manager) check for data, for example:

<Application Name="Microsoft Office Communicator 2005" ComponentType="Application" EvidenceType="Msi" […] Id="ebe7afc679a82c6560bb8b8276c262ec">

These hashes are generated by bucketizer.exe, and are key to application compatibility checks against the Web service. Bucketizer program takes the XML file generated by collector.exe, looks at NVVL data and generates the hashes.

So if I just managed to generate a properly formatted XML file similar to what Collector.exe creates, Bucketizer.exe may not notice the difference and generate the very same AppIDs in the XML that finally can be dropped into ACT logs folder to be processed by ACT Log Processing Service.

Your own Collector XML Template

So… next step in the hack will be generating our own collector XML file.
I managed to generate some sort of template for the collector XML that then we can distinguish in ACM as “imported” data, rather than “collected” data.

<?xml version="1.0" encoding="utf-16" ?>
<Collector>
   <SystemList>
      <ChassisInfo Vendor="Microsoft" AssetTag="" SerialNumber=""/>
      <OsInfo BuildNumber="0" CsdVersion="" MajorVersion="6" MinorVersion="1" PlatformId="2" ProductType="0" ServicePackMajor="0" ServicePackMinor="0" Suite="0" WindowsDirectory="" SystemDirectory=""/>
      <NetworkInfo Domain="" MachineName="_IMPORT_" UserName="" SmsGuid="" SmsHwId="">
         <NIC MacAddress="00-00-00-00-00-00"/>
      </NetworkInfo>
      <HardwareInfo>
         <MemoryInfo PageFile="0" Ram="0" Virtual="0"/>
         <ProcessorInfo Name="" Mhz="0" Architecture="x64" Family="0" Type="0" Level2CacheSize="0" Level="0" Vendor="" Caption="" ProcessorId=""/>
      </HardwareInfo>
      <CustomValues>
      </CustomValues>
   </SystemList>
   <Collection>
[ YOUR APPS INFO WILL BE HERE ]
        </Collection>
</Collector>

This will look like this in ACM, so then you can filter it in/out in case you need it:

image

image

Adding Application Data to your Collector XML file

The next step will be adding application data to your custom Collector XML file template. To do so, we need to be respectful with the expected tags and values as much as we can to avoid errors during log processing, so again I built a couple of templates for this. Here are for examples:

<StaticProperty Type="Msi" ProductName="Microsoft Office Live Meeting 2007" CompanyName="Microsoft Corporation" ProductVersion="8.0.6338" InstallDate="01/01/1900 00:00:00" Path="" FileName="" Language="0" ProductId="RandomGUID_001" PackageId="" GUID="" RNP="0" DNP="0" UniqueId="0">
   <StaticProperty Type="File" Name="file.exe" Path="C:\Program Files" Size="0" PeChecksum="0" Checksum="0" LegalCopyright="" OriginalFilename="" InternalName="" ProductName="" CompanyName="" ProductVersion="" FileVersion="" BinProductVersion="" BinFileVersion="" VerLanguage="" FileDescription="" LinkDate="01/01/1900 00:00:00" Created="01/01/1900 00:00:00" Modified="01/01/1900 00:00:00" BinaryType="32BIT" RNP="0" DNP="0" UniqueId="0" LowerCaseLongPath=""/>
</StaticProperty>

<StaticProperty Type="Msi" ProductName="Microsoft Office Communicator 2005" CompanyName="Microsoft Corporation" ProductVersion="1.0.557" InstallDate="01/01/1900 00:00:00" Path="" FileName="" Language="1033" ProductId="RandomGUID_002" PackageId="" GUID="" RNP="0" DNP="0" UniqueId="0">
   <StaticProperty Type="File" Name="file.exe" Path="C:\Program Files" Size="0" PeChecksum="0" Checksum="0" LegalCopyright="" OriginalFilename="" InternalName="" ProductName="" CompanyName="" ProductVersion="" FileVersion="" BinProductVersion="" BinFileVersion="" VerLanguage="" FileDescription="" LinkDate="01/01/1900 00:00:00" Created="01/01/1900 00:00:00" Modified="01/01/1900 00:00:00" BinaryType="32BIT" RNP="0" DNP="0" UniqueId="0" LowerCaseLongPath=""/>
</StaticProperty>

<StaticProperty Type="Msi" ProductName="Adobe Reader 9.1.3" CompanyName="Adobe Systems Incorporated" ProductVersion="9.1.3" InstallDate="01/01/1900 00:00:00" Path="" FileName="" Language="1033" ProductId="RandomGUID_003" PackageId="{F9157B99-840D-4ED4-BF61-E34C8B092756}" GUID="" RNP="0" DNP="0" UniqueId="0">
   <StaticProperty Type="File" Name="file.exe" Path="C:\Program Files" Size="0" PeChecksum="0" Checksum="0" LegalCopyright="" OriginalFilename="" InternalName="" ProductName="" CompanyName="" ProductVersion="" FileVersion="" BinProductVersion="" BinFileVersion="" VerLanguage="" FileDescription="" LinkDate="01/01/1900 00:00:00" Created="01/01/1900 00:00:00" Modified="01/01/1900 00:00:00" BinaryType="32BIT" RNP="0" DNP="0" UniqueId="0" LowerCaseLongPath=""/>
</StaticProperty>

<StaticProperty Type="AddRemoveProgram" DisplayName="Adobe Shockwave Player" CompanyName="Adobe Systems, Inc." Language="1033" ProductVersion="11.0" Path="" RegistrySubKey="" RegistryPath="HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" UninstallString="" GUID="" RNP="0" DNP="0" UniqueId="0"/>

Looking at this XML data, you can notice that all the information is just fake data for Bucketizer.exe and Log Processing Service not to complain about it, but actually only NVVL data needs to be accurate.

You also may notice that there are UniqueId values. However, they do not have to be unique for the purpose of the hack. What it has to is –just for MSI blocks– the ProductId value. Originally this is a GUID, but as you see, it doesn’t even have to be a former GUID, but some sort of different string across the applications represented in the XML.

Another interesting thing, again for the MSI blocks, is that you need to add additional evidence data for it to work. In this case I added a completely fake info about “file.exe”. As you see, this file evidence is exactly the same for the three MSI applications I added, as it does not seem to matter much to the AppID generation we are looking for.

From NVVL data to AppID

For the exactly same AppID to be generated in your log file, we need to provide the very same application name, version, vendor and language information as a real DCP will gather in the data collection process.

For example, if DCP puts “Microsoft Corp.” in the logs, and you write just “Microsoft” in your custom XML, the AppID will be completely different.

Something I have notice though is that building a custom “MSI” or “AddRemoveProgram” block type makes a different AppID, so not only NVVL data is important, but also the evidence type you are trying to build. For example, if a normal DCP gathers an application as MSI, and you put the very same NVVL data in your XML, you will get a different AppID.

Building your Collector Log

So now we know what we have to include in the collector-like log to be processed properly by bucketizer.exe and get proper AppIDs to check with the ACT Web Service. So now.. how can I easily build that XML data?

Well, that is completely up to you. My best friend in these type of cases is Excel. If you can have your software inventory to export a CSV or similar Excel-friendly data with application name, vendor, version and language, you are ready to rock!

Just use the CONCATENATE() function in Excel to concatenate the proper strings. For example, if you have appName, appVersion and appPublisher, you can build “XML MSI” column and “XML AddRemove” column to generate data to paste into your collector XML file template.

image

The formula for MSI blocks:

=CONCATENATE("<StaticProperty Type=",CHAR(34),"Msi",CHAR(34)," ProductName=",CHAR(34),A20963,CHAR(34)," CompanyName=",CHAR(34),C20963,CHAR(34)," ProductVersion=",CHAR(34),B20963,CHAR(34)," InstallDate=",CHAR(34),"01/01/1900 00:00:00",CHAR(34)," Path=",CHAR(34),"",CHAR(34)," FileName=",CHAR(34),"",CHAR(34)," Language=",CHAR(34), "0" ,CHAR(34)," ProductId=",CHAR(34),"{",RANDBETWEEN(10000000,99999999),"-",RANDBETWEEN(1000,9999),"-",RANDBETWEEN(1000,9999),"-",RANDBETWEEN(1000,9999),"-",RANDBETWEEN(100000000000,999999999999),"}",CHAR(34)," PackageId=",CHAR(34),"",CHAR(34)," GUID=",CHAR(34),"",CHAR(34)," RNP=",CHAR(34),"0",CHAR(34)," DNP=",CHAR(34),"0",CHAR(34)," UniqueId=",CHAR(34),"0",CHAR(34),">
         <StaticProperty Type=",CHAR(34),"File",CHAR(34)," Name=",CHAR(34),"file.exe",CHAR(34)," Path=",CHAR(34),"C:\Program Files",CHAR(34)," Size=",CHAR(34),"0",CHAR(34)," PeChecksum=",CHAR(34),"0",CHAR(34)," Checksum=",CHAR(34),"0",CHAR(34)," LegalCopyright=",CHAR(34),"",CHAR(34)," OriginalFilename=",CHAR(34),"",CHAR(34)," InternalName=",CHAR(34),"",CHAR(34)," ProductName=",CHAR(34),"",CHAR(34)," CompanyName=",CHAR(34),"",CHAR(34)," ProductVersion=",CHAR(34),"",CHAR(34)," FileVersion=",CHAR(34),"",CHAR(34)," BinProductVersion=",CHAR(34),"",CHAR(34)," BinFileVersion=",CHAR(34),"",CHAR(34)," VerLanguage=",CHAR(34),"",CHAR(34)," FileDescription=",CHAR(34),"",CHAR(34)," LinkDate=",CHAR(34),"01/01/1900 00:00:00",CHAR(34)," Created=",CHAR(34),"01/01/1900 00:00:00",CHAR(34)," Modified=",CHAR(34),"01/01/1900 00:00:00",CHAR(34)," BinaryType=",CHAR(34),"32BIT",CHAR(34)," RNP=",CHAR(34),"0",CHAR(34)," DNP=",CHAR(34),"0",CHAR(34)," UniqueId=",CHAR(34),"0",CHAR(34)," LowerCaseLongPath=",CHAR(34),"",CHAR(34),"/>
      </StaticProperty>")

Important: You can see appName, appVendor and appVersion cells refered in the formula. However, in my test inventory data I had no language data, so I just used literal version data in the formula (bolded “0”), what causes lots of AppIDs not to be properly generated though. You can also notice that to generate unique GUIDs I used RANDBETWEEN() formula, to generate look-a-like numeric GUIDs.

The formula for AddRemoveProgram blocks (again fixed language data):

=CONCATENATE("<StaticProperty Type=",CHAR(34),"AddRemoveProgram",CHAR(34)," DisplayName=",CHAR(34),A45892,CHAR(34)," CompanyName=",CHAR(34),C45892,CHAR(34)," Language=",CHAR(34),"1033",CHAR(34)," ProductVersion=",CHAR(34),B45892,CHAR(34)," Path=",CHAR(34),"",CHAR(34)," RegistrySubKey=",CHAR(34),"",CHAR(34)," RegistryPath=",CHAR(34),"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp",CHAR(34)," UninstallString=",CHAR(34),"",CHAR(34)," GUID=",CHAR(34),"",CHAR(34)," RNP=",CHAR(34),"0",CHAR(34)," DNP=",CHAR(34),"0",CHAR(34)," UniqueId=",CHAR(34),"0",CHAR(34),"/>")

Bucketizing your Collector Log

Once you have your custom Collector log, you need to use Bucketizer.exe to generate the XML with the right AppIDs, that you then will manually drop in your ACT Logs folder to be consumed by Log Processing Service.

bucketizer.exe /c "<custom collector log folder>" /o "<bucketizer log destination folder>" /b "<bucketizer log destination folder>" /d "<bucketizer log destination folder>" /s "_IMPORTED_"

where:
        /c    Collector log directory path.
        /o    Output directory path.
        /b    Principle (bucketized) log directory path.
        /d    Dynamic agent log directory path.

So you put your custom collector XML in a folder and bucketizer.exe processes it and dumps a bucketized XML in another folder.

An interesting Bucketizer.exe switch is /m. Running bucketizer.exe without parameters it says that it is used for “Custom Md5 attributes xml file path.  This suppresses built-in Md5
attributes.”. I have not being able to get further information about this switch though.

Caveats

There are some caveats when using this hack that may cause your custom collector log unusable or without value, though, including:

  • NVVL data has to be exactly the same as the one used to generate hashes used as AppIDs in ACT web service. Any minimal difference in any NVVL field will generate a completely different AppID that then will not find a match in ACT web service. This applies also to standard ACT usage. If you deploy a vendor application using a customized MSI package and you change how they look like in the system, DCP will collect customized information and therefore generate a different AppID that ACM will not be able to match against the web service.
  • MSI vs. AddRemoveProgram block. AppID generated by bucketizer.exe is different if you use the very same NVVL data either in an MSI or an AddRemoveProgram block. If you don’t know wheather a particular app is installed through an MSI or not, you may need to generate both blocks and therefore have more noise in ACM.
  • Expect log processing errors. Using this technique, some data generated in the custom collector log may not get properly processed. During my tests, I built an XML file with 1000 application blocks, and while bucketizer.exe did not complain, the generated log failed to get into ACM. However, it did work using just the first 500 application blocks, so you may need to find offending records.