nabiy writes:
Ok, I've not slept for the last day or so... really and it all started with that damn crack mdb thread.
Access mdb files aren't really a problem for me. Whenever I've got the need I use Access PassView. So I guess it wasn't really that thread's fault and i can't blame floyd1123 (the original poster). Nope, I think I can place the blame squarely on JLP. He is the one who brought up Excel and so he has to take the blame for my overdose on coffee.
Looking at a protected Excel file the only thing I could immediately think of was the utility strings to pull data from the file. This would only work if the file was not encrypted however and it probably doesn't even recover everything. There are some very good closed source and commercial commercial tools out there that do the job nicely, but I could not find a 'free' tool available. People without money lose passwords... no free tools... how do they do that anyways? I decided to find out.
Now, when working with Office from another application you have a few 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 (yeah, I?m not too zappy with either). 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 the other platforms.
When faced with this decision I chose to code my client in C++ without MFC in order to avoid the extra overhead. The problem with this though is that there is very little 'real' documentation available. I mean, don't get me wrong, there is plenty of documentation on using COM in your applications but this documentation is usually geared toward Visual Basic or .NET. This is probably a benefit in disguise though because it has made my adventure that much more fun! When you throw off all the abstraction you get down to the nitty gritty and into some real hackerish exploration.
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.
So anyways, here is the code, littered with comments...
Now there is still quite a lot to do and quite a lot to learn. Some things I could add are better error checking and real exception handling. I?d also like to extend this to Word files and perhaps add a bruteforce method (it would have to be fast and efficient). Updates will be posted on the our projects page and on sourceforge (you can get he current release there as well).
Before I forget, I have to thank stand__sure for providing me the initial direction and the friendly warning of what I was getting into. The lack of documentation on programming COM in C++ was frustrating (as he predicted) and I?ve found myself loading up libraries in the OLE / COM Viewer and decomposing Visual Basic documentation. It?s been great fun though so thank you JLP for mentioning that Excel file.
nabiy . [url]http://nabiy.sdf1.org[/url]
---
implementation in .NET by stand__sure:
---
Tools and Links of Interest:
How to Automate Excel from C++ Without Using MFC or #import
Automating Microsoft Office 97 and Microsoft Office 2000
Compiler COM Support
Excel Encryption Information
Eric's Complete Guide To BSTR Semantics
Office 2003 Update: Redistributable Primary Interop Assemblies
---
Access mdb files aren't really a problem for me. Whenever I've got the need I use Access PassView. So I guess it wasn't really that thread's fault and i can't blame floyd1123 (the original poster). Nope, I think I can place the blame squarely on JLP. He is the one who brought up Excel and so he has to take the blame for my overdose on coffee.
Looking at a protected Excel file the only thing I could immediately think of was the utility strings to pull data from the file. This would only work if the file was not encrypted however and it probably doesn't even recover everything. There are some very good closed source and commercial commercial tools out there that do the job nicely, but I could not find a 'free' tool available. People without money lose passwords... no free tools... how do they do that anyways? I decided to find out.
Now, when working with Office from another application you have a few 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 (yeah, I?m not too zappy with either). 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 the other platforms.
When faced with this decision I chose to code my client in C++ without MFC in order to avoid the extra overhead. The problem with this though is that there is very little 'real' documentation available. I mean, don't get me wrong, there is plenty of documentation on using COM in your applications but this documentation is usually geared toward Visual Basic or .NET. This is probably a benefit in disguise though because it has made my adventure that much more fun! When you throw off all the abstraction you get down to the nitty gritty and into some real hackerish exploration.
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.
So anyways, here is the code, littered with comments...
1 //filename: crackXL.cpp
2
3 //usage is: crackXLwhere the dictionary
4 //is a simple word list
5
6 #include
7 #include
8 #include
9
10 using namespace std;
11
12 int main(int argc, char* argv[])
13 {
14 // global variables
15
16 ifstream in_fp;
17 const char* szFileName = argv[1];
18 const char* szDictFile = argv[2];
19 wchar_t wszFileName[MAX_PATH+1];
20 DISPPARAMS dpNoArgs = {NULL, NULL, 0, 0};
21 VARIANT vResult; // used to hold variant results
22 OLECHAR FAR* szFunction;
23 IDispatch* pDispApp;
24 IDispatch* pDispXlBooks;
25 DISPID dispid_Books; //Documents property of application object
26 DISPID dispid_Quit;
27 BSTR bstrFileName = ::SysAllocString(OLESTR("filename"));
28 BSTR bstrPassWord = ::SysAllocString(OLESTR("pass"));
29
30 //get arguements and check files
31
32 if ( argc != 3 ){
33 printf("usage %s\n", argv[0]);
34 exit(1);
35 }
36
37 in_fp.open(szFileName, ios::in);
38 if (!in_fp || strlen(szFileName) > MAX_PATH){
39 printf("error loading %s\n", szFileName);
40 exit(1);
41 }
42 in_fp.close();
43 in_fp.open(szDictFile, ios::in);
44 if (!in_fp){
45 printf("error loading %s\n", szDictFile);
46 exit(1);
47 }
48
49 //convert file name to bstr for use with COM
50 MultiByteToWideChar(CP_ACP, 0, szFileName, strlen(szFileName)+1,
51 wszFileName, sizeof(wszFileName)/sizeof(wszFileName[0]));
52
53 bstrFileName = ::SysAllocString(wszFileName);
54
55 // COM work starts here
56
57 //Initialize COM
58 ::CoInitialize(NULL);
59 printf("initializing COM...\n");
60
61 //get the CLSID for the Excel Application Object
62 CLSID clsid;
63 CLSIDFromProgID(L"Excel.Application", &clsid);
64
65 //get a pointer to the Objects IUnknown interface and Create
66 //an instance of the Excel Application.
67
68 IUnknown* pUnk;
69 HRESULT hr = ::CoCreateInstance( clsid, NULL,
70 CLSCTX_SERVER, IID_IUnknown, (void**) &pUnk);
71
72 printf("instance of Excel Created...\n");
73
74 /*
75 All COM interfaces inherit from the IUnknown interface, this interface
76 has a total of three functions: QueryInterface(), AddRef() and Release()
77 QueryInterface() is used to retrieve a pointer to the IDispatch Interface
78 and the AddRef() and Release() functions are used to maintain the
79 reference count (a count of all the number of interface pointers that are
80 being used by clients).
81 */
82
83 hr = pUnk->QueryInterface(IID_IDispatch, (void**)&pDispApp);
84
85 /*
86 use ::GetIDsOfNames on pDisApp to get the DISPID
87 The DISPID (dispatch identifier) uniquely identifies each method within
88 a function. You then use ::Invoke to call that method or property.
89 when IDispatch::Invoke is called it also passes on parameters for the
90 method and recieves it's returned value. This does most of the work.
91 */
92
93 szFunction = OLESTR("Workbooks");
94 hr = pDispApp->GetIDsOfNames (IID_NULL, &szFunction, 1,
95 LOCALE_USER_DEFAULT, &dispid_Books);
96
97 hr = pDispApp->Invoke (dispid_Books, IID_NULL,
98 LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET,
99 &dpNoArgs, &vResult, NULL, NULL);
100
101 pDispXlBooks = vResult.pdispVal;
102
103 //DISPPARAMS for Open method
104
105 DISPID dispid_Open;
106 VARIANT vArgsOpen[5];
107 DISPPARAMS dpOpen;
108 dpOpen.cArgs = 5;
109 dpOpen.cNamedArgs = 0;
110 dpOpen.rgvarg = vArgsOpen;
111 char entry[25];
112 wchar_t wszPassWord[25];
113
114 printf("attempting dictionary attack...\n");
115
116 while(in_fp.getline(entry, sizeof(entry))){
117
118 //again convert to bstr
119 MultiByteToWideChar(CP_ACP, 0, entry, -1,
120 wszPassWord, sizeof(wszPassWord)/sizeof(wszPassWord[0]));
121
122 bstrPassWord = ::SysAllocString(wszPassWord);
123
124 vArgsOpen[4].vt = VT_BSTR;
125 vArgsOpen[4].bstrVal = bstrFileName; //Filename
126 vArgsOpen[3].vt = VT_ERROR;
127 vArgsOpen[3].scode = DISP_E_PARAMNOTFOUND; //Updatelinks - ommitted
128 vArgsOpen[2].vt = VT_BOOL;
129 vArgsOpen[2].scode = TRUE; //Open ReadOnly
130 vArgsOpen[1].vt = VT_ERROR;
131 vArgsOpen[1].scode = DISP_E_PARAMNOTFOUND; //file format - ommitted
132 vArgsOpen[0].vt = VT_BSTR;
133 vArgsOpen[0].bstrVal = bstrPassWord; //the file password;
134
135 //Invoke the Open method
136 szFunction = OLESTR("Open");
137 hr = pDispXlBooks->GetIDsOfNames(IID_NULL, &szFunction, 1,
138 LOCALE_USER_DEFAULT, &dispid_Open);
139 hr = pDispXlBooks->Invoke(dispid_Open, IID_NULL,
140 LOCALE_USER_DEFAULT, DISPATCH_METHOD,
141 &dpOpen, NULL, NULL, NULL);
142 printf(".");
143 if (!FAILED(hr))
144 break;
145 }
146
147 if (!FAILED(hr)) // the password was in the dictionary file
148 printf("\npassword is %s\n", entry);
149 else
150 printf("\nthe password was not found\n");
151
152 //Invoke the Quit method
153
154 szFunction = OLESTR("Quit");
155 hr = pDispApp->GetIDsOfNames(IID_NULL, &szFunction, 1,
156 LOCALE_USER_DEFAULT, &dispid_Quit);
157 hr = pDispApp->Invoke (dispid_Quit, IID_NULL,
158 LOCALE_USER_DEFAULT, DISPATCH_METHOD,
159 &dpNoArgs, NULL, NULL, NULL);
160
161 //Clean-up
162
163 ::SysFreeString(bstrFileName);
164 ::SysFreeString(bstrPassWord);
165
166 pDispXlBooks->Release();
167 pDispApp->Release();
168 pUnk->Release();
169
170 ::CoUninitialize();
171
172 return 0;
173 }
Now there is still quite a lot to do and quite a lot to learn. Some things I could add are better error checking and real exception handling. I?d also like to extend this to Word files and perhaps add a bruteforce method (it would have to be fast and efficient). Updates will be posted on the our projects page and on sourceforge (you can get he current release there as well).
Before I forget, I have to thank stand__sure for providing me the initial direction and the friendly warning of what I was getting into. The lack of documentation on programming COM in C++ was frustrating (as he predicted) and I?ve found myself loading up libraries in the OLE / COM Viewer and decomposing Visual Basic documentation. It?s been great fun though so thank you JLP for mentioning that Excel file.
nabiy . [url]http://nabiy.sdf1.org[/url]
---
implementation in .NET by stand__sure:
' you need the Office Primary Interop Assemblies (PIAs) for this application
'to compile & run (redistributable is free) add a project reference
'to the Microsoft Office Excel 11 Object Library
Imports Microsoft.Office.Interop
Imports System.Runtime.InteropServices
Imports System.IO
Module Module1
Sub Main()
If (My.Application.CommandLineArgs.Count < 2) Then
'TODO say something helpful to the user
Exit Sub
End If
Dim NameOfExcelFile As String = My.Application.CommandLineArgs(0)
Dim NameOfDictionaryFile As String = My.Application.CommandLineArgs(1)
Dim fstream As FileStream
Try
fstream = New FileStream(NameOfDictionaryFile, FileMode.Open)
Catch notfoundex As FileNotFoundException
Console.WriteLine("dictionary file not found")
Exit Sub
End Try
If (Not File.Exists(NameOfExcelFile)) Then
Console.WriteLine("excel file not found")
Exit Sub
End If
Dim txtreader As TextReader = New StreamReader(fstream)
Dim password As String = txtreader.ReadLine()
Dim app As New Excel.Application()
Dim workbook As Excel.Workbook
Do While (password IsNot Nothing)
Try
workbook = app.Workbooks.Open(NameOfExcelFile, Password:=password)
Catch ex As COMException
If (ex.ErrorCode = -2146827284) Then 'this is the password hresult
Console.Out.WriteLine("Password {0} failed.", password)
Else
Console.WriteLine(ex.Message)
End If
password = txtreader.ReadLine()
Continue Do
End Try
'if we get here...
Console.Out.WriteLine("Password {0} succeeded.", password)
workbook.Close()
app.Quit()
Exit Do
Loop
End Sub
End Module
---
Tools and Links of Interest:
How to Automate Excel from C++ Without Using MFC or #import
Automating Microsoft Office 97 and Microsoft Office 2000
Compiler COM Support
Excel Encryption Information
Eric's Complete Guide To BSTR Semantics
Office 2003 Update: Redistributable Primary Interop Assemblies
---