VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "CPEDiffer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

Option Explicit

'PE File Differ - Compares two CPEEditor instances and reports all differences
'David's malware analysis toolkit

Private m_pe1 As CPEEditor
Private m_pe2 As CPEEditor
Private m_report As Collection 'of CDiffEntry
Private m_hasChanges As Boolean

Public Enum DiffCategory
    dcFileInfo = 0
    dcOptionalHeader = 1
    dcSections = 2
    dcImports = 3
    dcExports = 4
    dcResources = 5
    dcRelocations = 6
    dcDebugInfo = 7
    dcRichHeader = 8
End Enum

Public Function Compare(pe1 As CPEEditor, pe2 As CPEEditor) As Boolean
    On Error GoTo hell
    
    Set m_pe1 = pe1
    Set m_pe2 = pe2
    Set m_report = New Collection
    m_hasChanges = False
    
    If Not pe1.isLoaded Or Not pe2.isLoaded Then
        Err.Raise vbObjectError, , "Both PE files must be loaded"
    End If
    
    'Compare all sections
    CompareFileInfo
    CompareOptionalHeader
    CompareSections
    CompareImports
    CompareExports
    CompareResources
    CompareRelocations
    CompareDebugInfo
    
    Compare = True
    Exit Function
    
hell:
    Compare = False
End Function

Private Sub CompareFileInfo()
    AddDiffIfChanged "File Path", m_pe1.LoadedFile, m_pe2.LoadedFile, dcFileInfo
    AddDiffIfChanged "Machine Type", m_pe1.MachineType, m_pe2.MachineType, dcFileInfo
    AddDiffIfChanged "Is 64-bit", m_pe1.is64bit, m_pe2.is64bit, dcFileInfo
    AddDiffIfChanged "Is 32-bit", m_pe1.is32Bit, m_pe2.is32Bit, dcFileInfo
    AddDiffIfChanged ".NET Assembly", m_pe1.isDotNet, m_pe2.isDotNet, dcFileInfo
    
    If m_pe1.isDotNet Then
        AddDiffIfChanged ".NET Version", m_pe1.dotNetVersion, m_pe2.dotNetVersion, dcFileInfo
        AddDiffIfChanged ".NET AnyCPU", m_pe1.isDotNetAnyCpu, m_pe2.isDotNetAnyCpu, dcFileInfo
    End If
    
    AddDiffIfChanged "Compiled Date", m_pe1.CompiledDate, m_pe2.CompiledDate, dcFileInfo
    AddDiffIfChanged "Import Hash", m_pe1.impHash, m_pe2.impHash, dcFileInfo
End Sub

Private Sub CompareOptionalHeader()
    Dim oh1 As COptionalHeader, oh2 As COptionalHeader
    Set oh1 = m_pe1.OptionalHeader
    Set oh2 = m_pe2.OptionalHeader
    
    'ULong64 comparisons need special handling
    AddDiffIfChanged "EntryPoint", oh1.EntryPoint.ToString, oh2.EntryPoint.ToString, dcOptionalHeader
    AddDiffIfChanged "ImageBase", oh1.ImageBase.ToString, oh2.ImageBase.ToString, dcOptionalHeader
    AddDiffIfChanged "SizeOfStackReserve", oh1.SizeOfStackReserve.ToString, oh2.SizeOfStackReserve.ToString, dcOptionalHeader
    AddDiffIfChanged "SizeOfStackCommit", oh1.SizeOfStackCommit.ToString, oh2.SizeOfStackCommit.ToString, dcOptionalHeader
    AddDiffIfChanged "SizeOfHeapReserve", oh1.SizeOfHeapReserve.ToString, oh2.SizeOfHeapReserve.ToString, dcOptionalHeader
    AddDiffIfChanged "SizeOfHeapCommit", oh1.SizeOfHeapCommit.ToString, oh2.SizeOfHeapCommit.ToString, dcOptionalHeader
    
    AddDiffIfChanged "Magic", Hex(oh1.Magic), Hex(oh2.Magic), dcOptionalHeader
    AddDiffIfChanged "Linker Version", oh1.LinkerVersion, oh2.LinkerVersion, dcOptionalHeader
    AddDiffIfChanged "SizeOfCode", oh1.SizeOfCode, oh2.SizeOfCode, dcOptionalHeader
    AddDiffIfChanged "SizeOfInitializedData", oh1.SizeOfInitializedData, oh2.SizeOfInitializedData, dcOptionalHeader
    AddDiffIfChanged "SizeOfUninitializedData", oh1.SizeOfUninitializedData, oh2.SizeOfUninitializedData, dcOptionalHeader
    AddDiffIfChanged "BaseOfCode", Hex(oh1.BaseOfCode), Hex(oh2.BaseOfCode), dcOptionalHeader
    
    If Not m_pe1.is64bit Then 'BaseOfData only in 32-bit
        AddDiffIfChanged "BaseOfData", Hex(oh1.BaseOfData), Hex(oh2.BaseOfData), dcOptionalHeader
    End If
    
    AddDiffIfChanged "SectionAlignment", Hex(oh1.SectionAlignment), Hex(oh2.SectionAlignment), dcOptionalHeader
    AddDiffIfChanged "FileAlignment", Hex(oh1.FileAlignment), Hex(oh2.FileAlignment), dcOptionalHeader
    AddDiffIfChanged "OS Version", oh1.OSVersion, oh2.OSVersion, dcOptionalHeader
    AddDiffIfChanged "Image Version", oh1.ImageVersion, oh2.ImageVersion, dcOptionalHeader
    AddDiffIfChanged "Subsystem Version", oh1.SubSysVersion, oh2.SubSysVersion, dcOptionalHeader
    AddDiffIfChanged "Win32VersionValue", oh1.Win32VersionValue, oh2.Win32VersionValue, dcOptionalHeader
    AddDiffIfChanged "SizeOfImage", Hex(oh1.SizeOfImage), Hex(oh2.SizeOfImage), dcOptionalHeader
    AddDiffIfChanged "SizeOfHeaders", Hex(oh1.SizeOfHeaders), Hex(oh2.SizeOfHeaders), dcOptionalHeader
    AddDiffIfChanged "Checksum", Hex(oh1.checkSum), Hex(oh2.checkSum), dcOptionalHeader
    AddDiffIfChanged "Subsystem", oh1.Subsystem, oh2.Subsystem, dcOptionalHeader
    AddDiffIfChanged "DllCharacteristics", Hex(oh1.DllCharacteristics), Hex(oh2.DllCharacteristics), dcOptionalHeader
    AddDiffIfChanged "LoaderFlags", Hex(oh1.LoaderFlags), Hex(oh2.LoaderFlags), dcOptionalHeader
    
    'Security flags
    AddDiffIfChanged "DEP Enabled", oh1.DEP, oh2.DEP, dcOptionalHeader
    AddDiffIfChanged "ASLR Enabled", oh1.ASLR, oh2.ASLR, dcOptionalHeader
    AddDiffIfChanged "Force Integrity", oh1.FORCE_INTEGRITY, oh2.FORCE_INTEGRITY, dcOptionalHeader
    
    'Data directories
    Dim i As Long
    For i = 0 To 15 'IMAGE_NUMBEROF_DIRECTORY_ENTRIES
        Dim ddName As String
        ddName = GetDataDirectoryName(i)
        
        Dim va1 As Long, va2 As Long, sz1 As Long, sz2 As Long
        va1 = oh1.ddVirtualAddress(i)
        va2 = oh2.ddVirtualAddress(i)
        sz1 = oh1.ddSize(i)
        sz2 = oh2.ddSize(i)
        
        If va1 <> va2 Or sz1 <> sz2 Then
            AddDiff ddName & " RVA", Hex(va1), Hex(va2), dcOptionalHeader
            AddDiff ddName & " Size", sz1, sz2, dcOptionalHeader
        End If
    Next i
End Sub

Private Function GetDataDirectoryName(index As Long) As String
    Select Case index
        Case 0: GetDataDirectoryName = "Export Table"
        Case 1: GetDataDirectoryName = "Import Table"
        Case 2: GetDataDirectoryName = "Resource Table"
        Case 3: GetDataDirectoryName = "Exception Table"
        Case 4: GetDataDirectoryName = "Certificate Table"
        Case 5: GetDataDirectoryName = "Base Relocation Table"
        Case 6: GetDataDirectoryName = "Debug Directory"
        Case 7: GetDataDirectoryName = "Architecture"
        Case 8: GetDataDirectoryName = "Global Ptr"
        Case 9: GetDataDirectoryName = "TLS Table"
        Case 10: GetDataDirectoryName = "Load Config Table"
        Case 11: GetDataDirectoryName = "Bound Import"
        Case 12: GetDataDirectoryName = "Import Address Table"
        Case 13: GetDataDirectoryName = "Delay Import Descriptor"
        Case 14: GetDataDirectoryName = "CLR Runtime Header"
        Case 15: GetDataDirectoryName = "Reserved"
        Case Else: GetDataDirectoryName = "Unknown[" & index & "]"
    End Select
End Function

Private Sub CompareSections()
    Dim i As Long
    Dim maxSections As Long
    Dim s1 As CSection, s2 As CSection
    
    maxSections = IIf(m_pe1.Sections.Count > m_pe2.Sections.Count, m_pe1.Sections.Count, m_pe2.Sections.Count)
    
    If m_pe1.Sections.Count <> m_pe2.Sections.Count Then
        AddDiff "Section Count", m_pe1.Sections.Count, m_pe2.Sections.Count, dcSections
    End If
    
    For i = 1 To maxSections
        Dim sectName1 As String, sectName2 As String
        
        If i <= m_pe1.Sections.Count Then
            Set s1 = m_pe1.Sections(i)
            sectName1 = s1.nameSec
        Else
            sectName1 = "<missing>"
        End If
        
        If i <= m_pe2.Sections.Count Then
            Set s2 = m_pe2.Sections(i)
            sectName2 = s2.nameSec
        Else
            sectName2 = "<missing>"
        End If
        
        If sectName1 <> sectName2 Then
            AddDiff "Section[" & i & "] Name", sectName1, sectName2, dcSections
            GoTo nexti 'Different sections, skip field comparison
        End If
        
        If sectName1 = "<missing>" Then GoTo nexti
        
        'Compare section fields
        Dim prefix As String
        prefix = sectName1 & "."
        
        AddDiffIfChanged prefix & "VirtualSize", Hex(s1.VirtualSize), Hex(s2.VirtualSize), dcSections
        AddDiffIfChanged prefix & "VirtualAddress", Hex(s1.VirtualAddress), Hex(s2.VirtualAddress), dcSections
        AddDiffIfChanged prefix & "SizeOfRawData", Hex(s1.SizeOfRawData), Hex(s2.SizeOfRawData), dcSections
        AddDiffIfChanged prefix & "PointerToRawData", Hex(s1.PointerToRawData), Hex(s2.PointerToRawData), dcSections
        AddDiffIfChanged prefix & "Characteristics", Hex(s1.Characteristics), Hex(s2.Characteristics), dcSections
        
        'Compare relocations/linenumbers if present
        If s1.PointerToRelocations <> 0 Or s2.PointerToRelocations <> 0 Then
            AddDiffIfChanged prefix & "PointerToRelocations", Hex(s1.PointerToRelocations), Hex(s2.PointerToRelocations), dcSections
            AddDiffIfChanged prefix & "NumberOfRelocations", s1.NumberOfRelocations, s2.NumberOfRelocations, dcSections
        End If
nexti:
    Next i
End Sub

Private Sub CompareImports()
    'Enhanced import comparison using modImportAnalysis
    On Error Resume Next
    
    Dim imp1 As CLoadImports, imp2 As CLoadImports
    Set imp1 = m_pe1.Imports
    Set imp2 = m_pe2.Imports
    
    If imp1 Is Nothing And imp2 Is Nothing Then Exit Sub
    If imp1 Is Nothing Or imp2 Is Nothing Then
        AddDiff "Imports", IIf(imp1 Is Nothing, "<none>", "Present"), IIf(imp2 Is Nothing, "<none>", "Present"), dcImports
        Exit Sub
    End If
    
    'Use enhanced import analysis
    Dim report As CImportChangeReport
    Set report = AnalyzeImportChanges(m_pe1, m_pe2)
    
    'Add imphash comparison
    AddDiffIfChanged "Import Hash", report.imphash1, report.imphash2, dcImports
    
    'Add DLL changes
    Dim i As Long
    For i = 1 To report.dllsAdded.Count
        AddDiff "Import DLL", "<new>", report.dllsAdded(i), dcImports
    Next
    
    For i = 1 To report.dllsRemoved.Count
        AddDiff "Import DLL", report.dllsRemoved(i), "<removed>", dcImports
    Next
    
    'Add function changes grouped by DLL
    Dim fc As CImportFunctionChange
    For i = 1 To report.functionsAdded.Count
        Set fc = report.functionsAdded(i)
        AddDiff "Import: " & fc.dllName, "<new>", fc.functionName, dcImports
    Next
    
    For i = 1 To report.functionsRemoved.Count
        Set fc = report.functionsRemoved(i)
        AddDiff "Import: " & fc.dllName, fc.functionName, "<removed>", dcImports
    Next
End Sub

Private Sub CompareExports()
    On Error Resume Next
    
    Dim exp1 As CLoadExports, exp2 As CLoadExports
    Set exp1 = m_pe1.Exports
    Set exp2 = m_pe2.Exports
    
    If exp1 Is Nothing And exp2 Is Nothing Then Exit Sub
    If exp1 Is Nothing Or exp2 Is Nothing Then
        AddDiff "Exports", IIf(exp1 Is Nothing, "<none>", "Present"), IIf(exp2 Is Nothing, "<none>", "Present"), dcExports
        Exit Sub
    End If
    
    If exp1.functions.Count <> exp2.functions.Count Then
        AddDiff "Export Count", exp1.functions.Count, exp2.functions.Count, dcExports
    End If
    
    'Build lookup tables by name
    Dim exps1 As Object, exps2 As Object
    Set exps1 = CreateObject("Scripting.Dictionary")
    Set exps2 = CreateObject("Scripting.Dictionary")
    
    Dim exp As CExport, i As Long
    
    For i = 1 To exp1.functions.Count
        Set exp = exp1.functions(i)
        If Len(exp.functionName) > 0 Then
            exps1(LCase(exp.functionName)) = exp
        End If
    Next
    
    For i = 1 To exp2.functions.Count
        Set exp = exp2.functions(i)
        If Len(exp.functionName) > 0 Then
            exps2(LCase(exp.functionName)) = exp
        End If
    Next
    
    'Find added/removed exports
    Dim key
    For Each key In exps1.Keys
        If Not exps2.Exists(key) Then
            AddDiff "Export", key, "<removed>", dcExports
        End If
    Next
    
    For Each key In exps2.Keys
        If Not exps1.Exists(key) Then
            AddDiff "Export", "<new>", key, dcExports
        End If
    Next
    
    'Compare common exports
    For Each key In exps1.Keys
        If exps2.Exists(key) Then
            Dim e1 As CExport, e2 As CExport
            Set e1 = exps1(key)
            Set e2 = exps2(key)
            
            If e1.FunctionAddress <> e2.FunctionAddress Then
                AddDiff "Export '" & e1.functionName & "' RVA", Hex(e1.FunctionAddress), Hex(e2.FunctionAddress), dcExports
            End If
            
            If e1.FunctionOrdial <> e2.FunctionOrdial Then
                AddDiff "Export '" & e1.functionName & "' Ordinal", e1.FunctionOrdial, e2.FunctionOrdial, dcExports
            End If
        End If
    Next
End Sub

Private Sub CompareResources()
    On Error Resume Next
    
    Dim res1 As CResources, res2 As CResources
    Set res1 = m_pe1.Resources
    Set res2 = m_pe2.Resources
    
    If res1 Is Nothing And res2 Is Nothing Then Exit Sub
    If res1 Is Nothing Or res2 Is Nothing Then
        AddDiff "Resources", IIf(res1 Is Nothing, "<none>", "Present"), IIf(res2 Is Nothing, "<none>", "Present"), dcResources
        Exit Sub
    End If
    
    'Get resource name lists
    Dim names1 As Collection, names2 As Collection
    Set names1 = res1.dumpNames(True) 'includeSize = True
    Set names2 = res2.dumpNames(True)
    
    'Build lookup tables
    Dim dict1 As Object, dict2 As Object
    Set dict1 = CreateObject("Scripting.Dictionary")
    Set dict2 = CreateObject("Scripting.Dictionary")
    
    Dim i As Long, resName As String
    For i = 1 To names1.Count
        resName = CStr(names1(i))
        dict1(resName) = True
    Next
    
    For i = 1 To names2.Count
        resName = CStr(names2(i))
        dict2(resName) = True
    Next
    
    'Find added/removed resources
    Dim key
    For Each key In dict1.Keys
        If Not dict2.Exists(key) Then
            AddDiff "Resource", key, "<removed>", dcResources
        End If
    Next
    
    For Each key In dict2.Keys
        If Not dict1.Exists(key) Then
            AddDiff "Resource", "<new>", key, dcResources
        End If
    Next
    
    'Note: Could also compare resource data with hashes if needed
End Sub

Private Sub CompareRelocations()
    On Error Resume Next
    
    Dim rel1 As CRelocations, rel2 As CRelocations
    Set rel1 = m_pe1.Relocations
    Set rel2 = m_pe2.Relocations
    
    If rel1 Is Nothing And rel2 Is Nothing Then Exit Sub
    If rel1 Is Nothing Or rel2 Is Nothing Then
        AddDiff "Relocations", IIf(rel1 Is Nothing, "<none>", "Present"), IIf(rel2 Is Nothing, "<none>", "Present"), dcRelocations
        Exit Sub
    End If
    
    If rel1.Tables.Count <> rel2.Tables.Count Then
        AddDiff "Relocation Table Count", rel1.Tables.Count, rel2.Tables.Count, dcRelocations
    End If
    
    Dim i As Long
    For i = 1 To IIf(rel1.Tables.Count > rel2.Tables.Count, rel1.Tables.Count, rel2.Tables.Count)
        If i > rel1.Tables.Count Then
            Dim t2 As CRelocTable
            Set t2 = rel2.Tables(i)
            AddDiff "Relocation Table[" & i & "]", "<missing>", "RVA=" & Hex(t2.TableRVA) & " Size=" & t2.TableSize, dcRelocations
        ElseIf i > rel2.Tables.Count Then
            Dim t1 As CRelocTable
            Set t1 = rel1.Tables(i)
            AddDiff "Relocation Table[" & i & "]", "RVA=" & Hex(t1.TableRVA) & " Size=" & t1.TableSize, "<missing>", dcRelocations
        Else
            Set t1 = rel1.Tables(i)
            Set t2 = rel2.Tables(i)
            
            If t1.TableRVA <> t2.TableRVA Then
                AddDiff "Relocation Table[" & i & "] RVA", Hex(t1.TableRVA), Hex(t2.TableRVA), dcRelocations
            End If
            
            If t1.NumEntries <> t2.NumEntries Then
                AddDiff "Relocation Table[" & i & "] Entries", t1.NumEntries, t2.NumEntries, dcRelocations
            End If
        End If
    Next i
End Sub

Private Sub CompareDebugInfo()
    On Error Resume Next
    
    Dim dbg1 As CDebugDir, dbg2 As CDebugDir
    Set dbg1 = m_pe1.DebugDirectory
    Set dbg2 = m_pe2.DebugDirectory
    
    If dbg1 Is Nothing And dbg2 Is Nothing Then Exit Sub
    If dbg1 Is Nothing Or dbg2 Is Nothing Then
        AddDiff "Debug Directory", IIf(dbg1 Is Nothing, "<none>", "Present"), IIf(dbg2 Is Nothing, "<none>", "Present"), dcDebugInfo
        Exit Sub
    End If
    
    AddDiffIfChanged "Debug Timestamp", dbg1.TimeStampStr, dbg2.TimeStampStr, dcDebugInfo
    AddDiffIfChanged "Debug Type", dbg1.dbgtype, dbg2.dbgtype, dcDebugInfo
    AddDiffIfChanged "Debug Size", dbg1.sizeofData, dbg2.sizeofData, dcDebugInfo
    AddDiffIfChanged "Debug Address", Hex(dbg1.adrRawData), Hex(dbg2.adrRawData), dcDebugInfo
    AddDiffIfChanged "PDB Path", dbg1.pdbPath, dbg2.pdbPath, dcDebugInfo
End Sub

Private Sub AddDiffIfChanged(fieldName As String, val1, val2, category As DiffCategory)
    If val1 <> val2 Then
        AddDiff fieldName, val1, val2, category
    End If
End Sub

Private Sub AddDiff(fieldName As String, val1, val2, category As DiffCategory)
    Dim d As New CDiffEntry
    d.fieldName = fieldName
    d.value1 = CStr(val1)
    d.value2 = CStr(val2)
    d.category = category
    m_report.Add d
    m_hasChanges = True
End Sub

Public Property Get HasChanges() As Boolean
    HasChanges = m_hasChanges
End Property

Public Property Get DiffCount() As Long
    DiffCount = m_report.Count
End Property

Public Function GetTextReport(Optional includeUnchanged As Boolean = False) As String
    Dim sb As String
    Dim i As Long
    Dim d As CDiffEntry
    Dim lastCat As DiffCategory
    lastCat = -1
    
    sb = String(80, "=") & vbCrLf
    sb = sb & "PE DIFF REPORT" & vbCrLf
    sb = sb & String(80, "=") & vbCrLf
    sb = sb & "File 1: " & m_pe1.LoadedFile & vbCrLf
    sb = sb & "File 2: " & m_pe2.LoadedFile & vbCrLf
    sb = sb & String(80, "=") & vbCrLf
    sb = sb & vbCrLf
    
    If m_report.Count = 0 Then
        sb = sb & "No differences found." & vbCrLf
        GetTextReport = sb
        Exit Function
    End If
    
    For i = 1 To m_report.Count
        Set d = m_report(i)
        
        'Category header
        If d.category <> lastCat Then
            If i > 1 Then sb = sb & vbCrLf
            sb = sb & "[" & GetCategoryName(d.category) & "]" & vbCrLf
            sb = sb & String(80, "-") & vbCrLf
            lastCat = d.category
        End If
        
        'Difference entry
        sb = sb & PadRight(d.fieldName, 35) & " : "
        sb = sb & PadRight(d.value1, 20) & " => "
        sb = sb & d.value2 & vbCrLf
    Next i
    
    sb = sb & vbCrLf & String(80, "=") & vbCrLf
    sb = sb & "Total differences: " & m_report.Count & vbCrLf
    
    GetTextReport = sb
End Function

Public Sub FillListView(lv As Object)
    'Fills a ListView with diff results
    'Expected columns: Field | Value1 | Value2 | Category
    On Error Resume Next
    
    lv.ListItems.Clear
    
    Dim i As Long
    Dim d As CDiffEntry
    Dim li As Object
    
    For i = 1 To m_report.Count
        Set d = m_report(i)
        Set li = lv.ListItems.Add(, , d.fieldName)
        li.SubItems(1) = d.value1
        li.SubItems(2) = d.value2
        li.SubItems(3) = GetCategoryName(d.category)
    Next i
End Sub

Private Function GetCategoryName(cat As DiffCategory) As String
    Select Case cat
        Case dcFileInfo: GetCategoryName = "File Info"
        Case dcOptionalHeader: GetCategoryName = "Optional Header"
        Case dcSections: GetCategoryName = "Sections"
        Case dcImports: GetCategoryName = "Imports"
        Case dcExports: GetCategoryName = "Exports"
        Case dcResources: GetCategoryName = "Resources"
        Case dcRelocations: GetCategoryName = "Relocations"
        Case dcDebugInfo: GetCategoryName = "Debug Info"
        Case dcRichHeader: GetCategoryName = "Rich Header"
        Case Else: GetCategoryName = "Unknown"
    End Select
End Function

Private Function PadRight(s As String, length As Long) As String
    If Len(s) >= length Then
        PadRight = s
    Else
        PadRight = s & Space(length - Len(s))
    End If
End Function

Public Function GetDifferences() As Collection
    'Returns collection of CDiffEntry objects
    Set GetDifferences = m_report
End Function

Public Function GetDifferencesByCategory(cat As DiffCategory) As Collection
    Dim result As New Collection
    Dim i As Long
    Dim d As CDiffEntry
    
    For i = 1 To m_report.Count
        Set d = m_report(i)
        If d.category = cat Then
            result.Add d
        End If
    Next i
    
    Set GetDifferencesByCategory = result
End Function
