Protecting Thick-Client Applications From Attack (Or How To Not Have To)

Security PS Application Security
In the previous post, I discussed security testing techniques Security PS used to assess a complex thick-client application. After the assessment was complete, our client asked:
  • Is .NET less secure than other languages since these techniques are possible?
  • How do I stop attackers from manipulating my applications?
This post answers those questions and discusses best practices around securing client-server architectures.

Security PS tested the thick-client application with a variety of techniques including:
  • Reusing the application's DLLs to communicate with the server and decrypt data
  • Using a debugger to interactively modify variables and program flow
  • Disassembling, modifying, and reassembling the thick-client application to integrate it with a custom testing framework
Considering these methods, how does .NET compare to other platforms? Is .NET less secure than another choice?
.NET is not unique. In other assessments, Security PS has used the same techniques to assess Android, Java, and Native (C/C++ executables) applications. Based on my quick research some or all of the techniques work for iOS applications as well. The only differences between these platforms are the level of complexity and the toolset required. .NET is not any more or less secure than any other platform in this way.

How do you stop attackers from reusing DLLs, interactively debugging applications, or modifying applications?
You shouldn't need to in most cases. For a client-server architecture, the thick-client resides on a user's (or attacker's) computer. That environment cannot and should not be trusted to enforce security controls or protect sensitive data. Client-side security controls can be defeated or bypassed completely, and any data sent to the client can be obtained by an attacker (even if it is encrypted).

Instead, organizations should spend their time architecting and designing applications that enforce security controls on the server-side. If all the security controls are implemented on the server-side, then it does not matter whether the attacker manipulates the thick-client (or writes his or her own client application). This security best practice applies to web applications, web services, and client-server applications.

If an organization still wishes to protect client-side code from analysis and manipulation, what are the options? If you search on the Internet, you may find these choices:
  • Strong name verification (for .NET)
  • Obfuscation
  • Native compilation (for .NET)
  • Encryption
  • and more...
Each option can be used to slow down an attacker and will make analysis or modification more difficult. But, none of them prevent a skilled and determined attacker from eventually reaching their goal. Let's briefly dig in to each one.

Strong name verification enables an assembly to identify and reference the correct version of a DLL. Some Internet sources recommend using strong name verification to prevent attackers from modifying DLLs. But, according to Microsoft, it should not be used as a security control. Security PS's experience agrees with that assertion. It is trivial to bypass Strong name verification, especially with local administration privileges on a computer.

A non-technical explanation of Obfuscation is that a tool jumbles up the variable names, program structure, and or program flow before it is distributed to users. Then, when an attacker uses an interactive debugger or reflection to view the code, he or she has difficulty following and understanding the program's logic. There are many free and commercial tools to provide this protection, and it does demotivate casual attackers from performing analysis. However, there are also tools to help deobfuscate applications or track program flow.

Obfuscation tools can also make it difficult to use reverse engineering tools like ILSpy, dnSpy, and ILDasm/ILAsm. The tools can corrupt or mangle portions of the application to crash an attacker's toolsets. Additionally, encryption can be applied to strings, resources, or the code within a method. This makes it difficult to use reflection to see the original code and more complex to modify the IL code. However, eventually the code must be decrypted so it can be run, making it available to attackers.

Security PS's research into two obfuscation tools (ConfuserEx and Dotfuscator Community Edition), showed that most controls can be bypassed by a skilled attacker or worked around using WinDbg and managed code extensions. Additionally, there's a significant performance impact to using some of the Obfuscation controls.

Native compilation (i.e. Ngen) compiles a .NET DLL into processor-specific machine-code. Security PS found that native compiled .NET applications still allow an attacker to use interactive debuggers to introspect and control program. Additionally, there's no mention of using it as a security feature in Microsoft's documentation. Therefore, this technique does not provide a significant amount of protection.

There are even more techniques then I've named here. But, the important points to remember are:
  • Implement and enforce security controls on the server-side
  • Only send information to the client-side that you want the user (or attacker) to see (even encrypted)
  • Don't rely upon thick-client, browser, desktop application, etc. to provide any sort of reliable level of security
  • Only apply protection mechanisms to the executable if you absolutely have to and/or if it is nearly free (money, time, operationally, etc.)

Lessons From Attacking Complex Thick-Client Applications

Security PS performs assessments on a wide variety of software architectures and platforms, some of which cannot be tested effectively using the more standard testing tools and methods. Recently, our team performed an assessment on a more complex application architecture. In this case, a .NET thick-client communicated with a variety of server-side components using either signed SOAP messages or with custom TCP messages. These factors meant our consultants couldn't use a proxy tool to directly manipulate traffic for security testing purposes. This post discusses some of the techniques our application security team used to overcome those challenges and successfully complete the assessment.

Security PS Application Security
Security PS used three techniques to manipulate both the signed SOAP requests and the custom TCP messages:
  • Writing custom code and reusing thick-client libraries
  • Attaching a debugger to the running application and manipulating variables
  • Disassembling, modifying, and reassembling the application
Code is often written in a modular way that makes it easy to reuse existing libraries. In this assessment, Security PS wrote GUI applications that reused the thick-client's libraries to decrypt data or send data to the server. This technique involved creating a new Visual Studio Project, adding the DLLs as a reference, and then writing code that calls functions within those thick-client libraries.

Next, Security PS needed to modify a field within a signed SOAP request to test authorization controls. Our team used a debugger and breakpoints to perform this modification. For .NET thick-clients, this attack is possible after disassembling and reassembling the application with debugging enabled.

Finally, we needed a way to quickly and easily manipulate custom TCP messages to identify vulnerabilities. Use of the debugger and breakpoints was too slow. Use of a custom written testing tool meant having to understand and duplicate some complex interactions that the thick-client managed. So, Security PS chose to directly modify the thick-client to allow interactive modification of TCP messages by consultants. For that to be possible, we needed to disassemble the thick-client, modify the intermediate language code, and then reassemble it.

Using these testing techniques, Security PS identified a number of high impact vulnerabilities. After discussing the vulnerabilities with the client, two of the questions they asked were:
  • Is .NET less secure than other languages since these techniques are possible?
  • How do I stop attackers from manipulating my applications?
The next post will consider these questions more, but the primary message we communicated to our client focused on a critical best practice for secure software design: all security controls must be implemented on a trusted component in the application architecture. In this case, security controls must be implemented on the server-side rather than on the client-side. The client operates on the attacker's computer where everything can be analyzed and modified regardless of the security controls used. The architecture of the application must assume the client environment cannot be trusted.  While additional controls can be applied to increase the difficultly an attacker would have in attempting to manipulate client-side security controls, it is important to recognize that the root of this security weakness is fundamentally a design flaw that would need to be addressed to fully mitigate the risks.

Stay tuned for a follow-up on the questions brought up above.