Skip to content
Permalink
Browse files

treewide: Use uthenticode for Authenticode checks (#70)

* treewide: Use uthenticode for Authenticode checks

Closes #39.
  • Loading branch information
woodruffw committed May 18, 2020
1 parent 04ad1b7 commit 0079c01776b06471bf6f5356fd25472766169b48
Showing with 105 additions and 116 deletions.
  1. +11 −0 .cmake-format.json
  2. +11 −4 .github/workflows/ci.yml
  3. +4 −10 CMakeLists.txt
  4. +14 −29 Checksec.cc
  5. +20 −2 Checksec.h
  6. +11 −3 Makefile
  7. +34 −16 README.md
  8. +0 −52 authenticode.cc
@@ -0,0 +1,11 @@
{
"format": {
"line_width": 100,
"tab_size": 2,
"separate_ctrl_name_with_space": true,
"separate_fn_name_with_space": false,
"dangle_parens": true,
"dangle_align": "prefix",
"line_ending": "unix"
}
}
@@ -10,16 +10,23 @@ on:
- cron: '0 12 * * *'

env:
vcpkg-commit: 422fb5df87f6ae2d01a6ccdca7211a54c0e88fa1
vcpkg-install: pe-parse
vcpkg-commit: 8a583e80da3b72141105da9003175679af2fcb92
vcpkg-install: pe-parse uthenticode

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: lint
run: make lint

- name: deps
run: |
sudo apt-get update
sudo apt-get install -y clang-format-9
sudo pip3 install cmake-format
- name: format
run: make format

winchecksec:
strategy:
@@ -3,24 +3,18 @@ project(winchecksec)
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)

find_package(pe-parse REQUIRED)
find_package(uthenticode REQUIRED)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(winchecksec Checksec.cc Checksec.h)
target_compile_definitions(winchecksec PRIVATE _WINCHECKSEC_EXPORT=1)
target_link_libraries(winchecksec PRIVATE pe-parse::pe-parser-library)
if (MSVC)
target_sources(winchecksec PRIVATE authenticode.cc)
endif ()
target_link_libraries(winchecksec PRIVATE pe-parse::pe-parser-library uthenticode::uthenticode)

# Dumb hack to build an executable with the same name.
add_executable(winchecksec-bin Checksec.cc main.cc)
target_compile_definitions(winchecksec-bin PRIVATE _WINCHECKSEC_STANDALONE=1)
target_link_libraries(winchecksec-bin PRIVATE pe-parse::pe-parser-library)
if (MSVC)
target_sources(winchecksec-bin PRIVATE authenticode.cc)
target_link_libraries(winchecksec-bin PRIVATE wintrust)
endif ()
target_link_libraries(winchecksec-bin PRIVATE pe-parse::pe-parser-library uthenticode::uthenticode)

# Dumb hack to build an executable with the same name.
set_target_properties(winchecksec-bin PROPERTIES OUTPUT_NAME winchecksec)
@@ -1,6 +1,7 @@
#include "Checksec.h"

#include <parser-library/parse.h>
#include <uthenticode.h>

#include <ostream>
#include <vector>
@@ -47,29 +48,9 @@ void to_json(json& j, const MitigationReport& r) {
}
}

class LoadedImage {
public:
explicit LoadedImage(const std::string path) {
if (!(pe_ = peparse::ParsePEFromFile(path.c_str()))) {
throw ChecksecError("Couldn't load file; corrupt or not a PE?");
}
}
~LoadedImage() { peparse::DestructParsedPE(pe_); }

// can't make copies of LoadedImage
LoadedImage(const LoadedImage&) = delete;
LoadedImage& operator=(const LoadedImage&) = delete;

peparse::parsed_pe* operator&() { return pe_; }

private:
peparse::parsed_pe* pe_;
};

Checksec::Checksec(std::string filepath) : filepath_(filepath) {
LoadedImage loadedImage{filepath};

peparse::nt_header_32 nt = (&loadedImage)->peHeader.nt;
Checksec::Checksec(std::string filepath)
: filepath_(filepath), loadedImage_(filepath) {
peparse::nt_header_32 nt = loadedImage_.get()->peHeader.nt;
peparse::file_header* imageFileHeader = &(nt.FileHeader);

targetMachine_ = imageFileHeader->Machine;
@@ -100,7 +81,7 @@ Checksec::Checksec(std::string filepath) : filepath_(filepath) {
}

if (!peparse::GetDataDirectoryEntry(
(&loadedImage), peparse::DIR_LOAD_CONFIG, loadConfigData)) {
loadedImage_.get(), peparse::DIR_LOAD_CONFIG, loadConfigData)) {
std::cerr << "Warn: No load config in the PE"
<< "\n";
return;
@@ -140,7 +121,7 @@ Checksec::Checksec(std::string filepath) : filepath_(filepath) {
}

if (!peparse::GetDataDirectoryEntry(
(&loadedImage), peparse::DIR_LOAD_CONFIG, loadConfigData)) {
loadedImage_.get(), peparse::DIR_LOAD_CONFIG, loadConfigData)) {
std::cerr << "Warn: No load config in the PE"
<< "\n";
return;
@@ -179,9 +160,7 @@ Checksec::operator json() const {
{"rfg", isRFG()},
{"safeSEH", isSafeSEH()},
{"gs", isGS()},
#if _WIN32
{"authenticode", isAuthenticode()},
#endif
{"dotNET", isDotNET()},
},
},
@@ -276,6 +255,14 @@ const MitigationReport Checksec::isCFG() const {
}
}

const MitigationReport Checksec::isAuthenticode() const {
if (uthenticode::verify(loadedImage_.get())) {
return REPORT(Present, kAuthenticodeDescription);
} else {
return REPORT(NotPresent, kAuthenticodeDescription);
}
}

const MitigationReport Checksec::isRFG() const {
// NOTE(ww): a load config under 148 bytes implies the absence of the
// GuardFlags field.
@@ -360,10 +347,8 @@ std::ostream& operator<<(std::ostream& os, Checksec& self) {
os << "SafeSEH : " << j["mitigations"]["safeSEH"]["presence"]
<< "\n";
os << "GS : " << j["mitigations"]["gs"]["presence"] << "\n";
#ifdef _WIN32
os << "Authenticode : " << j["mitigations"]["authenticode"]["presence"]
<< "\n";
#endif
os << ".NET : " << j["mitigations"]["dotNET"]["presence"]
<< "\n";
return os;
@@ -101,6 +101,25 @@ struct EXPORT MitigationReport {
operator bool() const { return presence == MitigationPresence::Present; }
};

class LoadedImage {
public:
explicit LoadedImage(const std::string path) {
if (!(pe_ = peparse::ParsePEFromFile(path.c_str()))) {
throw ChecksecError("Couldn't load file; corrupt or not a PE?");
}
}
~LoadedImage() { peparse::DestructParsedPE(pe_); }

// can't make copies of LoadedImage
LoadedImage(const LoadedImage&) = delete;
LoadedImage& operator=(const LoadedImage&) = delete;

peparse::parsed_pe* get() const { return pe_; }

private:
peparse::parsed_pe* pe_;
};

class EXPORT Checksec {
public:
Checksec(std::string filepath);
@@ -115,9 +134,7 @@ class EXPORT Checksec {
const MitigationReport isIsolation() const;
const MitigationReport isSEH() const;
const MitigationReport isCFG() const;
#ifdef _WIN32
const MitigationReport isAuthenticode() const;
#endif
const MitigationReport isRFG() const;
const MitigationReport isSafeSEH() const;
const MitigationReport isGS() const;
@@ -127,6 +144,7 @@ class EXPORT Checksec {
friend std::ostream& operator<<(std::ostream& os, Checksec&);

private:
LoadedImage loadedImage_;
std::string filepath_;
std::uint16_t targetMachine_ = 0;
std::uint16_t imageCharacteristics_ = 0;
@@ -4,7 +4,15 @@ ALL_SRCS := $(wildcard *.cc) $(wildcard *.h)
all:
echo "This is not a build system! Run my targets individually!"

.PHONY: lint
lint:
clang-format -i -style=file $(ALL_SRCS)
.PHONY: format
format: clang-format cmake-format

.PHONY: clang-format
clang-format:
$(CLANG_FORMAT) -i -style=file $(ALL_SRCS)
git diff --exit-code

.PHONY: cmake-format
cmake-format:
cmake-format -i CMakeLists.txt
git diff --exit-code
@@ -59,34 +59,52 @@ enabled by passing `-j`:
```cmd
> .\Release\winchecksec.exe C:\Windows\notepad.exe
Dynamic Base : true
ASLR : true
High Entropy VA : true
Force Integrity : false
Isolation : true
NX : true
SEH : true
CFG : true
RFG : false
SafeSEH : false
GS : true
Authenticode : false
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
{"aslr":true,"authenticode":false,"cfg":true,"dynamicBase":true,"forceIntegrity":false,"gs":true,"highEntropyVA":true,"isolation":true,"nx":true,"path":"C:\\Windows\\notepad.exe","rfg":false,"safeSEH":false,"seh":true}
{
"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` only takes one file at a time. To run it on multiple files or entire directories,
wrap it in a loop.

## Hacking

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

```bash
$ make lint
$ make clang-format
```

## Statistics for different flags across EXEs on Windows 10

This file was deleted.

0 comments on commit 0079c01

Please sign in to comment.
You can’t perform that action at this time.