Whilst working on an application at a client's site the other day, I noticed that access to certain code was being controlled by disabling buttons on a form.
The code in question should not be invoked under certain conditions (in this case, under certain security conditions), and so the application developers had assumed that disabling the button that invoked the code would prevent the user from invoking the functionality behind the button.
Sadly, this is not the case.
I demonstrated a very simple attack using Win32 API calls that enabled the button, and allowed the user to click on it, whether or not the application had disabled it or not.
In this article, I will be discussing the technique I used, and how you can write code that is protected from this kind of attack.
To demonstrate the attack to my client, I implemented my code in Microsoft Excel. I did this because most desktop PCs tend to have Microsoft Excel and I wanted to show my client that you do not need development tools like Visual Studio in order to orchestrate a simple attack like this.
In addition, I used a standard network user account to to show that this attack can be performed by a standard user, with the only requirement (in this scenario) being access to VBA in an Office product and the ability to execute macros in the product.
In order to enable the disabled button, the user will need to carry out the following actions:
- Find the window handle (HWND) of the button
- Enable the button
To start new Microsoft Excel workbook should be created, and the Visual Basic Editor (Tools -> Macros -> Visual Basic Editor) should be open.
A new Module must be added to the current workbook. The following code must go into the Module - it will not work unless it is in a VBA code Module (not WorkSheet or WorkBook code).
Find the HWND
In order to enable the button, the user needs to know the window handle (which is often referred to as the HWND) of the button.
If the user can use a tool such as Spy++ this is very easy, but as most users will not have it we need a way of discovering the window handle from Excel.
An easy way to do this is to produce a list of window handles that exist in the current session.
This is done by making an API call to EnumWindows (which lives in user32).
EnumWindows requires a callback to enumerate the window list, which is simple to achieve in Excel; We simply declare a method that matches the callback specification:
---
Private Function EnumWindowsProc(ByVal hwnd As Long, ByVal lparam As Long) As Long
---
This method matches the requirement, so we can now make the API call as required. To do this, we need to make the API method declaration and call it:
---
' This is the API method declaration
Private Declare Function EnumWindows Lib "user32" (ByVal callback As Long, ByVal lparam As Long) As Boolean
' This method, when invoked, will make the API call
Public Sub EnumerateAllWindows()
EnumWindows AddressOf EnumWindowsProc, 0
End Sub
---
When the EnumerateAllWindows method is invoked, the API call is made to EnumWindows, and our callback method (EnumWindowsProc) will be invoked.
In EnumWindowsProc, we get passed a window handle in hwnd. The lparam parameter serves no purpose in this example and can be ignored (it will be 0).
The value in hwnd will be the first enumerated window handle. To get the next one, the EnumWindowsProc must return a true value:
---
Private Function EnumWindowsProc(ByVal hwnd As Long, ByVal lparam As Long) As Long
Debug.Print hwnd
EnumWindowsProc = 1
End Function
---
This will then be repeatedly invoked until all the window have been enumerated, and the Immediate window will now contain a list of a couple of hundred numbers.
However, this is not very useful as far as the user is concerned, because they do not know which of these window handles are any use to them.
To help the user, the name of the window whose handle has been displayed can also be obtained.
To do this, an API call to GetWindowTextA (which also lives in user32) can be made, which has the following API method declaration:
---
Private Declare Function GetWindowTextA Lib "user32" (ByVal hwnd As Long, ByVal Text As String, ByVal maxcount As Long) As Long
---
And now in EnumWindowsProc, the following code is added before the Debug.Print statement:
---
Dim Length As Long
Dim Text As String
' Empty string to accept a name
Text = String(100, " ")
' Get the text
Length = GetWindowTextA(hwnd, Text, 100)
If Length > 0 Then
Text = Mid$(Text, 1, Length)
Else
Text = ""
End If
Debug.Print Text
---
There are some important points to note when using GetWindowTextA:
- A string (named Text) of length 100, containing spaces, was created for the string to be put into. This must be done, else the string will always be empty.
- The resulting string (from GetWindowTextA) was terminated using the length returned. If this was not done the Text would contain a string terminator (null, '\0') and then spaces up to the 100th character and would be unusable.
After including the above code, invoking the method will dump the name of the window and the window handle to the immediate window.
As event this output is not ideal, Excel can be used to dump the list to the current Sheet. A member variable, RowCounter, of type Integer is added to the Module, and then in EnumerateChildWindows method, is set this value to 1.
Now in EnumWindowsProc, the Debug.Print statements are changed to:
---
Application.ActiveSheet.Cells(RowCounter, 1).Value = Str(hwnd)
Application.ActiveSheet.Cells(RowCounter, 2).Value = Text
RowCounter = RowCounter + 1
---
When this code is now invoked, it dumps a list of window handles and window names to the current spreadsheet - much more useful when looking for something.
Now that is done, a list of the window handles in the system can be obtained. This can be tested by running Calc (calc.exe) and then invoking EnumerateAllWindows. The text "Calculator" will be visible next to a window handle on the list somewhere.
That's a good start, but if the user is trying to enable a button on a form, at the moment they will only have visiblity of the form's window handle.
To have a look at what is on the form, another API call, EnumChildWindows. EnumChildWindows will be used to enumerate all the child windows of a specified window handle.
Each button (and indeed most forms components) will have their own window handle.
The declaration for EnumChildWindows is:
---
Private Declare Function EnumChildWindows Lib "user32" (ByVal hwndParent As Long, ByVal callback As Long, ByVal lparam As Long) As Boolean
---
In the declaration hwndParent refers to the parent window handle, and then the callback and lparam parameters are the same as before.
To use EnumChildWindows, another callback method is required, which is declared in the same was as with EnumWindowsProc:
---
Private Function EnumChildWindowsProc(ByVal hwnd As Long, ByVal lparam As Long) As Long
---
The method body is the same as EnumWindowsProc, so it will also produce a list of window handles and associated text.
A method called EnumerateChildWindows is declared, which will read the number (window handle) in the currently selected cell and enumerate child windows of it:
---
Public Sub EnumerateChildWindows()
If IsNumeric(Application.Selection) Then
Dim Number As Long
Number = Application.Selection
If Number > 0 Then
Application.ActiveSheet.Cells.Clear
RowCounter = 1
EnumChildWindows Number, AddressOf EnumChildWindowsProc, 0
End If
End If
End Sub
---
If the user clicks on a window handle in the worksheet and then runs the method EnumerateChildWindows, the current list will be cleared and then the child windows of the selected window handle will be displayed.
This can be repeated if necessary to pass through multiple layers of child windows, but it is most likely that the button the user is looking for has been found the first time EnumerateChildWindows is invoked.
This can be tested this by:
- Run Calc (calc.exe)
- Invoking EnumerateAllWindows
- Selecting the window handle next to "Calculator" in the list
- Invoking EnumerateChildWindows
A list that contains all of the controls on the Calculator form should now be displayed.
The user will now be able to obtain the window handle of the button they wish to enable.
Enable The Button
In the above section, the user can obtained the window handle for a control that they wish to enable.
Now the user needs to enable the button.
This is achieved by making an API call to EnableWindow, which is declared in user32:
---
Private Declare Function EnableWindow Lib "user32" (ByVal hwnd As Long, ByVal state As Boolean) As Boolean
---
When making the API call, the window handle must be supplied in hwnd, and the enabled state of the target window in state.
A method can be declared to do this:
---
Public Sub EnableSelectedWindow()
If IsNumeric(Application.Selection) Then
Dim Number As Long
Number = Application.Selection
If Number > 0 Then
EnableWindow Number, True
End If
End If
End Sub
---
Now, the user can simply select a window handle in the list in the spreadsheet and invoke EnableSelectedWindow, and the window will become enabled.
This can be tested by:
- Run Calc (calc.exe)
- Putting Calc into Scientific mode (View -> Scientific). Note that the Ave, Sum, s, Dat, A, B, C, D, E and F buttons are disabled
- Invoking EnumerateAllWindows
- Selecting the window handle next to "Calculator" in the list
- Invoking EnumerateChildWindows
- Select the window handle next to one of the disabled button names (i.e. "Ave") - Invoking EnableSelectedWindow
The button will now be enabled. This can repeated as many times as necessary with other controls on the form.
This method will work on anything that has a window handle in Win32.
Conclusion
It is very easy to modify the state of controls using Win32.
This is because almost all standard controls in Windows have a window handle. This is a fundamental part of the operation of Microsoft Windows, and cannot be prevented.
It is important when designing and developing UI-invoked code that preventative steps are always taken to double-check that code execution is permitted.
As this article has demonstrated, it cannot be assumed that code is protected by simply disabling or hiding UI components.
A much more secure method would be to disable a control but to verify that execution is permitted inside the control's Click event or inside the code that is being executed.
Additionally, it is important to realise that it is not only the Enabled state of controls that can be influenced. In fact, using the same principal described in this article a user can change any of the following aspects of a window:
- Colour
- Position
- Size
- Visibility
Following the demonstration of the above to my client, they readily agreed that a better approach to protecting code from unwanted execution was required, and they have taken steps to prevent code access in this way.
If you would like to download a copy of the code used in this article, it can be downloaded here. This code is written (as per this article) for use in Microsoft Excel but can easily be changed to work with other Office products.
Have fun!
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment