kevoreilly / capemon

capemon: CAPE's monitor
GNU General Public License v3.0
100 stars 49 forks source link

Improving IsPeImageRaw() #74

Closed raashidbhatt closed 9 months ago

raashidbhatt commented 9 months ago
IsPeImageRaw()

The central pointer, where the image is mapped or the raw data is located in memory, is determined. The logic inside IsPeImageRaw() works fine but can fail in many scenarios.

extern "C" int LooksLikeSectionBoundary(DWORD_PTR Buffer)
{
    if (
        (*(DWORD*)((BYTE*)Buffer - 4) == 0) &&           // The end of the previous section has zeros
        (*(DWORD*)((BYTE*)Buffer) != 0)                   // The beginning of the section is non-zero
    )
    {
        // If sectionHeader.VirtualAddress == sectionHeader.SizeOfRawData, the above check would fail
    }
}

For example, in the case of Guloader (7911e39e07995e3afb97ac0e5a4608c10c2e278bef29924ecc3924edfcc495ca), after RtlDecompress, the buffer is mapped into memory. AllocationHandler adds the allocation to the monitor list: AllocationHandler: Adding allocation to tracked region list: 0x00410000, size: 0x5000. Thus, during Free or processExit when the list is processed, a dump is mistakenly taken as a Virtual Section boundary. Specifically, in the case of Guloader, in the first section of the PE file, there is a code buffer placed at the PointerToRawData of the first section (which is strange).

The fix:

I have written a small function that checks the presence of relocations in the buffer containing a valid PE. Based on the validity of the relocations, it determines if the buffer is virtually mapped or raw mapped. This function can be called inside IsPeImageRaw() before the boundary checks as a precedence.


        if (peFile->hasRelocationDirectory() )
        {
            if (CheckRelocsTest((char*)Buffer, peFile) )// Virtual mapped image 
                return 0;
            else    
                return 1;

        }
// Test based on validity of relocation table , if mapped images is as per virtual boundary then relocations will be parsed successfully 
extern "C" int CheckRelocsTest(char *pMappedImage, PeParser *peFile)
{

    PIMAGE_BASE_RELOCATION RelocTable = NULL;
    unsigned int iRelocVaddr = 0;

    IMAGE_DOS_HEADER DosHdr = {0};
    IMAGE_FILE_HEADER FileHdr = {0};
    IMAGE_OPTIONAL_HEADER OptHdr = {0};
    PIMAGE_BASE_RELOCATION pRelocEntry = NULL;
    unsigned int RelocBlockSize = 0;
    unsigned int *FixUp = 0;

    int i = 0;

    if (peFile->hasRelocationDirectory())
    {

        pRelocEntry = (PIMAGE_BASE_RELOCATION)((unsigned int)peFile->getCurrentNtHeader()->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + (unsigned int)pMappedImage);
        DebugOutput("CheckRelocs: pRelocEntry->VirtualAddress %d.\n", pRelocEntry->VirtualAddress);

        if (pRelocEntry->VirtualAddress >= peFile->getCurrentNtHeader()->OptionalHeader.SizeOfImage ||  pRelocEntry->VirtualAddress == 0) 
        {

            return 0;
        }

        while (pRelocEntry->VirtualAddress)
        {

            iRelocVaddr = pRelocEntry->VirtualAddress;

            RelocBlockSize = (pRelocEntry->SizeOfBlock - 8) / 2;
            pRelocEntry = (PIMAGE_BASE_RELOCATION) ((unsigned char *)pRelocEntry + 8); // TypeOffset

            while (RelocBlockSize--)
            {
                if (*(unsigned short *)pRelocEntry == 0x3000)
                {
                    pRelocEntry = (PIMAGE_BASE_RELOCATION)  ((unsigned char *)pRelocEntry + 2);
                    continue;
                }

                FixUp = (unsigned int *)(*(unsigned short *)pRelocEntry & 0x0fff);
                DebugOutput("\nFixup value before adding = %x, Page base = %x", FixUp, iRelocVaddr);
                FixUp = (unsigned int *)((unsigned int)FixUp + ((unsigned int)pMappedImage + (unsigned int)iRelocVaddr));

                DebugOutput("\nOriginal Address = 0x%p", *FixUp);

                pRelocEntry = (PIMAGE_BASE_RELOCATION)  ((unsigned char *)pRelocEntry + 2);

            }

            if (pRelocEntry >= (PIMAGE_BASE_RELOCATION)peFile->getCurrentNtHeader()->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress)
            {
                break;  
            }   

            if (pRelocEntry->VirtualAddress >= peFile->getSectionHeaderBasedFileSize()) 

            {

                return 0;
            }
        }

    }

    return 1; // Virtual Image
}
raashidbhatt commented 9 months ago

test case Invalid payload Dump 471204_27a1f7b4a5d468897a5e4ec62c0214da5023892c441439abb6fe3acfe28a9761 in file 7911e39e07995e3afb97ac0e5a4608c10c2e278bef29924ecc3924edfcc495ca

raashidbhatt commented 9 months ago

7911e39e07995e3afb97ac0e5a4608c10c2e278bef29924ecc3924edfcc495ca

kevoreilly commented 9 months ago

Issue confirmed - thanks for the hashes. As you rightly point out, in this sample it is strange that code is left at the pointer to raw data. I think it's likely just the 'manual mapping' performed by the malware to load the image is doing a shoddy job by leaving behind a chunk of raw code in the virtual image.

In terms of improving IsPeImageRaw() to better handle this, it was apparent that by testing the 'raw' boundary first the function handles this scenario badly. However, by swapping the order of precedence and testing the 'virtual' boundary first, the function will correctly assess this image as virtual and it is dumped correctly with hash 16b184148a8bc29cd9324b9d1e03c241da68b4f66d9de849baa3b90a4d2cd62c.

The suggested method of checking relocations is in my view too computationally expensive a solution to deal with such a rare scenario as this particular sample and its quirk, particularly when swapping boundary precedence solves the issue. I have therefore opted for this solution in https://github.com/kevoreilly/capemon/commit/69fdd64e44f7fa03fafc20adcd00563f25d52fd8.

Thank you again for your feedback and for helping improve capemon.

raashidbhatt commented 9 months ago

Thank you !