Git Product home page Git Product logo

winchecksec's Introduction

winchecksec

CI

winchecksec performs static detection of common Windows security features.

The following security features are currently detected:

  • ASLR:
    • /DYNAMICBASE with stripped relocation entries edge-case
    • /HIGHENTROPYVA for 64-bit systems
  • Code integrity/signing:
    • /INTEGRITYCHECK
    • Authenticode-signed with a valid (trusted, active) certificate (currently unsupported on Linux)
  • DEP (a.k.a. W^X, NX)
  • Manifest isolation via (/ALLOWISOLATION)
  • Structured Exception Handling and SafeSEH support
  • Control Flow Guard and Return Flow Guard instrumentation
  • Stack cookie (/GS) support

Building

winchecksec depends on pe-parse and uthenticode, which can be installed via vcpkg:

$ vcpkg install pe-parse uthenticode

NOTE: On Windows, vcpkg defaults to 32-bit builds. If you're doing a 64-bit winchecksec build, you'll need to explicitly build the dependencies as 64-bit:

$ vcpkg install pe-parse:x64-windows uthenticode:x64-windows

Building on Linux

$ git clone https://github.com/trailofbits/winchecksec.git
$ cd winchecksec
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ cmake --build .
$ ./build/winchecksec

Building on Windows

> git clone https://github.com/trailofbits/winchecksec.git
> cd winchecksec
> mkdir build
> cd build
> cmake ..
> cmake --build . --config Release
> .\Release\winchecksec.exe C:\Windows\notepad.exe

Usage

As a command-line tool, winchecksec has two output modes: a plain-text mode for easy reading, and a JSON mode for consumption in other programs. The plain-text mode is the default; JSON output is enabled by passing --json or -j:

> .\Release\winchecksec.exe C:\Windows\notepad.exe

Dynamic Base    : "Present"
ASLR            : "Present"
High Entropy VA : "Present"
Force Integrity : "NotPresent"
Isolation       : "Present"
NX              : "Present"
SEH             : "Present"
CFG             : "NotPresent"
RFG             : "NotPresent"
SafeSEH         : "NotApplicable"
GS              : "Present"
Authenticode    : "NotPresent"
.NET            : "NotPresent"

> .\Release\winchecksec.exe -j C:\Windows\notepad.exe

[{
   "path": "C:\\Windows\\notepad.exe",
   "mitigations": {
      "dynamicBase": {
         "presence": "Present",
         "description": "Binaries with dynamic base support can be dynamically rebased, enabling ASLR."
      },
      "rfg": {
         "description": "Binaries with RFG enabled have additional return-oriented-programming protections.",
         "presence": "NotPresent"
      },
      "seh": {
         "description": "Binaries with SEH support can use structured exception handlers.",
         "presence": "Present"
      },
      // ...
   }
}]

winchecksec also provides a C++ API; documentation is hosted here.

Hacking

winchecksec is formatted with clang-format. You can use the clang-format target to auto-format it locally:

$ make clang-format

winchecksec also comes with a suite of unit tests that use pegoat as a reference for various security mitigations. To build the unit tests, pass -DBUILD_TESTS=1 to the CMake build.

Statistics for different flags across EXEs on Windows 10

Prevalence of various security features on a vanilla Windows 10 (1803) installation:

aslr authenticode cfg dynamicBase forceIntegrity gs highEntropyVA isolation nx rfg safeSEH seh
79% 37% 49% 79% 3% 65% 43% 100% 79% 6% 25% 91%

winchecksec's People

Contributors

artemdinaburg avatar dependabot-preview[bot] avatar ekilmer avatar hamled avatar haxmeadroom avatar inventednight avatar karanlyons avatar reaperhulk avatar woodruffw avatar yardenshafir avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

winchecksec's Issues

Don't report SAFESEH for x64 binaries

According to https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers?view=vs-2019:

/SAFESEH is only valid when linking for x86 targets. /SAFESEH is not supported for platforms that already have the exception handlers noted. For example, on x64 and ARM, all exception handlers are noted in the PDATA. ML64.exe has support for adding annotations that emit SEH information (XDATA and PDATA) into the image, allowing you to unwind through ml64 functions. See MASM for x64 (ml64.exe) for more information.

The SAFESEH only applies for x86 (32-bit)/PE binaries.

I am not sure if we report it for x64/PE+ binaries but if we do, we should not do that or mark it as it doesn't apply to x64 binaries.

Additionally, we should make sure the README points this out too, so we should change:

Structured Exception Handling and SafeSEH support

Into:

Structured Exception Handling and SafeSEH support (for x86 binaries, where it applies)

Use `ImageDirectoryEntryToDataEx` to find the right section header

Right now we loop over all of the IMAGE_SECTION_HEADERs in the LOADED_IMAGE to find the one that contains the RVA for the load config. This works and is fine, but Windows provides a convenience method that would be even better: ImageDirectoryEntryToDataEx. Using this would allow us to get rid of the loop + manual offset calculation and would make #15 unnecessary.

Reference: https://docs.microsoft.com/en-us/windows/desktop/api/dbghelp/nf-dbghelp-imagedirectoryentrytodataex

Python bindings

Winchecksec can also be built as a library. We should consider making Python bindings for it, like so:

from winchecksec import Checksec

checksec = Checksec("foo.exe")
checksec.gs
checksec.authenticode
# ...

Unable to build on Ubuntu

System Details:

Virtualization: vmware
Operating System: Ubuntu 18.04.4 LTS
Kernel: Linux 4.15.0-88-generic
Architecture: x86-64
cmake version 3.19.0

image

How am I building?
$ git clone https://github.com/trailofbits/winchecksec.git
$ cd winchecksec
$ mkdir build
$ cd build
$ export CMAKE_PREFIX_PATH=/home/iot/tools/vcpkg/packages/pe-parse_x64-linux/share/pe-parse/
$ export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:/home/iot/tools/vcpkg/packages/uthenticode_x64-linux/share/uthenticode

root@xyz ~/t/w/build# cmake -DCMAKE_BUILD_TYPE=Release ..
-- The C compiler identification is GNU 7.4.0
-- The CXX compiler identification is GNU 7.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/iot/tools/winchecksec/build

root@xyz ~/t/w/build# cmake --build .
Scanning dependencies of target winchecksec
[ 20%] Building CXX object CMakeFiles/winchecksec.dir/checksec.cpp.o
[ 40%] Linking CXX static library libwinchecksec.a
[ 40%] Built target winchecksec
Scanning dependencies of target winchecksec-bin
[ 60%] Building CXX object CMakeFiles/winchecksec-bin.dir/checksec.cpp.o
[ 80%] Building CXX object CMakeFiles/winchecksec-bin.dir/main.cpp.o
make[2]: *** No rule to make target '/home/iot/tools/vcpkg/packages/uthenticode_x64-linux/lib/libcrypto.a', needed by 'winchecksec'. Stop.
CMakeFiles/Makefile2:123: recipe for target 'CMakeFiles/winchecksec-bin.dir/all' failed
make[1]: *** [CMakeFiles/winchecksec-bin.dir/all] Error 2
Makefile:102: recipe for target 'all' failed
make: *** [all] Error 2

Revisit output format for mitigation presence

We currently model the presence of mitigations as true or false, making a partially arbitrary choice when a mitigation doesn't apply (e.g. ASLR on .NET, SafeSEH on x86_64). Instead, we should model presence as true, false, or "N/A".

Live process checks

There are lots of other interesting checks that we could do, although most require a live process handle:

  • Dynamic code/handle check policies
  • Disabled/restricted system calls
  • Disabled non-system fonts

CFG edge case

Split out from #1: We should attempt to detect functions that are marked __declspec(guard(nocf)) or __declspec(guard(ignore)).

Cut a 2.0 release

There hasn't been a stable winchecksec release in over a year, meaning that we haven't stabilized the pe-parse integration and other improvements made in that period.

Here's the release needlist:

  • Enable tests in the CI (#47)
  • Enforce code style with clang-format in the CI (#50)
  • Move VERSION to Checksec.cpp, to emphasize that we're versioning the entire project and not just the CLI interface
  • Actually bump VERSION in main.cpp
  • Use smolverify to verify Authenticode signatures on Linux (#39)
  • Publish build products for releases (via a GitHub action, see pegoat)

Wantlist:

  • Builds for Linux and macOS (#38, #51)

cc @ekilmer

Stack cookie check + edge case

We should also probably check whether the program was built with /GS, which inserts stack cookies.

Like /DYNAMICBASE, /GS has an interesting edge case: if the user defines a custom entry point via /ENTRY and forgets to call __security_init_cookie() within it, then the cookie value is set to a default value that makes circumvention much easier.

RFG check

Windows 10 also introduced return-flow guard (RFG) protection, which acts like a beefed up /GS by storing the return address on the (user-inaccessible) thread control stack (which is really just the fs segment) rather than just placing it behind a randomized stack cookie.

The relevant flags are in the load config (like #5 and #9), and are:

#define IMAGE_GUARD_RF_INSTRUMENTED                    0x00020000 // Module contains return flow instrumentation and metadata
#define IMAGE_GUARD_RF_ENABLE                          0x00040000 // Module requests that the OS enable return flow protection
#define IMAGE_GUARD_RF_STRICT                          0x00080000 // Module requests that the OS enable return flow protection in strict mode

Links:

ASLR edge case for .NET

.NET applications are de-facto ASLR'd, even if the PE that contains them has had its relocation entries stripped or wasn't built with /DYNAMICBASE to begin with. It'd be good to report this.

IMAGE_OPTIONAL_HEADER.DataDirectory[14] represents the CLR header, so checking for this is a good way to check whether a PE is a .NET application and therefore unconditionally ASLR'd.

Explain the output

As a wishlist item, I'd like to better understand the output of winchecksec.

Please provide a newbie page under
https://github.com/trailofbits/winchecksec/wiki
explaining what each of the fields means, a short description of each, and links to the reference documentation.

As an end-user, I'm just curious, but as a developer, I might want some expert guidance on which security features are worth the effort to enable.

Add a running process analysis mode

Not all Windows security features/mitigations can be detected statically; it'd be nice to have a -p PID or similar flag that allows a running process to be audited via GetProcessMitigationPolicy and other calls.

Out of bounds copy from loadConfigData to loadConfig.

The below code checks to see if the loadConfiData.size() is greater than loadConfig but does not check to see if it is as big as loadConfig. It then does a memcpy of loadConfiData.data() to loadConfig of size sizeof(loadConfig). In my tests, loadConfigData.size() is 64 and is sizeof(loadConfig) is 164. So 100 random bytes are being copied into loadConfig. The RFG test gives inconsistent results because it references these random bytes.

        peparse::image_load_config_64 loadConfig;
        if (loadConfigData.size() > sizeof(loadConfig)) {
            std::cerr << "Warn: large load config, probably contains undocumented "
                         "fields"
                      << "\n";
        }
        memcpy(&loadConfig, loadConfigData.data(), sizeof(loadConfig));

Remove or add a Deprecated comment to the RFG check

According to Windows 10 security: How the shadow stack will help to keep the hackers at bay published by TechRepublic in April of 2020, Return Flow Guard (RFG) was never released by Microsoft:

But when the same team looked at how they could attack their own design, they found a race condition that meant some apps weren't protected and decided not to ship it at all.

Displaying this protection may cause people like me(1) to spend a lot of unneeded time trying to figure out how to enable this technology. The above article is consistent with what I experienced.

Can the check be removed or at least have the documentation show a deprecated flag?


(1) I am Mark Eklund, an employee of Zoom Video Communications.

Remove json/pretty-printing from the public API

These APIs shouldn't be part of the Checksec class:

    json toJson() const;
    friend std::ostream& operator<<(std::ostream& os, Checksec&);

Instead, they should be private to the winchecksec CLI.

Replace ImageHlp with our own pe-parse

ImageHlp can't (correctly) parse 32-bit binaries when built into a 64-bit build of winchecksec and vice versa. It's also a PITA to use and needs to be wrapped to conform to C++ idioms.

We should really just use our own pe-parse instead. PRs appreciated.

Compile on linux

The original goal was to have it compile on linux. Should be easier with pe-parse now.

Report metadata when run with CLI

It could be useful to report/display in CLI:

  • a hash of the scanned binary/binaries
  • information if the binary os x86 or x64 (PE vs PE+)

If we don't want it by default, this could be behind some flag.

Unit tests

It'd be nice to have a basic sanity test for the JSON output, at the very least.

Broken off from #18.

SafeSEH check

We currently check whether the target is using SEH, but we should also check whether it was compiled with the (even safer) /SAFESEH. The flags for SafeSEH live in the same load structure as those for /GS, so the check for this should be pretty similar to that for #5.

CFG test should test more fields

isCFG currently checks for IMAGE_DLLCHARACTERISTICS_GUARD_CF in the image's DllCharacteristics. It should also check for IMAGE_GUARD_CF_INSTRUMENTED and IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT in the load config's GuardFlags.

Replace WinVerifyTrust with a cross-platform signature verification API

The only thing blocking winchecksec builds for non-Windows is a single API: WinVerifyTrust.

We should try to remove usage of it in favor of an open-source implementation, of which there should be a few (since Authenticode is basically PKCS#7).

Some potential starting points:

cc @haxmeadroom @ekilmer

Read image load config directly from memory

Right now we seek to and read from the right offset within the file HANDLE given to us by LOADED_IMAGE.hFile, but there's no need to do this when we already have the full file mapped into memory at LOADED_IMAGE.MappedAddress already.

SetFilePointer(loadedImage.hFile, (LONG) loadConfigOffset, NULL, FILE_BEGIN);

Document the API

The Winchecksec API (i.e., the classes and functions exposed in Checksec.h) should be documented. Documentation should be auto-build with Doxygen and auto-published to GitHub Pages.

Components that aren't intended for public use should be isolated in an impl namespace.

Incorrect Offsets in loadConfigSize_ Checks?

It appears that the fast out checks for loadConfigSize_ thresholds are hardcoded for x64 and thus result in incorrect reports for x86 binaries.

I think they’re based on Microsoft’s Load Configuration Layout table which lists sizes for both 32 bit and 64 bit binaries, and that perhaps only the right hand side of the Offset column was used by mistake.

For example isSafeSEH’s loadConfigSize_ check specifies 112, but this is correct only for x64 binaries, which of course don’t use SafeSEH at all. The correct value to check against would appear to be 72, and thus the current checks may result in false negatives.

Provided I haven’t completely misunderstood this then there’d be two options:

  1. Set all the checks to the x86 offsets as they’re the lower of the two, or
  2. Support both sets of offsets so that the fast out can still be used in 100% of cases.

Logic Error possible?

Hey in Checksec.cpp Line 115:

const bool Checksec::isDynamicBase() const  {   
    return !!(dllCharacteristics_ & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE);  
}

The double NOT is equivalent to the absence of both of them. Are you relying on a side-effect behavior or was this an oversight? This is probably more of a refactoring issue rather than a bug if there is no change in behavior.

CFG checking

Checking whether an image is CFG-instrumented should be as simple as testing the header flags for IMAGE_DLLCHARACTERISTICS_GUARD_CF.

Test Framework

Add some basic pre-commit testing that runs winchecksec on a few exe's and checks the results.

CI/CD for builds and releases

We should have a basic suite of unit tests, plus a CI/CD setup for automatically attaching build products to new releases.

GS edge case

Split out from #5: If the user defines a custom entrypoint via /ENTRY, we should verify that they call call __security_init_cookie(). If they fail to, we should mark GS as not enabled, as the stack cookie will be whatever the compile time value was.

How to compile on Linux

I'm confused about how to build this on linux. Don't know how best to fulfil the pe-parse requirement. Running "cmake --build . --target install" for that dependency does not seem to install anything.

Can anyone please help?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.