0

I was reading this writeup of how CylancePROTECT can be exploited. It has a Windows service which is implemented using .NET.

It seems that for code written in .NET, it is trivial to locate important variables and find their location in memory.

The exploit requires access to a context running as NT AUTHORITY\SYSTEM, so its impact is likely limited. The writer was able to use WriteProcessMemory to change a key variable that effectively disabled monitoring.

A possible protection I found was Microsoft protected processes, but some digging revealed that Microsoft only allows vendors to use it if they are members of the Microsoft Virus Initiative or Virus Information Alliance - which essentially means only large vendors are welcome.

Now, I know you can't protect against everything, but I was wondering what kind of defences were possible to:

  1. Prevent the discover of important variables
  2. Prevent finding the location in memory of variables that have been found
  3. Prevent another process from writing to your processes memory
2

You can't implement this effectively.

You're running into what's generally referred to as the "DRM problem" - code running on a general purpose computing platform can always be subverted or modified by the owner of that system. Your code is no longer your code when it runs on a user system.

Designing these kinds of anti-dumping, anti-injection, anti-modification, anti-debugging, anti-cracking, anti-reversing, anti-emulation, anti-whatever-else-you-feel-like systems is an ongoing field of research and commercial effort. So far nobody has been able to produce an uncrackable protection. There have been cases where breaking protection was particularly difficult, but all schemes have eventually been broken somehow, even in the case where specialist hardware was used (see: HDCP, PS3 LV0 keys)

Discussion of what can be done to make things difficult for someone attempting to subvert your program could easily span a whole book, but here's a rough outline of tricks on a Windows platform:

  • Anti-analysis tricks such as:
    • Compression or encryption of code (packing)
    • Virtual machines (implement a custom processor emulator, write code for that)
    • Control-flow obfuscation
    • String and data obfuscation
    • Fake code
    • Runtime code modification
  • Anti-debug tricks such as:
    • Early execution of code (pre-EntryPoint) using TLS callbacks.
    • Execution time comparisons using rdtsc or GetTickCount.
    • Direct debugger checks (PEB flags, IsDebuggerPresent, etc)
    • Exploiting vulnerabilities in common debuggers (e.g. OllyDbg OutputDebugString bug)
    • Detection of common debuggers and tools via process names, window names, window classes, mutexes and other objects, drivers, presence of files
    • Injecting debugger checks into other processes (e.g. explorer) to avoid them being identified.
    • Endless others...
  • Anti-modification tricks such as:
    • Runtime checksums of critical code and data
    • Shadow copies of critical data for later verification
    • Encryption of memory using CryptProtectMemory API.
    • Moving critical operations to the kernel (using a driver) to make it harder for a user to tamper with code.
  • VM detection (again whole books can be written on this)
  • Hooking common APIs in all processes (either in usermode using DLL injection, or in the kernel) to deny or otherwise tamper with calls which attempt to access your process.
  • Anti-tracing tricks using software breakpoints and exception handlers.
  • General security hardening such as enabling NX + ASLR + ForceIntegrity, plus mitigation policies which may make it harder for software to access the process or inject DLLs.

Even if you implemented every feature on this list, it can all be bypassed with some work.

It's important to note that .NET itself doesn't necessarily have any real weakness to being memory edited that native (e.g. C, C++) executables don't. What makes .NET easier to attack in general is that the intermediate language (IL) instructions and metadata contained within can be more easily reverse engineered into something human-understandable, whereas native executables have more options for obfuscation and anti-reversing tricks and don't contain such metadata. The article you linked simply shows a way to use reflection to get access to the variable rather than a more native approach which would work just as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.