diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f8a029..ead3d5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 @@ -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: | @@ -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 @@ -93,6 +93,10 @@ 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 + - name: Smoke test + run: ./dist/checksec.exe C:\Windows --json + test: needs: build runs-on: ubuntu-latest @@ -100,10 +104,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 @@ -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: | diff --git a/README.md b/README.md index d99a7e5..3959b18 100644 --- a/README.md +++ b/README.md @@ -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 | ❌ | ✔ | @@ -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 | ✔ | ❌ | diff --git a/checksec/__main__.py b/checksec/__main__.py index 9395be3..7ae7b78 100644 --- a/checksec/__main__.py +++ b/checksec/__main__.py @@ -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 @@ -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[""]] debug = args["--debug"] @@ -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 = { diff --git a/checksec/elf.py b/checksec/elf.py index 8b3016c..78ecacc 100644 --- a/checksec/elf.py +++ b/checksec/elf.py @@ -1,3 +1,4 @@ +import logging from collections import namedtuple from enum import Enum from functools import lru_cache @@ -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"] @@ -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) diff --git a/checksec/output.py b/checksec/output.py index 6dff34f..35c7d3d 100644 --- a/checksec/output.py +++ b/checksec/output.py @@ -1,4 +1,5 @@ import json +import logging from abc import ABC, abstractmethod from pathlib import Path from typing import List, Union @@ -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 diff --git a/checksec/utils.py b/checksec/utils.py index 29d1eb8..d2ad6c2 100644 --- a/checksec/utils.py +++ b/checksec/utils.py @@ -7,6 +7,8 @@ from pathlib import Path import lddwrap +# cannot use is_elf because of circular dependency +import lief class LibcNotFoundError(Exception): @@ -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: diff --git a/setup.py b/setup.py index fb41c93..d0899c9 100644 --- a/setup.py +++ b/setup.py @@ -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", @@ -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", )