diff --git a/smda/common/labelprovider/OrdinalHelper.py b/smda/common/labelprovider/OrdinalHelper.py index 0b8ee56..2efcc78 100644 --- a/smda/common/labelprovider/OrdinalHelper.py +++ b/smda/common/labelprovider/OrdinalHelper.py @@ -1,5 +1,6 @@ class OrdinalHelper: - # TODO POC implementation, extend list. ole32.dll and mfc42.dll are candidates here + # Central mapping for DLL ordinals to function names. + # Only include mappings that are stable across Windows versions (XP, Win7, Win10+). ORDINALS = { "ws2_32.dll": { 1: "accept", @@ -35,7 +36,19 @@ class OrdinalHelper: 21: "setsockopt", 22: "shutdown", 23: "socket", - } + }, + "oleaut32.dll": { + 2: "SysAllocString", + 4: "SysAllocStringLen", + 6: "SysFreeString", + 7: "SysStringLen", + 8: "VariantInit", + 9: "VariantClear", + 10: "VariantCopy", + 144: "DllCanUnloadNow", + 149: "SysStringByteLen", + 150: "SysAllocStringByteLen", + }, } @staticmethod diff --git a/smda/intel/definitions.py b/smda/intel/definitions.py index 658a799..d87c9fb 100644 --- a/smda/intel/definitions.py +++ b/smda/intel/definitions.py @@ -138,18 +138,25 @@ ], 2: [ b"\x66\x90", # NOP2_OVERRIDE_NOP - AMD / nop - INTEL - b"\x8b\xc0", + b"\x8b\xc0", # mov eax, eax + b"\x89\xc0", # mov eax, eax b"\x8b\xff", # mov edi, edi + b"\x89\xff", # mov edi, edi b"\x8d\x00", # lea eax, dword ptr [eax] b"\x86\xc0", # xchg al, al b"\x66\x2e", # NOP2_OVERRIDE_NOP - AMD / nop - INTEL + b"\xeb\x00", # jmp $+2 (jumps to next instruction; used as padding by some MSVC versions) + b"\x8b\xc9", # mov ecx, ecx + b"\x8b\xd2", # mov edx, edx + b"\x8b\xdb", # mov ebx, ebx + b"\x8b\xf6", # mov esi, esi ], 3: [ b"\x0f\x1f\x00", # NOP3_OVERRIDE_NOP - AMD / nop - INTEL b"\x8d\x40\x00", # lea eax, dword ptr [eax] b"\x8d\x00\x00", # lea eax, dword ptr [eax] b"\x8d\x49\x00", # lea ecx, dword ptr [ecx] - b"\x8d\x64\x24", # lea esp, dword ptr [esp] + b"\x8d\x24\x24", # lea esp, dword ptr [esp] b"\x8d\x76\x00", b"\x66\x66\x90", ], @@ -157,14 +164,17 @@ b"\x0f\x1f\x40\x00", # NOP4_OVERRIDE_NOP - AMD / nop - INTEL b"\x8d\x74\x26\x00", b"\x66\x66\x66\x90", + b"\x8d\x64\x24\x00", # lea esp, [esp+0] ], 5: [ b"\x0f\x1f\x44\x00\x00", # NOP5_OVERRIDE_NOP - AMD / nop - INTEL b"\x90\x8d\x74\x26\x00", + b"\x66\x0f\x1f\x40\x00", ], 6: [ b"\x66\x0f\x1f\x44\x00\x00", # NOP6_OVERRIDE_NOP - AMD / nop - INTEL b"\x8d\xb6\x00\x00\x00\x00", + b"\x8d\xbf\x00\x00\x00\x00", # lea edi, [edi] ], 7: [ b"\x0f\x1f\x80\x00\x00\x00\x00", # NOP7_OVERRIDE_NOP - AMD / nop - INTEL, diff --git a/tests/testDefinitionsExpansion.py b/tests/testDefinitionsExpansion.py new file mode 100644 index 0000000..c0c6f6e --- /dev/null +++ b/tests/testDefinitionsExpansion.py @@ -0,0 +1,56 @@ +import unittest + +from smda.common.labelprovider.OrdinalHelper import OrdinalHelper +from smda.intel.definitions import GAP_SEQUENCES + + +class TestDefinitionsExpansion(unittest.TestCase): + def test_gap_sequences(self): + expected_sequences = [ + (2, b"\xeb\x00"), # jmp $+2 (NOP-equivalent padding) + (2, b"\x89\xc0"), # mov eax, eax + (2, b"\x8b\xc9"), # mov ecx, ecx + (2, b"\x8b\xd2"), # mov edx, edx + (2, b"\x8b\xdb"), # mov ebx, ebx + (2, b"\x8b\xf6"), # mov esi, esi + (3, b"\x8d\x24\x24"), # lea esp, [esp] (fixed from incorrect \x8d\x64\x24) + (4, b"\x8d\x64\x24\x00"), # lea esp, [esp+0] + (5, b"\x66\x0f\x1f\x40\x00"), # multi-byte NOP (operand-size prefix) + (6, b"\x8d\xbf\x00\x00\x00\x00"), # lea edi, [edi] + ] + + for length, seq in expected_sequences: + self.assertIn( + seq, GAP_SEQUENCES[length], f"Sequence {seq.hex()} of length {length} not found in GAP_SEQUENCES" + ) + + def test_no_rex_sequences_in_shared_dict(self): + # REX-prefixed (0x48) sequences must not be in the shared dict: + # in 32-bit mode 0x48 is "dec eax", making these non-NOP sequences. + rex_sequences = [ + b"\x48\x8b\xc0", + b"\x48\x89\xc0", + b"\x48\x8d\x00", + b"\x48\x8d\x40\x00", + b"\x48\x0f\x1f\x40\x00", + b"\x48\x8d\x64\x24\x00", + ] + for seq in rex_sequences: + length = len(seq) + self.assertNotIn( + seq, GAP_SEQUENCES[length], f"REX sequence {seq.hex()} should not be in shared GAP_SEQUENCES" + ) + + def test_ordinal_expansion(self): + # Test new oleaut32.dll ordinals + self.assertEqual(OrdinalHelper.resolveOrdinal("oleaut32.dll", 6), "SysFreeString") + self.assertEqual(OrdinalHelper.resolveOrdinal("oleaut32.dll", 2), "SysAllocString") + self.assertEqual(OrdinalHelper.resolveOrdinal("oleaut32.dll", 149), "SysStringByteLen") + # Case insensitivity + self.assertEqual(OrdinalHelper.resolveOrdinal("OLEAUT32.DLL", 144), "DllCanUnloadNow") + # Existing ws2_32.dll entries still resolve + self.assertEqual(OrdinalHelper.resolveOrdinal("ws2_32.dll", 23), "socket") + + +if __name__ == "__main__": + unittest.main()