A forum for reverse engineering, OS internals and malware analysis 

Discussion on reverse-engineering and debugging.
 #16643  by utsav.0202
 Fri Nov 16, 2012 12:12 pm
Hi
My objective is to check if the module loaded in the memory is actually that of its image available in user mode. For this I thought to compare the 'n' bytes from the entry point of the module by obtaining it from
- the image from hard disk (UM)
- the module that is loaded in the memory (KM)

I tried for first 5 bytes of ntoskrnl.exe and they were all different.

To get the Entry Point:

In User mode
I used, CreateFile -> CreateFileMapping -> MapViewOfFile.
Using the pointer from MapViewOfFile as base address I got OptionalHeader and read the Address of EntryPoint from that.
Converted this entrypoint address to offset and added to the base address.

In Kernel mode
I used ZwQuerySystemInformation(SystemModuleInformation, ...) to read all the modules and got the base address of ntoskrnl.exe. (Rest is same as in UM)

It is my understanding that these bytes should be same. Is it correct?
If yes, then where am I going wrong?
Can I see the Entry Point of a loaded module using Windbg?
 #16644  by rinn
 Fri Nov 16, 2012 2:43 pm
Hi.
utsav.0202 wrote:My objective is to check if the module loaded in the memory is actually that of its image available in user mode.
Can I see the Entry Point of a loaded module using Windbg?
If the driver have discardable sections (flag IMAGE_SCN_MEM_DISCARDABLE) then image in memory will not be the same because MmFreeDriverInitialization will unload such sections after driver initialization complete.

beep.sys as example
Code: Select all
lkd> !dh beep.sys

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
    8664 machine (X64)
       6 number of sections
4A5BCA8D time date stamp Tue Jul 14 08:00:13 2009

       0 file pointer to symbol table
       0 number of symbols
      F0 size of optional header
      22 characteristics
            Executable
            App can handle >2gb addresses

OPTIONAL HEADER VALUES
     20B magic #
    9.00 linker version
     C00 size of code
     A00 size of initialized data
       0 size of uninitialized data
      5064 address of entry point
    1000 base of code


>>skip useless info

SECTION HEADER #5
    INIT name
     362 virtual size
    5000 virtual address
     400 size of raw data
    1200 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
E2000020 flags
         Code
         Discardable
         (no align specified)
         Execute Read Write
Code: Select all
lkd> db beep.sys
fffff880`01bf8000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ..............
fffff880`01bf8010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ........@.......
fffff880`01bf8020  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
fffff880`01bf8030  00 00 00 00 00 00 00 00-00 00 00 00 e8 00 00 00  ................
fffff880`01bf8040  0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68  ........!..L.!Th
fffff880`01bf8050  69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f  is program canno
fffff880`01bf8060  74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20  t be run in DOS 
fffff880`01bf8070  6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00  mode....$.......
discarded section INIT
Code: Select all
lkd> db fffff880`01bf8000 + 5064 
fffff880`01bfd064  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd074  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd084  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd094  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd0a4  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd0b4  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd0c4  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????
fffff880`01bfd0d4  ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ??  ????????????????

Your approach won't work for all drivers. Please show your code you are using for
In Kernel mode
I used ZwQuerySystemInformation(SystemModuleInformation, ...) to read all the modules and got the base address of ntoskrnl.exe. (Rest is same as in UM)
Best Regards,
-rin
 #16650  by utsav.0202
 Fri Nov 16, 2012 3:32 pm
Code: Select all
PMODULE_LIST GetListOfModules(PNTSTATUS pnStatus)
{
	ULONG			ulNeededSize = 0;
	ULONG			*ulModuleListAddress = NULL;
	NTSTATUS		nStatus = STATUS_UNSUCCESSFUL;
	PMODULE_LIST	pModuleList = NULL;

	ZwQuerySystemInformation(SystemModuleInformation, (PVOID)&ulNeededSize, 0, &ulNeededSize);

	ulModuleListAddress = (ULONG *)ExAllocatePool(PagedPool, ulNeededSize);
	if(!ulModuleListAddress)
	{
		if(pnStatus != NULL)
			*pnStatus = STATUS_INSUFFICIENT_RESOURCES;

		return (PMODULE_LIST)ulModuleListAddress;
	}

	nStatus = ZwQuerySystemInformation(SystemModuleInformation, ulModuleListAddress, 
											ulNeededSize, NULL);
	if(nStatus != STATUS_SUCCESS)
	{
		ExFreePool((PVOID)ulModuleListAddress);

		if(pnStatus != NULL)
			*pnStatus = nStatus;

		return NULL;
	}

	pModuleList = (PMODULE_LIST)ulModuleListAddress;

	if(pnStatus != NULL)
		*pnStatus = nStatus;

	return pModuleList;
}

IMAGE_OPTIONAL_HEADER* GetOptHeader(IN PVOID pImageBase)
{
   IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *)pImageBase;
   IMAGE_NT_HEADERS *pNtHeaders;
   IMAGE_FILE_HEADER *pFileHeader;
   IMAGE_OPTIONAL_HEADER *pOptHeader;
    
   pNtHeaders = (IMAGE_NT_HEADERS*)((DWORD)pImageBase + pDosHeader->e_lfanew);
   pFileHeader = (IMAGE_FILE_HEADER *)(((LPBYTE)pImageBase) + pDosHeader->e_lfanew + sizeof(IMAGE_NT_SIGNATURE));
   pOptHeader = (IMAGE_OPTIONAL_HEADER *)(((LPBYTE)pFileHeader) + IMAGE_SIZEOF_FILE_HEADER);

   return(pOptHeader);
}

DWORD RVAToOffset(LPVOID lpBase, DWORD VirtualAddress)
{
	int i;
    PIMAGE_DOS_HEADER MyDosHeader;
    PIMAGE_NT_HEADERS MyNtHeader;
    PIMAGE_SECTION_HEADER MySectionHeader;
	int NumOfSection;
	DWORD dwSizeOfSection;

    MyDosHeader = (PIMAGE_DOS_HEADER)lpBase;
    MyNtHeader = (PIMAGE_NT_HEADERS)((long)lpBase+MyDosHeader->e_lfanew);
    MySectionHeader = (PIMAGE_SECTION_HEADER)((UINT32)MyNtHeader + 0x18 + (UINT32)MyNtHeader->FileHeader.SizeOfOptionalHeader);
    
    NumOfSection = MyNtHeader->FileHeader.NumberOfSections;
    dwSizeOfSection = sizeof(IMAGE_SECTION_HEADER);

    if (VirtualAddress < MySectionHeader->VirtualAddress)
    {
        return(VirtualAddress);
    }
    else
    {
        for(i = 0; i < NumOfSection; i++)
        {
            if (VirtualAddress >= MySectionHeader->VirtualAddress &&
				VirtualAddress < MySectionHeader->VirtualAddress + MySectionHeader->Misc.VirtualSize)
            {
                DWORD dwTmp = MySectionHeader->VirtualAddress - MySectionHeader->PointerToRawData;
                DWORD Offset = VirtualAddress - dwTmp;
                return Offset;
            }
            else
            {
                MySectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)MySectionHeader + dwSizeOfSection);
            }
        }
    }

    return 0;
}

void ShowBytes(PVOID pIn)
{
	IMAGE_OPTIONAL_HEADER* optionHeader = GetOptHeader((PVOID)pIn);
	DWORD rva_entrypoint = optionHeader->AddressOfEntryPoint;
	DWORD OffsetOfEP = 0;
	PUCHAR st = NULL;
	DWORD count = 5;

	OffsetOfEP = (DWORD)pIn + RVAToOffset((LPVOID)pIn, rva_entrypoint);
	DbgPrint("OffsetOfEP: 0X%X\n", OffsetOfEP);

	st = (unsigned char *)OffsetOfEP;
	
	while (count--)
	{
		DbgPrint("%02X ",*st);
		st++;
	}
}

Code: Select all
        MainModuleList = GetListOfModules(NULL);
	if(!MainModuleList)
		return STATUS_UNSUCCESSFUL;

	for(iCount = 0; iCount < MainModuleList->d_Modules; iCount++)
	{
		// Find The Entry For Ntoskrnl.exe
		if((_stricmp("ntoskrnl.exe", MainModuleList->a_Modules[iCount].a_bPath + MainModuleList->a_Modules[iCount].w_NameOffset) == 0) ||
		   (_stricmp("ntkrnlmp.exe", MainModuleList->a_Modules[iCount].a_bPath + MainModuleList->a_Modules[iCount].w_NameOffset) == 0) ||
		   (_stricmp("ntkrnlpa.exe", MainModuleList->a_Modules[iCount].a_bPath + MainModuleList->a_Modules[iCount].w_NameOffset) == 0) ||
		   (_stricmp("ntkrpamp.exe", MainModuleList->a_Modules[iCount].a_bPath + MainModuleList->a_Modules[iCount].w_NameOffset) == 0))
		{
			NtOsKrnl.Base = (DWORD)MainModuleList->a_Modules[iCount].p_Base;
			NtOsKrnl.End = (DWORD)MainModuleList->a_Modules[iCount].p_Base + MainModuleList->a_Modules[iCount].d_Size;

			DbgPrint("\n%s\n", MainModuleList->a_Modules[iCount].a_bPath);
			ShowBytes(MainModuleList->a_Modules[iCount].p_Base);
                        break;
		}
		
	}
 #16651  by EP_X0FF
 Fri Nov 16, 2012 4:02 pm
utsav.0202 wrote:
Code: Select all
OffsetOfEP = (DWORD)pIn + RVAToOffset((LPVOID)pIn, rva_entrypoint);
Can you explain this piece of your code? :D
 #16652  by rinn
 Fri Nov 16, 2012 4:34 pm
Hi.
utsav.0202 wrote:
Code: Select all
	OffsetOfEP = (DWORD)pIn + RVAToOffset((LPVOID)pIn, rva_entrypoint);
	DbgPrint("OffsetOfEP: 0X%X\n", OffsetOfEP);
I see basic errors in your code, maybe you did copy and paste without realizing purpose of this code. Yours RVAToOffset gives you offset in "I don't know what" because you have mixed everything in this call - image base of ntoskrnl in kernel space, entrypoint from it and file offset.
Code: Select all
#define RVATOVA(base, offset) ((PVOID)((ULONG_PTR)base + (ULONG_PTR)(offset)))
#define EP_ADDR(Image) RVATOVA(Image, (((PIMAGE_NT_HEADERS) RVATOVA(Image, ((PIMAGE_DOS_HEADER)Image)->e_lfanew))->OptionalHeader.AddressOfEntryPoint))
Don't know purpose of those multiple strcmp when ntoskrnl names are the same starting from Vista iirc, because they now all MP. Regarding ntoskrnl lookup you can take pointer from ntoskrnl - lets say one of the exported routines for example something for sure that won't be intercepted like RtlInitUnicodeString and use it address to find ntoskrnl - this won't be based on file naming.

Best Regards,
-rin
 #16661  by utsav.0202
 Sat Nov 17, 2012 8:57 am
rinn wrote: ...
Your approach won't work for all drivers. ...
Yes it is not working. I tried using windbg. What do I do now? How to get the entry point for all the modules?
 #16662  by EP_X0FF
 Sat Nov 17, 2012 9:07 am
utsav.0202 wrote:Yes it is not working. I tried using windbg. What do I do now? How to get the entry point for all the modules?
rinn wrote:If the driver have discardable sections (flag IMAGE_SCN_MEM_DISCARDABLE) then image in memory will not be the same because MmFreeDriverInitialization will unload such sections after driver initialization complete.

What do you want to do overall? Check drivers images for modifications?
 #16663  by rinn
 Sat Nov 17, 2012 2:41 pm
utsav.0202 wrote:
rinn wrote: ...
Your approach won't work for all drivers. ...
Yes it is not working. I tried using windbg. What do I do now? How to get the entry point for all the modules?
Hi.

It can be inside discarded section like in beep.sys above. You initially set your task incorrectly. If you want to do what EP_X0FF's asking, then it is obvious that you need skip several image sections, depending on their flags.

Best Regards,
-rin
 #16693  by utsav.0202
 Mon Nov 19, 2012 8:06 am
I saw a malware that infects any random driver.
It replaces the original driver with its own malware driver and when I query the file it returns the original driver file.
But the image in memory is of the malware driver.

I want to detect this malware. So I thought of matching the entry points of the file on hard disk and the module in memory.
 #16706  by rossetoecioccolato
 Mon Nov 19, 2012 11:50 pm
You like to live dangerously. There really is no safe way to do what you are trying to do online from KM. A better approach would be to (accurately) acquire the memory from the suspect system and do your analysis offline. Have you checked out the Windbg !chkimg command?

Instead of hacking ZwQuerySystemInformation try using AuxKlib, which is supported.