SourceForge.net Logo

crackXL - Microsoft Excel COM password cracker

This program was originally published on neworder and sourceforge. You can check the projects page or the comments section on neworder for more discussion. There you will find the implementation in .NET by stand__sure and the bruteforce version by rattle

When working with Office through COM you have several choices. You can either use .NET or Visual Basic, the result being the same for both which is having COM nicely packaged and wrapped up with all the extra overhead that comes with these two programming platforms. Another method is to code your client in C/C++ where you can use either MFC or attempt to make good use of the ole header on your own. This method should result in faster code but you don't get the benefits of the abstraction provided by .NET or MFC. Because I enjoy learning and hackerish exploration I chose to code without MFC or .NET.

There are several steps when accessing an Excel file via COM. The first step is to interface with COM and get the class identifier (CLSID) for Excel. This identifier located in the registry and is mapped to the programmatic identifier (PROGID) excel.application and also to the server Excel.exe.

HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Excel.Application\CLSID

Once you have the CLSID you use it, interfacing with the IUnknown interface to create an instance of the Excel Application. Next you need to call the QueryInterface method to determine the dispatch ID (DISPID) of the method we want to call. This ID is a unique number that identifies each method within the class. Once you have this Identifier you can use Invoke to call the method. When you invoke a function with IDispatch::Invoke you need to pass the correct parameter and be ready to receive it's returned value. You will see this pattern used throughout the code.

//filename: crackXL.cpp
//usage is: crackXL <excel file> <dictionary file> where the dictionary
//is a simple word list

#include <windows.h>
#include <ole2.h>
#include <fstream>

using namespace std;

int main(int argc, char* argv[])
{
	// global variables

	ifstream in_fp;
	const char* szFileName = argv[1];
	const char* szDictFile = argv[2];
	wchar_t wszFileName[MAX_PATH+1];
	DISPPARAMS dpNoArgs = {NULL, NULL, 0, 0};
	VARIANT vResult;	// used to hold variant results
	OLECHAR FAR* szFunction;
	IDispatch* pDispApp;
	IDispatch* pDispXlBooks;
	DISPID dispid_Books; 	//Documents property of application object
	DISPID dispid_Quit;
	BSTR bstrFileName = ::SysAllocString(OLESTR("filename"));
	BSTR bstrPassWord = ::SysAllocString(OLESTR("pass"));

	//get arguements and check files

	if ( argc != 3 ){
		printf("usage %s  \n", argv[0]);
		exit(1);
	}

	in_fp.open(szFileName, ios::in);
	if (!in_fp || strlen(szFileName) > MAX_PATH){
		printf("error loading %s\n", szFileName);
		exit(1);
	}
	in_fp.close();
	in_fp.open(szDictFile, ios::in);
	if (!in_fp){
		printf("error loading %s\n", szDictFile);
		exit(1);
	}

	//convert file name to bstr for use with COM
	MultiByteToWideChar(CP_ACP, 0, szFileName, strlen(szFileName)+1,
			wszFileName, sizeof(wszFileName)/sizeof(wszFileName[0]));

	bstrFileName = ::SysAllocString(wszFileName);
	
	// COM work starts here

	//Initialize COM
	::CoInitialize(NULL);
	printf("initializing COM...\n");

	//get the CLSID for the Excel Application Object
	CLSID clsid;
	CLSIDFromProgID(L"Excel.Application", &clsid);

	//get a pointer to the Objects IUnknown interface and Create
	//an instance of the Excel Application.

	IUnknown* pUnk;
	HRESULT hr = ::CoCreateInstance( clsid, NULL,
			CLSCTX_SERVER, IID_IUnknown, (void**) &pUnk);

	printf("instance of Excel Created...\n");

	/*
	   All COM interfaces inherit from the IUnknown interface, this interface
	   has a total of three functions: QueryInterface(), AddRef() and Release()
	   QueryInterface() is used to retrieve a pointer to the IDispatch Interface
	   and the AddRef() and Release() functions are used to maintain the 
	   reference count (a count of all the number of interface pointers that are
	   being used by clients).
	*/

	hr = pUnk->QueryInterface(IID_IDispatch, (void**)&pDispApp);

	/*
	   use ::GetIDsOfNames on pDisApp to get the DISPID
	   The DISPID (dispatch identifier) uniquely identifies each method within
	   a function.  You then use ::Invoke to call that method or property.
	   when IDispatch::Invoke is called it also passes on parameters for the 
	   method and recieves it's returned value.  This does most of the work.
	*/

	szFunction = OLESTR("Workbooks");
	hr = pDispApp->GetIDsOfNames (IID_NULL, &szFunction, 1,
			LOCALE_USER_DEFAULT, &dispid_Books);

	hr = pDispApp->Invoke (dispid_Books, IID_NULL,
			LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
			&dpNoArgs, &vResult, NULL, NULL);

	pDispXlBooks = vResult.pdispVal;

	//DISPPARAMS for Open method

	DISPID dispid_Open;
	VARIANT vArgsOpen[5];
	DISPPARAMS dpOpen;
	dpOpen.cArgs = 5;
	dpOpen.cNamedArgs = 0;
	dpOpen.rgvarg = vArgsOpen;
	char entry[25];
	wchar_t wszPassWord[25];

	printf("attempting dictionary attack...\n");

	while(in_fp.getline(entry, sizeof(entry))){

		//again convert to bstr
		MultiByteToWideChar(CP_ACP, 0, entry, -1,
				wszPassWord, sizeof(wszPassWord)/sizeof(wszPassWord[0]));

		bstrPassWord = ::SysAllocString(wszPassWord);
		
		vArgsOpen[4].vt = VT_BSTR;
		vArgsOpen[4].bstrVal = bstrFileName;		//Filename
		vArgsOpen[3].vt = VT_ERROR;
		vArgsOpen[3].scode = DISP_E_PARAMNOTFOUND;	//Updatelinks - ommitted
		vArgsOpen[2].vt = VT_BOOL;
		vArgsOpen[2].scode = TRUE;					//Open ReadOnly
		vArgsOpen[1].vt = VT_ERROR;
		vArgsOpen[1].scode = DISP_E_PARAMNOTFOUND;  //file format - ommitted
		vArgsOpen[0].vt = VT_BSTR;
		vArgsOpen[0].bstrVal = bstrPassWord;		//the file password;

		//Invoke the Open method
		szFunction = OLESTR("Open");
		hr = pDispXlBooks->GetIDsOfNames(IID_NULL, &szFunction, 1, 
			                             LOCALE_USER_DEFAULT, &dispid_Open);
		hr = pDispXlBooks->Invoke(dispid_Open, IID_NULL, 
			                      LOCALE_USER_DEFAULT, DISPATCH_METHOD, 
								  &dpOpen, NULL, NULL, NULL);
		printf(".");
		if (!FAILED(hr))
			break;
	}

	if (!FAILED(hr)) // the password was in the dictionary file
		printf("\npassword is %s\n", entry);
	else
		printf("\nthe password was not found\n");
	
	//Invoke the Quit method

    szFunction = OLESTR("Quit");
    hr = pDispApp->GetIDsOfNames(IID_NULL, &szFunction, 1, 
                                 LOCALE_USER_DEFAULT, &dispid_Quit);
    hr = pDispApp->Invoke (dispid_Quit, IID_NULL, 
                           LOCALE_USER_DEFAULT, DISPATCH_METHOD,
                           &dpNoArgs, NULL, NULL, NULL);

	//Clean-up

	::SysFreeString(bstrFileName);
	::SysFreeString(bstrPassWord);

	pDispXlBooks->Release();
	pDispApp->Release();
	pUnk->Release();

	::CoUninitialize();

	return 0;
}

Download the latest version of crackXL from sourceforge here.

feedback? comment on livejournal.