A managed DLL / Application will have a primary dependency on MSCOREE.dll... So, if you open the DLL in Dependency Walker you have no problems in telling a managed library from an unmanaged one.
Actually, if you open a .NET Library / Application in a binary editor, you will see that the ASCI text "BSJB" is shortly followed by the version of the Framework that the DLL / EXE needs.
So, depending on the presence of this search attribute, you can not only identify whether the library / executible is a managed library, but also the version of the Framework it uses.
Note: This is actually a hack as there seems to be no API that will help you out with your requirement - but, it is reliable (so far) and you can enforce some strong checks that nobody fools your application (though, I doubt if anyone will want to do this).
I have written a small function to determine whether a function is a .net assembly or not. It might be useful to other people as well (since I couldn't find anything on the internet about it).
Code:
bool IsNetAssembly(CString sFilename)
{
// Declare variables
bool bAssembly = false;
bool bTagFound = false;
bool bVersionFound = false;
char szBuffer[2048];
char szTagBuffer[5];
char szVersionBuffer[4];
int iBytesRead = 0;
CFile oFile;
// Set up tag
strcpy(szTagBuffer, "BSJB");
// Check if file exists
if (!PathFileExists(sFilename))
{
// No assembly
return bAssembly;
}
// Read the contents of this file
if (!oFile.Open(sFilename, CFile::modeRead | CFile::shareDenyNone, NULL))
{
// No assembly
return bAssembly;
}
// Read file
while (iBytesRead = oFile.Read(szBuffer, 2048))
{
// Check if we can find the special tag
if (!bTagFound)
{
if (ValueExistsInCharBuffer((char *)&szBuffer, iBytesRead, (char *)&szTagBuffer, 4))
{
// Tag found
bTagFound = true;
}
}
// Check if we can find the version
if (bTagFound && !bVersionFound)
{
// Loop all versions (until .net framework 9
for (int i = 1; i < 10; i++)
{
// Set up version
sprintf(szVersionBuffer, "v%d.", i);
if (ValueExistsInCharBuffer((char *)&szBuffer, iBytesRead, (char *)&szVersionBuffer, 3))
{
// Version found
bVersionFound = true;
}
}
}
// Did we find both?
if (bTagFound && bVersionFound)
{
// This is an assembly
bAssembly = true;
// Don't search any longer
break;
}
}
// Close file
oFile.Close();
// Return result
return bAssembly;
}
//=====================================================================
bool ValueExistsInCharBuffer(char * pBuffer, int iBufferSize, char * pValue, int iValueLength)
{
// Loop complete buffer
for (int i = 0; i < iBufferSize - iValueLength; i++)
{
// Check if this part contains the value
for (int j = 0; j < iValueLength; j++)
{
// Check the character
if (pBuffer[i + j] == pValue[j])
{
// Check if we compared whole item
if (j == iValueLength - 1)
{
// Yes!
return true;
}
}
else
{
// Break internal loop
break;
}
}
}
// Not found
return false;
}
That's interesting - but I am not sure if finding exported functions is relevant to this problem.
Devil is in the details I wanted to point at the PE format, from which you can easily get the dependent dlls , mscoree.dll would show up there.
The attached sample ( depends.exe ) has implemented it in a class called MODULE_DEPENDENCY_LIST. OP can simply use this class and find the dependent module name there.
Today it is - tomorrow, who knows - that is why I called it a hack.
On the otherhand - may be we can raise the same question on using a purely PE-based mechanism towards detecting .NET usage? It is theoretically possible for someone to get MSCOREE.DLL into his PE of a non-managed application...
So, may be if one wants to fool-proof his application for a reasonably competent fool, he is better off using both techniques i.e. first look the PE up, and then look the search patterns up?
Originally Posted by kirants
I mean, how did you arrive at the pattern ?
By analyzing the binary contents of many managed assemblies.
However, I must add that I analyzed the content of assemblies generated by Visual Studio only. It would be interesting to peek into a .NET assembly created by a competing compiler - though, I dare say that there aren't too many assemblies created by non-VS compilers out there.
Last edited by Siddhartha; May 24th, 2007 at 04:17 PM.
I think I found an important piece of information w.r.t PE and .NET executibles:
Originally Posted by Wiki
Microsoft's .NET Framework has extended the PE format with features which support the Common Language Runtime (an implementation of the .NET Virtual Machine). Among the additions are a CLR Header and CLR Data section. Upon loading a binary, the OS loader yields execution to the CLR via a reference in the PE/COFF IMPORT table. The CLR VM then loads CLR Header and Data sections.
So, putting all of it together, I think the pattern actually belongs to the PE, and hence the hack is foolproof.
This is interesting information. Based off of this link, I did some digging around and found this interesting information. One can get all this information again using just the PE structures.
Here is a sample code that detects if a module is a managed one and also prints out the Framework version no.
Code:
// IsManaged.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <winnt.h>
using namespace std;
DWORD GetActualAddressFromRVA(IMAGE_SECTION_HEADER* pSectionHeader,IMAGE_NT_HEADERS* pNTHeaders, DWORD dwRVA)
{
DWORD dwRet = 0;
for(int j = 0; j < pNTHeaders->FileHeader.NumberOfSections; j++,pSectionHeader++)
{
DWORD cbMaxOnDisk
= min( pSectionHeader->Misc.VirtualSize, pSectionHeader->SizeOfRawData );
DWORD startSectRVA,endSectRVA;
startSectRVA = pSectionHeader->VirtualAddress;
endSectRVA = startSectRVA + cbMaxOnDisk;
if ( (dwRVA >= startSectRVA) && (dwRVA < endSectRVA))
{
dwRet = (pSectionHeader->PointerToRawData ) + (dwRVA - startSectRVA);
break;
}
}
return dwRet;
}
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR szPath[MAX_PATH];
if(argc < 2)
{
cout << "Please specify file name";
return 0;
}
LPTSTR lpszImageName = argv[1];
HANDLE hFile = CreateFile(lpszImageName, GENERIC_READ, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
//attempt the standard paths
if(INVALID_HANDLE_VALUE == hFile)
{
//try to locate in windows directory
GetWindowsDirectory(szPath,MAX_PATH);
_tcscat(szPath,_T("\\"));
_tcscat(szPath,lpszImageName);
hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
}
if(INVALID_HANDLE_VALUE == hFile)
{
//try to locate in system directory
GetSystemDirectory(szPath,MAX_PATH);
_tcscat(szPath,_T("\\"));
_tcscat(szPath,lpszImageName);
hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
}
if(INVALID_HANDLE_VALUE != hFile)
{
//succeeded
HANDLE hOpenFileMapping = CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL);
if(hOpenFileMapping)
{
BYTE* lpBaseAddress = NULL;
lpBaseAddress = (BYTE*)MapViewOfFile(hOpenFileMapping,FILE_MAP_READ,0,0,0);
if(lpBaseAddress)
{
//having mapped the executable to our process space, now start navigating through the sections
//DOS header is straightforward. It is the topmost structure in the PE file
//i.e. the one at the lowest offset into the file
IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)lpBaseAddress;
//the only important data in the DOS header is the e_lfanew
//the e_lfanew points to the offset of the beginning of NT Headers data
IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)((BYTE*)pDOSHeader + pDOSHeader->e_lfanew);
IMAGE_SECTION_HEADER* pSectionHeader = (IMAGE_SECTION_HEADER*)((BYTE*)pNTHeaders + sizeof(IMAGE_NT_HEADERS));
//Now, start parsing
//check if it is a PE file
if(pNTHeaders->Signature == IMAGE_NT_SIGNATURE)
{
//start parsing COM table
DWORD dwNETHeaderTableLocation = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress;
if(dwNETHeaderTableLocation)
{
//import data does exist for this module
IMAGE_COR20_HEADER* pNETHeader = (IMAGE_COR20_HEADER*)((BYTE*)pDOSHeader + GetActualAddressFromRVA(pSectionHeader,pNTHeaders,dwNETHeaderTableLocation));
if(pNETHeader)
{
cout << "This is a managed component" << "\n";
cout << "Framework version dependent on is : " << pNETHeader->MajorRuntimeVersion << "." << pNETHeader->MinorRuntimeVersion;
}
else
{
cout << "This is NOT a managed component" << "\n";
}
}
else
{
cout << "This is NOT a managed component" << "\n";
}
}
else
{
cout << "Not PE file\r\n";
}
UnmapViewOfFile(lpBaseAddress);
}
CloseHandle(hOpenFileMapping);
}
CloseHandle(hFile);
}
return 0;
}
Just to let you all know: the code provided by kirants is great and faster when large assemblies are used. With my code, the whole assembly is searched until the pattern is found. This can take a long time when a large file is used which is no assembly (which means that the whole file must be read).
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.