Table of Contents
In this article, I will discuss a WiFi password recovery and management application that I created in WPF using Visual Studio 2019. For the creation of this application, I used MVVM architecture using Caliburn.Micro
and dependency injection with Autofac. The main purpose of this application is to manage WiFi profiles, recover passwords from WiFi profiles and match different passwords in order to connect to your own secure WiFi (in case you want to connect a new device to your WiFi but don't remember your own WiFi password and want to make an educated guess). The WiFi profile manager functionality allows you to view and manage all your WiFi profiles on your Windows 10 PC, such as deleting profiles, which can be useful because over time, if you connect to different WiFi networks, the WiFi profile list can increase and at times, you may wish to remove unnecessary WiFi profiles. The WiFi password recovery and management application is intended to recover your own WiFi password and is not intended to hack anyone's WiFi. Brute force hacking of secure WiFi networks takes a very different approach than the one used by this application as can be read here. The most recent code of this application can be found in my Github repository here, the ClickOnce deployment can be launched here.
The WiFi password recovery and management tool uses the ManagedNativeWifi library to retrieve WiFi profiles and their corresponding passwords from your Windows 10 PC. Furthermore, this library is also used to manipulate the WiFi profiles present on your PC such as increasing or decreasing their priorities. For the WiFi connection functionality, the application generates WiFi connection profiles using code from the simplewifi library. The application user can supply potential passwords in three different ways, manually by right clicking the network row in the home view, using dictionaries and regular expressions. Several print screens of the application are shown below. You can click the pictures to enlarge them.
Figure 1: Home view of WiFi password recovery and management tool.
Figure 2: Profile manager view.
The main user interface consists of four controls arranged in a DockPanel
:
- The left side control contains a navigation bar which allows you to navigate through all the available views.
- The top side control contains a header panel.
- The bottom control contains a status bar which displays application messages and busy status.
- The middle control contains the view that is selected in the navigation bar.
<Window x:Class="EasyPasswordRecoveryWiFi.Views.ShellView">
<DockPanel>
<ContentControl DockPanel.Dock="Bottom"
cal:View.Model="{Binding StatusBarBottom}"></ContentControl>
<ContentControl DockPanel.Dock="Top"
cal:View.Model="{Binding HeaderMenu}"></ContentControl>
<ContentControl DockPanel.Dock="Left"
cal:View.Model="{Binding LeftMenu}"></ContentControl>
<ContentControl x:Name="ActiveItem"></ContentControl>
</DockPanel>
</Window>
Code snippet 1: The main user interface (ShellView.xaml) consists of four controls arranged in a DockPanel
.
As mentioned above, the status bar control is docked on the bottom of the main user interface, when there are no messages and the application is not busy, the status bar control is hidden using data triggers. The status bar is updated by sending messages to it from different viewmodels using Caliburn.Micro
Event Aggregator. For those unfamiliar, an Event Aggregator is a service that provides the ability to publish an object from one entity to another in a loosely based fashion as can be read here.
To reduce the applications dependencies, I used dependency injection with Autofac. One major advantage of using dependency injection is that it increased the applications testability by allowing to inject a mocked implementation of the IWiFiService
interface into the MainController
. During startup of the application, I can easily switch between the mocked and the actual implementation of the IWiFiService
interface as shown in the code snippet below:
#if MOCK_DATA
builder.RegisterType<MockWiFiAdapter>().As<IWiFiService>();
#else
builder.RegisterType<NativeWiFiAdapter>().As<IWiFiService>();
#endif
public class MainController
{
private readonly IWiFiService _wiFiService;
public MainController(IWiFiService wifiService)
{
_wiFiService = wifiService;
}
}
Code snippet 2: Switch between the mocked and the actual implementation of the IWiFiService
interface.
The coupling between the application and the ManagedNativeWifi
library is accomplished using the adapter pattern. This approach allows to easily replace the used ManagedNativeWifi
library with other alternatives.
public class NativeWiFiAdapter : NativeWifiPlayer, IWiFiService { }
Code snippet 3: NativeWiFiAdapter
inherits from NativeWifiPlayer
(ManagedNativeWifi
) and implements the IWiFiService
interface.
For the handling of async tasks, I used a fire and forget approach that can be found here. This approach basically wraps tasks into a try catch
block. If an error occurs, it sends the exception to an error handler, in my case the error handler updates the status bar with the new error message. When a task is active, the busy flag is true
, resulting in disabling of the main user interface and thereby prohibiting the start of new tasks.
public static class TaskUtilities
{
#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
public static async void FireAndForgetSafeAsync(this Task task, IErrorHandler handler = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
{
try
{
await task;
}
catch (Exception ex)
{
handler?.HandleError(ex);
}
}
}
Code snippet 4: Task extension method used for fire and forget approach.
public interface IErrorHandler
{
void HandleError(Exception ex);
}
Code snippet 5: IErrorHandler
interface used by task extension method.
As mentioned earlier, the WiFi connection functionality generates WiFi connection profiles using the ProfileFactory
class. The ProfileFactory
class contains the CreateProfileXml
method which can be called with an Accesspoint
object (containing the authentication type, encryption type and Bss type of the targeted WiFi network) and a password as input arguments, after which it returns the connection profile. The resulting connection profile is then added to the PCs list of connection profiles, after which a connection is attempted to the targeted WiFi network. Prior to calling the CreateProfileXml
method, the password is validated using the PasswordHelper
class. Enterprise networks are not supported because they need an additional configuration step of the username and domain as can be read here.
Popup windows are displayed using the WindowManager
class of Caliburn.Micro
. Caliburn.Micro
takes care of initializing the window, setting its data context and displaying the appropriate view.
bool dialogResult = _windowManager.ShowDialog(_passwordViewModel) ?? false;
if (dialogResult)
{
password = _passwordViewModel.Password;
}
Code snippet 6: Popup windows are displayed using the WindowManager
class of Caliburn.Micro
.
The WiFi manager view of the application allows you to import and manipulate all the WiFi connection profiles present on your Windows 10 PC. By right clicking on a profile row, you can manipulate the WiFi connection profile (remove, increase or decrease its priority, export the profile or show its properties). When you click on the properties option, the properties window pops up showing profile properties including the password used by the profile to connect to its corresponding WiFi network. In order to retrieve the unencrypted password, I modified the GetProfile
method of the ManagedNativeWifi
library.
uint flags = WLAN_PROFILE_GET_PLAINTEXT_KEY;
var result = WlanGetProfile(
clientHandle,
interfaceId,
profileName,
IntPtr.Zero,
out string profileXml,
ref flags,
out uint grantedAccess);
Code snippet 7: WlanGetProfile
method retrieves a WiFi profile with its corresponding plain text password.
C:\Users>netsh wlan show profiles
C:\Users>netsh wlan show profile name="network-profile-name" key=clear
#Replace network-profile-name with your copied network name.
Code snippet 8: You can use the command prompt to display the plain text password of a WiFi profile.
As mentioned at the beginning of this article, the passwords used in the WiFi connection process can be provided using dictionaries and regular expressions. Details on how to generate password strings using regular expressions can be found in this article.
Last but not least, testing of the application was done using NUnit. I generated connection profiles for all the supported WiFi networks and validated them with reference profiles generated by a Windows 10 PC. Furthermore, I wrote test cases to validate the PasswordHelper
class (which checks if a string
meets the password rules for the targeted WiFi network).
Inversion of control (IoC) can improve testability of your application, for example, it allows you to easy mock application functionality. The use of Caliburn.Micro
framework can make programming easier, for example, the Event Aggregator allows you to easily interchange messages between different parts of your application.
- 27th September, 2019: Version 1.0.0.0 - Published the article