// Checksum Tool // By Bio (C) 2012 #define _VERSION_ "3.1" #include "commonheaders.h" // Return codes #define RESULT_OK 0 #define RESULT_NOTFOUND 10 #define RESULT_READERROR 20 #define RESULT_NOTPE 30 #define RESULT_CORRUPTED 40 #define RESULT_INVALID 50 #define RESULT_NONE 100 #define DEBUG_SECTIONS 1 #define DEBUG_REALLOCS 1 int debug = 0; static void PatchResourcesDirectory(PIMAGE_RESOURCE_DIRECTORY pIRD, BYTE* pBase); static void PatchResourceEntry(PIMAGE_RESOURCE_DIRECTORY_ENTRY pIRDE, BYTE* pBase) { if ( pIRDE->DataIsDirectory ) PatchResourcesDirectory( PIMAGE_RESOURCE_DIRECTORY(pBase + pIRDE->OffsetToDirectory), pBase); } static void PatchResourcesDirectory(PIMAGE_RESOURCE_DIRECTORY pIRD, PBYTE pBase) { UINT i; pIRD->TimeDateStamp = 0; PIMAGE_RESOURCE_DIRECTORY_ENTRY pIRDE = PIMAGE_RESOURCE_DIRECTORY_ENTRY(pIRD+1); for ( i=0; i < pIRD->NumberOfNamedEntries; i++, pIRDE++ ) PatchResourceEntry(pIRDE, pBase); for ( i=0; i < pIRD->NumberOfIdEntries; i++, pIRDE++ ) PatchResourceEntry(pIRDE, pBase); } int PEChecksum( TCHAR *filename, mir_md5_byte_t digest[16] ) { HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMap; PBYTE ptr = 0; int res = RESULT_OK; DWORD filesize; DWORD hsize = 0; WORD machine = 0; DWORD sections = 0; hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); if ( hFile == INVALID_HANDLE_VALUE ) return RESULT_NOTFOUND; // check minimum and maximum size filesize = GetFileSize( hFile, &hsize ); if ( !filesize || filesize == INVALID_FILE_SIZE || hsize ) { CloseHandle( hFile ); return RESULT_INVALID; } if ( filesize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) { CloseHandle( hFile ); return RESULT_NOTPE; } hMap = CreateFileMapping( hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL ); if ( hMap ) ptr = (PBYTE)MapViewOfFile( hMap, FILE_MAP_COPY, 0, 0 ,0 ); if ( ptr ) { PIMAGE_DOS_HEADER pIDH = 0; PIMAGE_NT_HEADERS pINTH = 0; pIDH = (PIMAGE_DOS_HEADER)ptr; if ( pIDH->e_magic == IMAGE_DOS_SIGNATURE ) pINTH = (PIMAGE_NT_HEADERS)( ptr + pIDH->e_lfanew ); if ( !pINTH) res = RESULT_NOTPE; else if ((PBYTE)pINTH + sizeof(IMAGE_NT_HEADERS) >= ptr + filesize ) res = RESULT_CORRUPTED; else if ( pINTH->Signature != IMAGE_NT_SIGNATURE ) res = RESULT_NOTPE; else { machine = pINTH->FileHeader.Machine; #ifdef DEBUG_SECTIONS if ( debug ) { switch(machine) { case IMAGE_FILE_MACHINE_I386: _ftprintf( stderr, _T("Build: x86\n")); break; case IMAGE_FILE_MACHINE_AMD64: _ftprintf( stderr, _T("Build: x64\n")); break; case IMAGE_FILE_MACHINE_IA64: _ftprintf( stderr, _T("Build: IA64 :-)\n")); break; default: _ftprintf( stderr, _T("Build: unknown :-(\n")); break; } } #endif sections = pINTH->FileHeader.NumberOfSections; if ( !sections ) res = RESULT_INVALID; else { PIMAGE_DATA_DIRECTORY pIDD = 0; PIMAGE_DEBUG_DIRECTORY pDBG = 0; DWORD dbgSize = 0, dbgAddr = 0; // debug information DWORD expSize = 0, expAddr = 0; // export table DWORD resSize = 0, resAddr = 0; // resource directory DWORD relocSize = 0, relocAddr = 0; // relocation table PBYTE pRealloc = 0; DWORD offset; ULONGLONG base = 0; // try to found correct offset independent of architectures offset = pIDH->e_lfanew + pINTH->FileHeader.SizeOfOptionalHeader + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER); if (( machine == IMAGE_FILE_MACHINE_I386 ) && ( pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER32)) && ( pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC )) { pIDD = (PIMAGE_DATA_DIRECTORY)((PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory )); base = *(DWORD*)((PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS32, OptionalHeader.ImageBase )); } else if (( machine == IMAGE_FILE_MACHINE_AMD64 ) && ( pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER64)) && ( pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC )) { pIDD = (PIMAGE_DATA_DIRECTORY)((PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory )); base = *(ULONGLONG*)((PBYTE)pINTH + offsetof( IMAGE_NT_HEADERS64, OptionalHeader.ImageBase )); } else res = RESULT_CORRUPTED; #ifdef DEBUG_REALLOCS if ( debug ) _ftprintf( stderr, _T("Image base is 0x%I64x \n"), base ); #endif if ( pIDD ) { // Debugging information entry dbgAddr = pIDD[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; dbgSize = pIDD[IMAGE_DIRECTORY_ENTRY_DEBUG].Size; // Export information entry expAddr = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; expSize = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; // Resource directory resAddr = pIDD[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress; resSize = pIDD[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size; // Reallocation information entry relocAddr = pIDD[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; relocSize = pIDD[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; } // verify image integrity for (DWORD idx=0; idx < sections; idx++) { PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)( ptr + offset + idx * sizeof(IMAGE_SECTION_HEADER)); if (((PBYTE)pISH + sizeof(IMAGE_SECTION_HEADER) > ptr + filesize ) || ( pISH->PointerToRawData + pISH->SizeOfRawData > filesize )) { res = RESULT_CORRUPTED; break; } // erase timestamp if (( dbgSize >= sizeof( IMAGE_DEBUG_DIRECTORY )) && ( dbgAddr >= pISH->VirtualAddress ) && ( dbgAddr + dbgSize <= pISH->VirtualAddress + pISH->SizeOfRawData )) { DWORD shift = dbgAddr - pISH->VirtualAddress; pDBG = (PIMAGE_DEBUG_DIRECTORY)( ptr + shift + pISH->PointerToRawData ); memset(pDBG, 0, pDBG->SizeOfData); #ifdef DEBUG_SECTIONS if ( debug ) _ftprintf( stderr, _T("Found debug section entry at 0x%08X (%d), data at 0x%08X (%d)\n"), pISH->PointerToRawData + shift, dbgSize, pDBG->PointerToRawData, pDBG->SizeOfData ); #endif } // erase export timestamp if (( expSize >= sizeof( IMAGE_EXPORT_DIRECTORY )) && ( expAddr >= pISH->VirtualAddress ) && ( expAddr + expSize <= pISH->VirtualAddress + pISH->SizeOfRawData )) { DWORD shift = expAddr - pISH->VirtualAddress; PIMAGE_EXPORT_DIRECTORY pEXP = (PIMAGE_EXPORT_DIRECTORY)( ptr + shift + pISH->PointerToRawData ); pEXP->TimeDateStamp = 0; #ifdef DEBUG_SECTIONS if ( debug ) _ftprintf( stderr, _T("Found export section entry at 0x%08X\n"), pISH->PointerToRawData + shift ); #endif } // find realocation table if (( relocSize >= sizeof( IMAGE_BASE_RELOCATION )) && ( relocAddr >= pISH->VirtualAddress ) && ( relocAddr + relocSize <= pISH->VirtualAddress + pISH->SizeOfRawData )) { DWORD shift = relocAddr - pISH->VirtualAddress; pRealloc = ptr + shift + pISH->PointerToRawData; #ifdef DEBUG_SECTIONS if ( debug ) _ftprintf( stderr, _T("Found reallocation table entry at 0x%08X (%d)\n"), pISH->PointerToRawData + shift, relocSize ); #endif } } if ( res == RESULT_OK ) { mir_md5_state_t pms; mir_md5_init( &pms ); for (size_t idx=0; idx < sections; idx++) { PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)( ptr + offset + idx * sizeof(IMAGE_SECTION_HEADER)); if (((PBYTE)pISH + sizeof(IMAGE_SECTION_HEADER) > ptr + filesize ) || ( pISH->PointerToRawData + pISH->SizeOfRawData > filesize )) { res = RESULT_CORRUPTED; break; } // erase debug information if ( pDBG && pDBG->SizeOfData > 0 && pDBG->PointerToRawData >= pISH->PointerToRawData && pDBG->PointerToRawData + pDBG->SizeOfData <= pISH->PointerToRawData + pISH->SizeOfRawData ) { ZeroMemory( ptr + pDBG->PointerToRawData, pDBG->SizeOfData ); } // patch resources if ( resSize > 0 && resAddr >= pISH->VirtualAddress && resAddr + resSize <= pISH->VirtualAddress + pISH->SizeOfRawData ) { DWORD shift = resAddr - pISH->VirtualAddress + pISH->PointerToRawData; IMAGE_RESOURCE_DIRECTORY* pIRD = (IMAGE_RESOURCE_DIRECTORY*)( ptr + shift ); PatchResourcesDirectory(pIRD, ptr + shift); } // rebase to zero address if ( pRealloc ) { DWORD blocklen = relocSize; PWORD pw; DWORD type; int len; PBYTE pAddr; DWORD shift; DWORD addr; PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)pRealloc; while( pIBR ) { if (( pIBR->VirtualAddress >= pISH->VirtualAddress ) && ( pIBR->VirtualAddress < pISH->VirtualAddress + pISH->SizeOfRawData ) && ( pIBR->SizeOfBlock <= blocklen )) { shift = pIBR->VirtualAddress - pISH->VirtualAddress + pISH->PointerToRawData; len = pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION); pw = (PWORD)((PBYTE)pIBR + sizeof(IMAGE_BASE_RELOCATION)); #ifdef DEBUG_REALLOCS if ( debug ) _ftprintf( stderr, _T("Realloc block at %08X (%d)\n"), pIBR->VirtualAddress, pIBR->SizeOfBlock ); #endif while( len > 0 ) { type = *pw >> 12; addr = ( *pw & 0x0FFF ); pAddr = ptr + shift + addr; switch( type ) { case IMAGE_REL_BASED_HIGHLOW: if ( addr + pIBR->VirtualAddress + sizeof(DWORD) >= pISH->VirtualAddress + pISH->SizeOfRawData ) { len = 0; break; } #ifdef DEBUG_REALLOCS if ( debug && ( *(PDWORD)pAddr < (DWORD)base )) _ftprintf( stderr, _T("Realloc address is less than base\n")); #endif *(PDWORD)pAddr = (DWORD)((*(PDWORD)pAddr) - (DWORD)base ); break; case IMAGE_REL_BASED_DIR64: if ( addr + pIBR->VirtualAddress + sizeof(ULONGLONG) >= pISH->VirtualAddress + pISH->SizeOfRawData ) { len = 0; break; } #ifdef DEBUG_REALLOCS if ( debug && ( *(ULONGLONG*)pAddr < base )) _ftprintf( stderr, _T("Realloc address is less than base\n")); #endif *(ULONGLONG*)pAddr = (ULONGLONG)((*(ULONGLONG*)pAddr) - base ); break; case IMAGE_REL_BASED_ABSOLUTE: // stop processing len = 0; break; case IMAGE_REL_BASED_HIGH: case IMAGE_REL_BASED_LOW: case IMAGE_REL_BASED_HIGHADJ: #ifdef DEBUG_REALLOCS if ( debug ) _ftprintf( stderr, _T("Unexpected block type %d\n"), type ); #endif break; default: #ifdef DEBUG_REALLOCS if ( debug ) _ftprintf( stderr, _T("Unknown block type %d\n"), type ); #endif break; } len -= sizeof(WORD); pw++; } } blocklen -= pIBR->SizeOfBlock; if ( blocklen > sizeof(IMAGE_BASE_RELOCATION)) pIBR = (PIMAGE_BASE_RELOCATION)((PBYTE)pIBR + pIBR->SizeOfBlock ); else break; } } #ifdef DEBUG_SECTIONS if ( debug ) { int i; mir_md5_byte_t digest2[16]; mir_md5_state_t pms2; mir_md5_init( &pms2 ); mir_md5_append( &pms2, ptr + pISH->PointerToRawData, pISH->SizeOfRawData ); mir_md5_finish( &pms2, digest2 ); _ftprintf( stderr, _T("%s - %08X - %d "), pISH->Name, pISH->PointerToRawData, pISH->SizeOfRawData); for ( i = 0; i < sizeof( digest2 ) / sizeof( digest2[0] ); i++ ) _ftprintf( stderr, _T("%02X"), digest2[i] ); _ftprintf( stderr, _T("\n")); } #endif mir_md5_append( &pms, ptr + pISH->PointerToRawData, pISH->SizeOfRawData ); } if ( res == RESULT_OK ) mir_md5_finish( &pms, digest ); } } } } else res = RESULT_READERROR; if ( ptr ) UnmapViewOfFile( ptr ); if ( hMap ) CloseHandle( hMap ); CloseHandle( hFile ); return res; } TCHAR* trtrim( TCHAR *str ) { if ( str == NULL ) return NULL; TCHAR* p = _tcschr( str, 0 ); while ( --p >= str ) { switch ( *p ) { case L' ': case L'\t': case L'\n': case L'\r': *p = 0; break; default: return str; } } return str; } int process(TCHAR *filename) { int res; mir_md5_byte_t digest[16] = {0}; res = PEChecksum( filename, digest); switch(res) { case RESULT_NOTFOUND: _ftprintf( stderr, _T("'%s'... not found!\n"), filename ); break; case RESULT_READERROR: _ftprintf( stderr, _T("'%s'... read error!\n"), filename ); break; case RESULT_NOTPE: _ftprintf( stderr, _T("'%s'... not PE type!\n"), filename ); break; case RESULT_CORRUPTED: _ftprintf( stderr, _T("'%s'... corrupted!\n"), filename ); break; case RESULT_OK: { int i; _ftprintf( stdout, _T("%s "), filename ); for ( i = 0; i < sizeof( digest ) / sizeof( digest[0] ); i++ ) _ftprintf( stdout, _T("%02X"), digest[i] ); _ftprintf( stdout, _T("\n")); break; } default: break; } return res; } int _tmain( int argc, TCHAR *argv[] ) { TCHAR buf[ MAX_PATH ]; int res = 0; int cnt = 0; int i; _ftprintf( stderr, _T("* PE CHECKSUM TOOL * VERSION %s * by Bio (c) 2012\n\n"), _VERSION_ ); if ( argc > 1 ) { WIN32_FIND_DATA ffd; HANDLE hFind = INVALID_HANDLE_VALUE; for ( i = 1; i < argc; i++ ) { if ( !_tcscmp( argv[i], _T("/debug")) || !_tcscmp( argv[i], _T("/DEBUG"))) { debug = 1; break; } } _ftprintf( stderr, _T("Processing ... \n")); for ( i = 1; i < argc; i++ ) { if ( !_tcscmp( argv[i], _T("/stdin")) || !_tcscmp( argv[i], _T("/STDIN"))) { while ( _fgetts( buf, sizeof( buf ), stdin ) != NULL ) { trtrim( buf ); res = process( buf ); cnt++; } continue; } hFind = FindFirstFile( argv[i], &ffd ); while( hFind != INVALID_HANDLE_VALUE ) { if ( ! ( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )) { res = process( ffd.cFileName ); cnt++; } if ( !FindNextFile( hFind, &ffd )) break; } FindClose( hFind ); } _ftprintf( stderr, _T("%d file(s) processed.\n"), cnt ); } else { _ftprintf( stderr, _T("Usage: checksum.exe [/debug] [/stdin] [*.dll] ... [*.exe]\n")); _ftprintf( stderr, _T("Example: dir /b /s | checksum.exe /stdin > hashes.txt\n")); res = RESULT_NONE; } return res; }