Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Set up Python 3.6 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.6'
python-version: '3.7'

- name: Install Nox
run: python -m pip install nox==2020.8.22
Expand All @@ -35,10 +35,10 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Set up Python 3.6 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.6'
python-version: '3.7'

- name: Install Nox
run: python -m pip install nox==2020.8.22
Expand All @@ -53,10 +53,10 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Set up Python 3.6 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.6'
python-version: '3.7'

- name: Build 🔨
run: |
Expand All @@ -71,10 +71,10 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Set up Python 3.9 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.9'
python-version: '3.7'

- name: Install PyInstaller
run: python -m pip install pyinstaller==4.0
Expand All @@ -93,17 +93,21 @@ jobs:
name: checksec.exe
path: dist/checksec.exe

# TODO: can't test rich output: UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-78: character maps to <undefined>
- name: Smoke test
run: ./dist/checksec.exe C:\Windows --json

test:
needs: build
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1

- name: Set up Python 3.6 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.6'
python-version: '3.7'

- name: Install Nox
run: python -m pip install nox==2020.8.22
Expand Down Expand Up @@ -177,10 +181,10 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Set up Python 3.6 🐍
- name: Set up Python 3.7 🐍
uses: actions/setup-python@v1
with:
python-version: '3.6'
python-version: '3.7'

- name: Build 🔨
run: |
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@ Check `--help` for more options (_JSON output_, _recursive walk_, _workers count

| | checksec.py | checksec.sh |
|----------------------------|:-----------:|:-----------:|
| Distributed workload | ✔ | ❌ |
| Cross-Platform support | ✔ | ❌ |
| Distributed workload | ✔ | ❌ |
| Scan file | ✔ | ✔ |
| Scan directory | ✔ | ✔ |
| Scan directory recursively | ✔ | ❌ |
| Specify libc path | ✔ | ❌ |
| Scan process | ❌ | ✔ |
| Scan process libs | ❌ | ✔ |
| Scan kernel config | ❌ | ✔ |
Expand All @@ -144,6 +146,7 @@ Check `--help` for more options (_JSON output_, _recursive walk_, _workers count

| | checksec.py | winchecksec |
|-----------------------------|:-----------:|:-----------:|
| Cross-Platform support | ✔ | ❌ |
| Distributed workload | ✔ | ❌ |
| Scan file | ✔ | ✔ |
| Scan directory | ✔ | ❌ |
Expand Down
18 changes: 16 additions & 2 deletions checksec/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import os
from concurrent.futures import ProcessPoolExecutor, as_completed
from pathlib import Path
from typing import Iterator, List, Union
from typing import Iterator, List, Optional, Union

from docopt import docopt

Expand Down Expand Up @@ -52,6 +52,18 @@ def checksec_file(filepath: Path) -> Union["ELFChecksecData", "PEChecksecData"]:
return binary.checksec_state


def worker_initializer(libc_path: Optional[Path] = None):
"""Routine to initialize some context in a worker process"""
# this function is used to set global object in the worker's process context
# multiprocessing has different behaviors on Windows and Linux
# on Windows, the global object __LIBC_OBJ in elf.py is found to be uninitialized,
# even after we explicitely initialized it in the main function.
#
# this function ensures that the object is initialized with the libc_path passed as cmdline argument
logging.debug("Worker %s: initializer", os.getpid())
get_libc(libc_path)


def main(args):
filepath_list = [Path(entry) for entry in args["<file/directory>"]]
debug = args["--debug"]
Expand Down Expand Up @@ -93,7 +105,9 @@ def main(args):
check_output.enumerating_tasks_start()
count = sum(1 for i in walk_filepath_list(filepath_list, recursive))
check_output.enumerating_tasks_stop(count)
with ProcessPoolExecutor(max_workers=workers) as pool:
with ProcessPoolExecutor(
max_workers=workers, initializer=worker_initializer, initargs=(libc_path,)
) as pool:
try:
check_output.processing_tasks_start()
future_to_checksec = {
Expand Down
8 changes: 7 additions & 1 deletion checksec/elf.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from collections import namedtuple
from enum import Enum
from functools import lru_cache
Expand Down Expand Up @@ -48,12 +49,16 @@ def get_libc(libc_path: Optional[Path] = None) -> Optional["Libc"]:
try:
__LIBC_OBJ["libc"]
except KeyError:
logging.debug("Libc object not set")
try:
libc = Libc(libc_path)
except (LibcNotFoundError, ErrorParsingFailed):
except (LibcNotFoundError, ErrorParsingFailed) as e:
logging.debug("Failed to init Libc object: %s", e)
__LIBC_OBJ["libc"] = None
else:
logging.debug("Libc object initialized")
__LIBC_OBJ["libc"] = libc
logging.debug(__LIBC_OBJ)
return __LIBC_OBJ["libc"]


Expand All @@ -79,6 +84,7 @@ def __init__(self, libpath: Path = None):
libpath = Path(find_libc())
if not libpath:
raise LibcNotFoundError
logging.debug("Initializing Libc from %s", libpath)
self.libc = lief.parse(str(libpath))
if not self.libc:
raise ErrorParsingFailed(libpath)
Expand Down
2 changes: 2 additions & 0 deletions checksec/output.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Union
Expand Down Expand Up @@ -137,6 +138,7 @@ def processing_tasks_start(self):
self.process_task_id = self.process_bar.add_task("Checking", total=self.total)

def add_checksec_result(self, filepath: Path, checksec: Union[ELFChecksecData, PEChecksecData]):
logging.debug("result for %s: %s", filepath, checksec)
if isinstance(checksec, ELFChecksecData):
row_res: List[str] = []
# display results
Expand Down
14 changes: 12 additions & 2 deletions checksec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from pathlib import Path

import lddwrap
# cannot use is_elf because of circular dependency
import lief


class LibcNotFoundError(Exception):
Expand Down Expand Up @@ -37,11 +39,19 @@ def find_libc():
# or other errors
try:
libc_path = find_libc_ldd()
except FileNotFoundError:
except (FileNotFoundError, RuntimeError):
# test hardcoded paths
logging.debug("Finding libc path: hardcoded paths")
for maybe_libc in LIBC_PATH_POSSIBILITIES:
if Path(maybe_libc).resolve().exists():
logging.debug("Testing libc at %s", maybe_libc)
maybe_libc_path = Path(maybe_libc)
if maybe_libc_path.exists():
# symlink
if maybe_libc_path.is_symlink():
dst = os.readlink(str(maybe_libc_path))
logging.debug("Resolve symlink %s -> %s", maybe_libc_path, dst)
maybe_libc_path = Path(dst)
if lief.is_elf(str(maybe_libc_path)):
libc_path = maybe_libc
break
if libc_path is None:
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setuptools.setup(
name="checksec.py",
version="0.4.5",
version="0.5.0",
author="Mathieu Tarral",
author_email="mathieu.tarral@protonmail.com",
description="Checksec tool implemented in Python",
Expand All @@ -21,10 +21,10 @@
"console_scripts": ["checksec = checksec.__main__:entrypoint"],
},
classifiers=[
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Development Status :: 4 - Beta",
"Typing :: Typed",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
],
python_requires=">=3.6",
python_requires=">=3.7",
)