pypackerdetect 1.2.0__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/.github/workflows/python-package.yml +29 -17
  2. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/.gitignore +3 -0
  3. pypackerdetect-1.2.1/.readthedocs.yaml +13 -0
  4. {pypackerdetect-1.2.0/src/pypackerdetect.egg-info → pypackerdetect-1.2.1}/PKG-INFO +7 -6
  5. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/README.md +4 -3
  6. pypackerdetect-1.2.1/docs/mkdocs.yml +60 -0
  7. pypackerdetect-1.2.1/docs/pages/api.md +101 -0
  8. pypackerdetect-1.2.1/docs/pages/cli/pypackerdetect.md +100 -0
  9. pypackerdetect-1.2.1/docs/pages/contributing.md +56 -0
  10. pypackerdetect-1.2.1/docs/pages/img/icon.png +0 -0
  11. pypackerdetect-1.2.1/docs/pages/index.md +72 -0
  12. pypackerdetect-1.2.1/docs/pages/installation.md +56 -0
  13. pypackerdetect-1.2.1/docs/pages/internals.md +141 -0
  14. pypackerdetect-1.2.1/docs/requirements.txt +6 -0
  15. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/pyproject.toml +2 -2
  16. pypackerdetect-1.2.1/requirements.txt +2 -0
  17. pypackerdetect-1.2.1/src/pypackerdetect/VERSION.txt +1 -0
  18. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/__info__.py +1 -1
  19. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/__init__.py +39 -16
  20. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/__main__.py +28 -23
  21. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/db/sections.json +2 -2
  22. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1/src/pypackerdetect.egg-info}/PKG-INFO +7 -6
  23. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect.egg-info/SOURCES.txt +11 -1
  24. pypackerdetect-1.2.1/src/pypackerdetect.egg-info/requires.txt +2 -0
  25. pypackerdetect-1.2.0/requirements.txt +0 -2
  26. pypackerdetect-1.2.0/src/pypackerdetect/VERSION.txt +0 -1
  27. pypackerdetect-1.2.0/src/pypackerdetect.egg-info/requires.txt +0 -2
  28. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/LICENSE +0 -0
  29. {pypackerdetect-1.2.0/docs → pypackerdetect-1.2.1/docs/pages/img}/logo.png +0 -0
  30. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/setup.cfg +0 -0
  31. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/db/packers.json +0 -0
  32. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/db/sigs_long.txt +0 -0
  33. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect/db/sigs_short.txt +0 -0
  34. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect.egg-info/dependency_links.txt +0 -0
  35. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect.egg-info/entry_points.txt +0 -0
  36. {pypackerdetect-1.2.0 → pypackerdetect-1.2.1}/src/pypackerdetect.egg-info/top_level.txt +0 -0
@@ -10,7 +10,6 @@ on:
10
10
  branches: [ "main" ]
11
11
 
12
12
  permissions:
13
- id-token: write # for OIDC
14
13
  contents: read
15
14
 
16
15
  jobs:
@@ -20,7 +19,7 @@ jobs:
20
19
  fail-fast: false
21
20
  matrix:
22
21
  os: [ubuntu-latest]
23
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
22
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
24
23
  steps:
25
24
  - uses: actions/checkout@v5
26
25
  - name: Set up Python ${{ matrix.python-version }}
@@ -40,31 +39,44 @@ jobs:
40
39
  # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
41
40
  flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
42
41
  deploy:
42
+ # only publish from pushes to main, never from pull requests
43
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
43
44
  environment:
44
45
  name: pypi
45
- url: ${{ needs.prepare.outputs.pypi_url }}
46
+ url: https://pypi.org/p/pypackerdetect
47
+ permissions:
48
+ # required for PyPI trusted publishing (OIDC)
49
+ id-token: write
50
+ contents: read
46
51
  runs-on: ubuntu-latest
47
52
  needs: build
48
53
  steps:
49
54
  - uses: actions/checkout@v5
50
- with:
51
- fetch-depth: 0
52
- - name: Check for version change
53
- uses: dorny/paths-filter@v4
54
- id: filter
55
- with:
56
- filters: |
57
- version:
58
- - '**/VERSION.txt'
59
- - if: steps.filter.outputs.version == 'true'
55
+ - name: Check whether the version is already on PyPI
56
+ # comparing with PyPI (not with the previous commit) also releases a version whose push did not trigger a deploy
57
+ id: version
58
+ run: |
59
+ version=$(tr -d '[:space:]' < src/pypackerdetect/VERSION.txt)
60
+ status=$(curl -s -o /dev/null -w '%{http_code}' --retry 3 "https://pypi.org/pypi/pypackerdetect/$version/json")
61
+ case "$status" in
62
+ 200) echo "Version $version is already on PyPI" ; echo "new=false" >> "$GITHUB_OUTPUT" ;;
63
+ 404) echo "Version $version is not on PyPI yet" ; echo "new=true" >> "$GITHUB_OUTPUT" ;;
64
+ *) echo "::error::Unexpected HTTP status $status from PyPI" ; exit 1 ;;
65
+ esac
66
+ - if: steps.version.outputs.new == 'true'
60
67
  name: Cleanup README
61
68
  run: |
62
69
  sed -ri 's/^(##*)\s*:.*:\s*/\1 /g' README.md
63
70
  awk '{if (match($0,"## Supporters")) exit; print}' README.md > README
64
71
  mv -f README README.md
65
- - if: steps.filter.outputs.version == 'true'
66
- name: Build package
72
+ - if: steps.version.outputs.new == 'true'
73
+ name: Set up Python
74
+ uses: actions/setup-python@v6
75
+ with:
76
+ python-version: "3.13"
77
+ - if: steps.version.outputs.new == 'true'
78
+ name: Build pypackerdetect package
67
79
  run: python3 -m pip install --upgrade build && python3 -m build
68
- - if: steps.filter.outputs.version == 'true'
69
- name: Upload to PyPi
80
+ - if: steps.version.outputs.new == 'true'
81
+ name: Publish to PyPI
70
82
  uses: pypa/gh-action-pypi-publish@release/v1
@@ -70,3 +70,6 @@ target/
70
70
  .test
71
71
  tmp
72
72
  TODO
73
+
74
+ # PEiD search-tree caches (rebuilt at runtime)
75
+ src/pypackerdetect/db/.*.json
@@ -0,0 +1,13 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.13"
7
+
8
+ mkdocs:
9
+ configuration: docs/mkdocs.yml
10
+
11
+ python:
12
+ install:
13
+ - requirements: docs/requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypackerdetect
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: Re-implementation and packaging of PyPackerDetect
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -691,14 +691,14 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
691
691
  Classifier: Programming Language :: Python :: 3
692
692
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
693
693
  Classifier: Topic :: Security
694
- Requires-Python: <4,>=3.9
694
+ Requires-Python: <4,>=3.10
695
695
  Description-Content-Type: text/markdown
696
696
  License-File: LICENSE
697
697
  Requires-Dist: pefile
698
- Requires-Dist: peid>=2.3.1
698
+ Requires-Dist: peid>=2.4.0
699
699
  Dynamic: license-file
700
700
 
701
- <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/logo.png"></p>
701
+ <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/pages/img/logo.png"></p>
702
702
  <h1 align="center">PyPackerDetect <a href="https://twitter.com/intent/tweet?text=PyPackerDetect%20-%20Packer%20detector%20using%20multiple%20heuristics%20and%20PEiD's%20signatures%20database.%0D%0Ahttps%3a%2f%2fgithub%2ecom%2fpacking-box%2fPyPackerDetect%0D%0A&hashtags=python,pe,peid,packer,packingdetection"><img src="https://img.shields.io/badge/Tweet--lightgrey?logo=twitter&style=social" alt="Tweet" height="20"/></a></h1>
703
703
  <h3 align="center">Detect packers on PE files using heuristics and signatures.</h3>
704
704
 
@@ -723,8 +723,9 @@ $ pypackerdetect --help
723
723
  usage examples:
724
724
  - pypackerdetect program.exe
725
725
  - pypackerdetect program.exe -b
726
- - pypackerdetect program.exe --low-imports --unknown-sections
726
+ - pypackerdetect program.exe --low-imports --unknown-sections # disable these two checks
727
727
  - pypackerdetect program.exe --imports-threshold 5 --bad-sections-threshold 5
728
+ - pypackerdetect program.exe --peid-db-path user_db.txt --sections-json sections.json
728
729
  ```
729
730
 
730
731
  ## Detection Mechanisms
@@ -732,7 +733,7 @@ usage examples:
732
733
  - PEID signatures
733
734
  - Known packer section names
734
735
  - Entrypoint in non-standard section
735
- - Threshhold of non-standard sections reached
736
+ - Threshold of non-standard sections reached
736
737
  - Low number of imports
737
738
  - Overlapping entrypoint sections
738
739
 
@@ -1,4 +1,4 @@
1
- <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/logo.png"></p>
1
+ <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/pages/img/logo.png"></p>
2
2
  <h1 align="center">PyPackerDetect <a href="https://twitter.com/intent/tweet?text=PyPackerDetect%20-%20Packer%20detector%20using%20multiple%20heuristics%20and%20PEiD's%20signatures%20database.%0D%0Ahttps%3a%2f%2fgithub%2ecom%2fpacking-box%2fPyPackerDetect%0D%0A&hashtags=python,pe,peid,packer,packingdetection"><img src="https://img.shields.io/badge/Tweet--lightgrey?logo=twitter&style=social" alt="Tweet" height="20"/></a></h1>
3
3
  <h3 align="center">Detect packers on PE files using heuristics and signatures.</h3>
4
4
 
@@ -23,8 +23,9 @@ $ pypackerdetect --help
23
23
  usage examples:
24
24
  - pypackerdetect program.exe
25
25
  - pypackerdetect program.exe -b
26
- - pypackerdetect program.exe --low-imports --unknown-sections
26
+ - pypackerdetect program.exe --low-imports --unknown-sections # disable these two checks
27
27
  - pypackerdetect program.exe --imports-threshold 5 --bad-sections-threshold 5
28
+ - pypackerdetect program.exe --peid-db-path user_db.txt --sections-json sections.json
28
29
  ```
29
30
 
30
31
  ## Detection Mechanisms
@@ -32,7 +33,7 @@ usage examples:
32
33
  - PEID signatures
33
34
  - Known packer section names
34
35
  - Entrypoint in non-standard section
35
- - Threshhold of non-standard sections reached
36
+ - Threshold of non-standard sections reached
36
37
  - Low number of imports
37
38
  - Overlapping entrypoint sections
38
39
 
@@ -0,0 +1,60 @@
1
+ site_author: dhondta
2
+ site_name: PyPackerDetect
3
+ site_description: Detect packers on PE files using heuristics and signatures
4
+ site_url: https://pypackerdetect.readthedocs.io
5
+ repo_url: https://github.com/packing-box/pypackerdetect
6
+ edit_uri: edit/main/docs/pages/
7
+ copyright: Copyright &copy; 2021 Alexandre D'Hondt
8
+ docs_dir: pages
9
+ nav:
10
+ - Home: index.md
11
+ - Installation: installation.md
12
+ - Command-line tool: cli/pypackerdetect.md
13
+ - Python API: api.md
14
+ - How it works: internals.md
15
+ - Contributing: contributing.md
16
+ extra:
17
+ generator: false
18
+ social:
19
+ - icon: fontawesome/solid/paper-plane
20
+ link: mailto:alexandre.dhondt@gmail.com
21
+ name: Contact Alex
22
+ - icon: fontawesome/brands/github
23
+ link: https://github.com/dhondta
24
+ name: Alex on GitHub
25
+ - icon: fontawesome/brands/linkedin
26
+ link: https://www.linkedin.com/in/alexandre-d-2ab2aa14/
27
+ name: Alex on LinkedIn
28
+ - icon: fontawesome/brands/twitter
29
+ link: https://twitter.com/alex_dhondt
30
+ name: Alex on Twitter
31
+ theme:
32
+ name: material
33
+ logo: img/logo.png
34
+ favicon: img/icon.png
35
+ palette:
36
+ - scheme: default
37
+ toggle:
38
+ icon: material/brightness-7
39
+ name: Switch to dark mode
40
+ - scheme: slate
41
+ toggle:
42
+ icon: material/brightness-4
43
+ name: Switch to light mode
44
+ features:
45
+ - content.code.copy
46
+ - navigation.sections
47
+ - navigation.top
48
+ - toc.follow
49
+ use_directory_urls: false
50
+ markdown_extensions:
51
+ - admonition
52
+ - attr_list
53
+ - def_list
54
+ - pymdownx.details
55
+ - pymdownx.highlight
56
+ - pymdownx.inlinehilite
57
+ - pymdownx.superfences
58
+ - tables
59
+ - toc:
60
+ permalink: true
@@ -0,0 +1,101 @@
1
+ # Python API
2
+
3
+ The `pypackerdetect` package exposes one class:
4
+
5
+ ```python
6
+ from pypackerdetect import PyPackerDetect
7
+ ```
8
+
9
+ ---
10
+
11
+ ## `PyPackerDetect`
12
+
13
+ ```python
14
+ PyPackerDetect(peid=True, peid_large_db=False, peid_ep_only=True, peid_db_path=None,
15
+ bad_ep_sections=True, low_imports=True, packer_sections=True,
16
+ unknown_sections=True, import_threshold=10, unknown_sections_threshold=3,
17
+ bad_sections_threshold=2, logger=None, packers_json=None,
18
+ sections_json=None, **kwargs)
19
+ ```
20
+
21
+ Configures a detector. The JSON databases are loaded once, here, so one
22
+ instance can analyze many files.
23
+
24
+ | Parameter | Type | Description |
25
+ |---|---|---|
26
+ | `peid` | `bool` | Match PEiD signatures. |
27
+ | `peid_large_db` | `bool` | Use the large embedded PEiD database instead of the short one. |
28
+ | `peid_ep_only` | `bool` | Match PEiD signatures at the entry point only. `False` scans the whole file. |
29
+ | `peid_db_path` | `str` or `None` | Path to a custom PEiD database. It takes precedence over `peid_large_db`. |
30
+ | `bad_ep_sections` | `bool` | Check the section(s) holding the entry point. |
31
+ | `low_imports` | `bool` | Check the number of imports. |
32
+ | `packer_sections` | `bool` | Check for known packer section names. |
33
+ | `unknown_sections` | `bool` | Check the number of non-standard and invalid section names. |
34
+ | `import_threshold` | `int` | Report a detection when there are this many imports or fewer. |
35
+ | `unknown_sections_threshold` | `int` | Report a detection from this many non-standard section names. |
36
+ | `bad_sections_threshold` | `int` | Report a detection from this many section names with invalid characters. |
37
+ | `logger` | `logging.Logger` | Optional logger for debug output. |
38
+ | `packers_json` | `str` or `None` | Path to a custom packer section names JSON. `None` uses the embedded one. |
39
+ | `sections_json` | `str` or `None` | Path to a custom known section names JSON. `None` uses the embedded one. |
40
+ | `**kwargs` | | Ignored. It lets you pass `vars(args)` from an argument parser. |
41
+
42
+ See [Databases](internals.md#databases) for the JSON formats.
43
+
44
+ ### `detect`
45
+
46
+ ```python
47
+ PyPackerDetect.detect(pe)
48
+ ```
49
+
50
+ Analyzes one executable.
51
+
52
+ | Parameter | Type | Description |
53
+ |---|---|---|
54
+ | `pe` | `str` or `pefile.PE` | Path to the executable, or an already parsed `pefile.PE` object. |
55
+
56
+ **Returns** a dictionary with two lists of messages:
57
+
58
+ ```python
59
+ {'detections': [...], 'suspicions': [...]}
60
+ ```
61
+
62
+ See [Findings](internals.md#findings) for the possible messages.
63
+
64
+ **Raises** `pefile.PEFormatError` if the file is not a valid PE file.
65
+
66
+ !!! note "Passing a `pefile.PE` object"
67
+ PEiD and `objdump` work on files. If the `PE` object has a `path`
68
+ attribute, that file is used. Otherwise (for instance with
69
+ `PE(data=...)`), the data is copied to a temporary file for the duration
70
+ of the analysis.
71
+
72
+ ### `report`
73
+
74
+ ```python
75
+ PyPackerDetect.report(pe, findings) # static method
76
+ ```
77
+
78
+ Prints the findings returned by [`detect`](#detect) in the format of the
79
+ original project (see [Output](cli/pypackerdetect.md#output)). `pe` is the
80
+ path or the `pefile.PE` object, and is only used for the report header.
81
+
82
+ ## Example
83
+
84
+ ```python
85
+ import logging
86
+ from pefile import PE
87
+ from pypackerdetect import PyPackerDetect
88
+
89
+ detector = PyPackerDetect(peid_large_db=True, import_threshold=5,
90
+ logger=logging.getLogger("pypackerdetect"))
91
+
92
+ for path in ["a.exe", "b.exe"]:
93
+ findings = detector.detect(path)
94
+ if findings["detections"]:
95
+ print(f"{path} looks packed")
96
+ PyPackerDetect.report(path, findings)
97
+
98
+ # with a PE object parsed from memory
99
+ with open("c.exe", "rb") as f:
100
+ findings = detector.detect(PE(data=f.read()))
101
+ ```
@@ -0,0 +1,100 @@
1
+ # `pypackerdetect`: detect packing
2
+
3
+ `pypackerdetect` runs all the [detection mechanisms](../internals.md) on a PE
4
+ file and reports the findings, either as **detections** or **suspicions**.
5
+
6
+ ```console
7
+ $ pypackerdetect [path arguments] [optional arguments] [threshold arguments] [-b] [-v] [-h] path
8
+ ```
9
+
10
+ ## Arguments
11
+
12
+ | Argument | Description |
13
+ |---|---|
14
+ | `path` | The PE file to analyze. It must exist. |
15
+
16
+ ### Path arguments
17
+
18
+ | Option | Default | Description |
19
+ |---|---|---|
20
+ | `--packers-json JSON` | embedded | JSON file mapping known packer section names to packer descriptions (see [Databases](../internals.md#databases)). |
21
+ | `--peid-db-path TXT_DB` | embedded | PEiD signatures database to use instead of the embedded one. It takes precedence over `--peid-large-db`. |
22
+ | `--sections-json JSON` | embedded | JSON file with the lists of known section names. |
23
+
24
+ ### Optional arguments
25
+
26
+ All checks are **enabled by default**. Each of these flags **turns one off**.
27
+
28
+ | Option | Turns off |
29
+ |---|---|
30
+ | `--bad-ep-sections` | The checks on the entry point section: irregular section, overlapping sections, no section, null entry point. |
31
+ | `--low-imports` | The check for a low number of imports. |
32
+ | `--packer-sections` | The check for known packer section names. |
33
+ | `--peid` | Detection with PEiD signatures. |
34
+ | `--peid-ep-only` | Entry point-only PEiD matching. PEiD then scans **every offset of the file** with the signatures that are not tied to the entry point, which is much slower. |
35
+ | `--unknown-sections` | The checks on the number of non-standard and invalid section names. |
36
+
37
+ | Option | Default | Description |
38
+ |---|---|---|
39
+ | `--peid-large-db` | off | Use the large embedded PEiD database (4,287 signatures) instead of the short one (1,832 signatures). |
40
+
41
+ !!! warning "Flags turn checks off"
42
+ The option names describe the checks, not the action. For instance,
43
+ `pypackerdetect program.exe --low-imports` **disables** the low imports
44
+ check.
45
+
46
+ ### Threshold arguments
47
+
48
+ | Option | Default | Description |
49
+ |---|---|---|
50
+ | `--bad-sections-threshold N` | 2 | Report a detection when at least `N` section names contain invalid characters. |
51
+ | `--imports-threshold N` | 10 | Report a detection when the executable has `N` imports or fewer. |
52
+ | `--unknown-sections-threshold N` | 3 | Report a detection when at least `N` section names are non-standard. |
53
+
54
+ `N` must be a non-negative integer.
55
+
56
+ ### Extra arguments
57
+
58
+ | Option | Description |
59
+ |---|---|
60
+ | `-b`, `--benchmark` | Print the elapsed time, in seconds, after the report. |
61
+ | `-v`, `--verbose` | Show debug information, including each finding as it is found. |
62
+ | `-h`, `--help` | Show the help message and exit. |
63
+
64
+ ## Output
65
+
66
+ ```console
67
+ $ pypackerdetect calc_packed.exe
68
+ Packer report for: calc_packed.exe
69
+ Detections: 3
70
+ Suspicions: 0
71
+ Log:
72
+ [DETECTION] Entry point 0xc4f80 in irregular section(s): 'UPX1'
73
+ [DETECTION] Section name 'UPX0' matches known packer: [UPX packer]
74
+ [DETECTION] Section name 'UPX1' matches known packer: [UPX packer]
75
+ ```
76
+
77
+ The `Log:` section only appears when there is at least one finding. See
78
+ [Findings](../internals.md#findings) for the list of messages.
79
+
80
+ ## Exit code
81
+
82
+ | Code | Meaning |
83
+ |---|---|
84
+ | `0` | The analysis ran, whatever the findings. |
85
+ | `1` | The analysis failed, for instance because the input is not a valid PE file. |
86
+ | `2` | Invalid arguments. |
87
+
88
+ !!! note
89
+ The exit code does not tell whether the file is packed. Parse the report,
90
+ or use the [Python API](../api.md), for that.
91
+
92
+ ## Examples
93
+
94
+ ```console
95
+ $ pypackerdetect program.exe
96
+ $ pypackerdetect program.exe -b
97
+ $ pypackerdetect program.exe --low-imports --unknown-sections # disable these two checks
98
+ $ pypackerdetect program.exe --imports-threshold 5 --bad-sections-threshold 5
99
+ $ pypackerdetect program.exe --peid-db-path user_db.txt --sections-json sections.json
100
+ ```
@@ -0,0 +1,56 @@
1
+ # Contributing
2
+
3
+ Contributions are welcome, especially **new packer section names** and
4
+ **false positive reports**.
5
+
6
+ ## Reporting issues
7
+
8
+ Open an issue on [GitHub](https://github.com/packing-box/pypackerdetect/issues)
9
+ and include:
10
+
11
+ - the PyPackerDetect version (`pypackerdetect --help` prints it);
12
+ - the exact command and its output, ideally with `-v`;
13
+ - if possible, a link to (or the hash of) the sample.
14
+
15
+ ## Adding section names
16
+
17
+ - **Packer section names** go in `src/pypackerdetect/db/packers.json`, with a
18
+ short description of the packer. Avoid names that are also standard
19
+ section names: they are ignored anyway (see [Section names](internals.md#section-names)).
20
+ - **Standard section names** that cause false positives go in the `known` list
21
+ of `src/pypackerdetect/db/sections.json`.
22
+
23
+ Test your change on packed and unpacked samples. The
24
+ [packed PE dataset](https://github.com/packing-box/dataset-packed-pe) and
25
+ [Docker Packing Box](https://github.com/packing-box/docker-packing-box) help
26
+ with this. Section names are compared after stripping null bytes and spaces,
27
+ so do not add trailing spaces to the names.
28
+
29
+ ## Development setup
30
+
31
+ ```console
32
+ $ git clone https://github.com/packing-box/pypackerdetect
33
+ $ cd pypackerdetect
34
+ $ pip install -e .
35
+ $ pip install flake8
36
+ $ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
37
+ ```
38
+
39
+ CI (`.github/workflows/python-package.yml`) installs the package on Python
40
+ 3.9 to 3.13 and runs this flake8 check.
41
+
42
+ ## Releasing
43
+
44
+ The version is stored in `src/pypackerdetect/VERSION.txt`. When a push to
45
+ `main` changes that file, CI builds the package and publishes it to PyPI.
46
+
47
+ ## Building this documentation
48
+
49
+ ```console
50
+ $ pip install -r docs/requirements.txt
51
+ $ mkdocs serve -f docs/mkdocs.yml # live preview at http://127.0.0.1:8000
52
+ $ mkdocs build --strict -f docs/mkdocs.yml # what Read the Docs runs
53
+ ```
54
+
55
+ Read the Docs builds the site from `.readthedocs.yaml` at the root of the
56
+ repository.
@@ -0,0 +1,72 @@
1
+ # PyPackerDetect
2
+
3
+ <p align="center"><img src="img/logo.png" alt="PyPackerDetect logo" width="200"></p>
4
+
5
+ **PyPackerDetect** detects whether a Windows PE executable is packed. It
6
+ combines several heuristics on the PE structure with
7
+ [PEiD](https://github.com/packing-box/peid) signatures. It is a complete
8
+ refactoring of [Cylance's PyPackerDetect](https://github.com/cylance/PyPackerDetect)
9
+ into a Python package with a console script.
10
+
11
+ [![PyPi](https://img.shields.io/pypi/v/pypackerdetect.svg)](https://pypi.python.org/pypi/pypackerdetect/)
12
+ [![Python Versions](https://img.shields.io/pypi/pyversions/pypackerdetect.svg)](https://pypi.python.org/pypi/pypackerdetect/)
13
+ [![Build Status](https://github.com/packing-box/pypackerdetect/actions/workflows/python-package.yml/badge.svg)](https://github.com/packing-box/pypackerdetect/actions/workflows/python-package.yml)
14
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5732866.svg)](https://doi.org/10.5281/zenodo.5732866)
15
+ [![License](https://img.shields.io/pypi/l/pypackerdetect.svg)](https://pypi.python.org/pypi/pypackerdetect)
16
+
17
+ ## Features
18
+
19
+ - **Six detection mechanisms** (see [How it works](internals.md)):
20
+ - PEiD signatures;
21
+ - known packer section names;
22
+ - entry point in a non-standard section;
23
+ - entry point in overlapping sections, or outside every section;
24
+ - too many non-standard (or invalid) section names;
25
+ - a low number of imports.
26
+ - **Two levels of findings**: *detections* are strong indicators of packing,
27
+ *suspicions* are weaker hints.
28
+ - **Configurable**: each check can be turned off, thresholds can be tuned and
29
+ the section-name and signature databases can be replaced with your own.
30
+ - **A command-line tool**, [`pypackerdetect`](cli/pypackerdetect.md), and a
31
+ [Python API](api.md).
32
+
33
+ ## Quick start
34
+
35
+ ```console
36
+ $ pip install pypackerdetect
37
+ $ pypackerdetect calc_packed.exe
38
+ Packer report for: calc_packed.exe
39
+ Detections: 3
40
+ Suspicions: 0
41
+ Log:
42
+ [DETECTION] Entry point 0xc4f80 in irregular section(s): 'UPX1'
43
+ [DETECTION] Section name 'UPX0' matches known packer: [UPX packer]
44
+ [DETECTION] Section name 'UPX1' matches known packer: [UPX packer]
45
+ ```
46
+
47
+ From Python:
48
+
49
+ ```python
50
+ from pypackerdetect import PyPackerDetect
51
+
52
+ findings = PyPackerDetect().detect("program.exe")
53
+ print(findings["detections"], findings["suspicions"])
54
+ ```
55
+
56
+ ## Related projects
57
+
58
+ PyPackerDetect is part of the [packing-box](https://github.com/packing-box) ecosystem:
59
+
60
+ - [Awesome Executable Packing](https://github.com/packing-box/awesome-executable-packing): a curated list of resources about executable packing.
61
+ - [Bintropy](https://github.com/packing-box/bintropy): estimates how likely a binary is to contain compressed or encrypted bytes.
62
+ - [Dataset of packed ELF files](https://github.com/packing-box/dataset-packed-elf) and [Dataset of packed PE files](https://github.com/packing-box/dataset-packed-pe).
63
+ - [Docker Packing Box](https://github.com/packing-box/docker-packing-box): a Docker image of packers and tools for building datasets of packed executables.
64
+ - [DSFF](https://github.com/packing-box/python-dsff): the DataSet File Format library.
65
+ - [PEiD](https://github.com/packing-box/peid): a Python implementation of the Packed Executable iDentifier, used here for signature matching.
66
+ - [REMINDer](https://github.com/packing-box/reminder): a packing detector based on a simple entropy heuristic.
67
+
68
+ ## License
69
+
70
+ PyPackerDetect is released under the
71
+ [GNU General Public License v3 or later](https://www.gnu.org/licenses/gpl-3.0.html).
72
+ The original project is © Cylance (author: Nick Cano).
@@ -0,0 +1,56 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ - Python **3.9 or later**. CI runs on 3.9 to 3.13.
6
+ - [`pefile`](https://pypi.org/project/pefile/) and
7
+ [`peid`](https://pypi.org/project/peid/) (2.4.0 or later), which pip
8
+ installs automatically.
9
+ - *Optional:* `objdump` (from GNU binutils). It is only used to resolve long
10
+ section names; see [Long section names](internals.md#long-section-names).
11
+
12
+ ## From PyPI
13
+
14
+ ```console
15
+ $ pip install pypackerdetect
16
+ ```
17
+
18
+ This installs the `pypackerdetect` Python package and the `pypackerdetect`
19
+ console script.
20
+
21
+ ## From source
22
+
23
+ ```console
24
+ $ git clone https://github.com/packing-box/pypackerdetect
25
+ $ cd pypackerdetect
26
+ $ pip install .
27
+ ```
28
+
29
+ For development, use an editable install:
30
+
31
+ ```console
32
+ $ pip install -e .
33
+ ```
34
+
35
+ ## Checking the installation
36
+
37
+ ```console
38
+ $ pypackerdetect --help
39
+ [...]
40
+ PyPackerDetect 1.2.1
41
+ [...]
42
+ ```
43
+
44
+ ```python
45
+ >>> from pypackerdetect.__info__ import __version__
46
+ >>> __version__
47
+ '1.2.1'
48
+ ```
49
+
50
+ ## Write access to the database folder
51
+
52
+ The first time a PEiD database is used, [peid](https://github.com/packing-box/peid)
53
+ writes a JSON cache of its search tree **next to the database file**. For the
54
+ embedded databases, that folder is the installed `pypackerdetect/db/` package
55
+ directory. If the folder is read-only, detection still works without the
56
+ cache, but each run rebuilds the search tree, which is slower.
@@ -0,0 +1,141 @@
1
+ # How it works
2
+
3
+ ## Overview
4
+
5
+ ```text
6
+ ┌── section names ──▶ known/unknown/packer/invalid names
7
+ program.exe ─pefile─▶├── entry point ────▶ section(s) holding it
8
+ ├── import table ───▶ number of imports
9
+ └── file ──── peid ─▶ PEiD signatures
10
+ │
11
+ ▼
12
+ {'detections': [...], 'suspicions': [...]}
13
+ ```
14
+
15
+ [pefile](https://github.com/erocarrera/pefile) parses the executable, then
16
+ each enabled check adds its findings. A **detection** is a strong indicator
17
+ of packing. A **suspicion** is a weaker hint.
18
+
19
+ ## Section names
20
+
21
+ Each section name is decoded as ASCII, then stripped of padding (null bytes
22
+ and spaces). A name that is not valid ASCII is an **invalid name**: it
23
+ produces a suspicion, and it is counted for the
24
+ [bad sections threshold](cli/pypackerdetect.md#threshold-arguments).
25
+
26
+ A valid name is then:
27
+
28
+ - **unknown** if it is not in the `known` list of `sections.json`;
29
+ - a **packer section** if it is a key of `packers.json` and is not a known
30
+ name, so that a packer entry cannot clash with a standard name.
31
+
32
+ ### Long section names
33
+
34
+ Section names are limited to 8 bytes. Some compilers (for instance MinGW with
35
+ debug information) write longer names in the COFF string table and store
36
+ `/N` in the section header, where `N` is the offset of the name in that
37
+ table. For such names, PyPackerDetect runs `objdump -h` on the file and uses
38
+ the real names. If `objdump` is not installed, fails or times out (after 10
39
+ seconds), the `/N` name is kept.
40
+
41
+ ## Entry point section
42
+
43
+ The entry point is the `AddressOfEntryPoint` field of the optional header. A
44
+ section holds it when:
45
+
46
+ ```text
47
+ VirtualAddress <= entry point < VirtualAddress + VirtualSize
48
+ ```
49
+
50
+ `SizeOfRawData` is used instead of `VirtualSize` when the latter is zero, as
51
+ the Windows loader does.
52
+
53
+ Then:
54
+
55
+ 1. A null entry point is a suspicion.
56
+ 2. If no section holds the entry point, it is a detection.
57
+ 3. If several sections hold it, they overlap, which is a detection.
58
+ 4. If none of the entry point sections is `acceptable` (`.text`), the
59
+ section is irregular, unless one of these exceptions applies:
60
+ - the file has a Delphi BSS section (`delphi-bss`) and the entry point is
61
+ in a Delphi code section (`delphi`: `.itext`, `CODE`);
62
+ - the file has no `.text` section at all and the entry point is in an
63
+ `alternative` section (`.code`, `text`, `.text0`, ...);
64
+ - the file has a `.text` section and the entry point is in a `driver`
65
+ section (`INIT`).
66
+
67
+ ## Imports
68
+
69
+ The imports of all the DLLs in the import table are counted. A count lower
70
+ than or equal to the threshold (10 by default) is a detection, as packers
71
+ usually only import what they need to unpack the payload (often
72
+ `LoadLibrary` and `GetProcAddress`). A file with no import table counts as 0
73
+ imports.
74
+
75
+ .NET assemblies (importing `mscoree.dll`) are skipped, as they always have
76
+ very few native imports.
77
+
78
+ ## PEiD signatures
79
+
80
+ The file is passed to
81
+ [`peid.identify_packer`](https://peid.readthedocs.io/en/latest/api.html#identify_packer)
82
+ with the chosen database. By default, only the signatures that apply at the
83
+ entry point are matched, using the short embedded database. Every match is
84
+ reported in one detection.
85
+
86
+ ## Findings
87
+
88
+ | Level | Message | Check |
89
+ |---|---|---|
90
+ | Suspicion | `Section name with invalid characters` | `unknown_sections` (always collected) |
91
+ | Suspicion | `Null entry point` | `bad_ep_sections` |
92
+ | Detection | `Entry point 0x… does not fall in any section` | `bad_ep_sections` |
93
+ | Detection | `Entry point 0x… falls in overlapping sections: '…'` | `bad_ep_sections` |
94
+ | Detection | `Entry point 0x… in irregular section(s): '…'` | `bad_ep_sections` |
95
+ | Detection | `Too few imports (total: N)` | `low_imports` |
96
+ | Detection | `Detected N non-standard sections: '…'` | `unknown_sections` |
97
+ | Detection | `Detected N sections with invalid names` | `unknown_sections` |
98
+ | Detection | `Section name '…' matches known packer: […]` | `packer_sections` |
99
+ | Detection | `Found PEID signature: …` | `peid` |
100
+
101
+ ## Databases
102
+
103
+ The embedded databases are in the `pypackerdetect/db/` package folder.
104
+
105
+ ### `packers.json`
106
+
107
+ Known packer section names, mostly from
108
+ [Hexacorn](https://www.hexacorn.com/blog/2016/12/15/pe-section-names-re-visited/).
109
+ The `packers` key maps each section name to a description:
110
+
111
+ ```json
112
+ {
113
+ "source": "https://www.hexacorn.com/blog/2016/12/15/pe-section-names-re-visited/",
114
+ "last_update": "Jul 11 2021",
115
+ "packers": {
116
+ "UPX0": "UPX packer",
117
+ ".MPRESS1": "Mpress Packer"
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### `sections.json`
123
+
124
+ Lists of standard section names, under the `sections` key:
125
+
126
+ | Key | Use |
127
+ |---|---|
128
+ | `known` | Standard section names; any other name is non-standard. |
129
+ | `acceptable` | Regular entry point sections. |
130
+ | `alternative` | Entry point sections accepted when there is no `.text` section. |
131
+ | `delphi-bss` | BSS sections that identify a Delphi executable. |
132
+ | `delphi` | Entry point sections accepted for Delphi executables. |
133
+ | `driver` | Entry point sections accepted for drivers. |
134
+
135
+ A custom file passed with `--sections-json` must contain all of these keys.
136
+
137
+ ### PEiD databases
138
+
139
+ `sigs_short.txt` (1,832 signatures, the default) and `sigs_long.txt` (4,287
140
+ signatures, `--peid-large-db`) are in the PEiD text format, described in the
141
+ [PEiD documentation](https://peid.readthedocs.io/en/latest/signatures.html).
@@ -0,0 +1,6 @@
1
+ jinja2
2
+ mkdocs>=1.3.0
3
+ mkdocs-bootswatch
4
+ mkdocs-material
5
+ mkdocs-rtd-dropdown
6
+ pymdown-extensions
@@ -19,7 +19,7 @@ authors = [
19
19
  description = "Re-implementation and packaging of PyPackerDetect"
20
20
  license = {file = "LICENSE"}
21
21
  keywords = ["packing", "packed-executable", "packing-detection", "pe"]
22
- requires-python = ">=3.9,<4"
22
+ requires-python = ">=3.10,<4"
23
23
  classifiers = [
24
24
  "Development Status :: 5 - Production/Stable",
25
25
  "Environment :: Console",
@@ -33,7 +33,7 @@ classifiers = [
33
33
  ]
34
34
  dependencies = [
35
35
  "pefile",
36
- "peid>=2.3.1",
36
+ "peid>=2.4.0",
37
37
  ]
38
38
  dynamic = ["version"]
39
39
 
@@ -0,0 +1,2 @@
1
+ pefile
2
+ peid>=2.4.0
@@ -0,0 +1 @@
1
+ 1.2.1
@@ -13,7 +13,7 @@ __credits__ = "Alexandre D'Hondt"
13
13
  __copyright__ = "© {} Cylance".format([__y, __s + "-" + __y][__y != __s])
14
14
  __license__ = "GPLv3 (https://www.gnu.org/licenses/gpl-3.0.fr.html)"
15
15
  __reference__ = "https://github.com/cylance/PyPackerDetect"
16
- __source__ = "https://github.com/dhondta/PyPackerDetect"
16
+ __source__ = "https://github.com/packing-box/pypackerdetect"
17
17
 
18
18
  with open(os.path.join(os.path.dirname(__file__), "VERSION.txt")) as f:
19
19
  __version__ = f.read().strip()
@@ -13,10 +13,17 @@ __all__ = ["PyPackerDetect"]
13
13
  @lru_cache
14
14
  def _real_section_names(path, logger=None, timeout=10):
15
15
  from subprocess import Popen, PIPE, TimeoutExpired
16
- p, names = Popen(["objdump", "-h", path], stdout=PIPE, stderr=PIPE), []
16
+ try:
17
+ p, names = Popen(["objdump", "-h", path], stdout=PIPE, stderr=PIPE), []
18
+ except OSError as e: # e.g. objdump is not installed
19
+ if logger:
20
+ logger.debug(f"objdump: {e}")
21
+ return
17
22
  try:
18
23
  out, err = p.communicate(timeout=timeout)
19
24
  except TimeoutExpired:
25
+ p.kill()
26
+ p.communicate()
20
27
  if logger:
21
28
  logger.debug(f"objdump: timeout ({timeout} seconds)")
22
29
  return
@@ -47,10 +54,12 @@ class PyPackerDetect:
47
54
  'packers_json': os.path.abspath(packers_json) if packers_json is not None else _p("packers.json"),
48
55
  'sections_json': os.path.abspath(sections_json) if sections_json is not None else _p("sections.json"),
49
56
  }
50
- logger.debug(f"JSON with common packer section names: {self.__config['packers_json']}")
57
+ if logger:
58
+ logger.debug(f"JSON with common packer section names: {self.__config['packers_json']}")
51
59
  with open(self.__config['packers_json']) as f:
52
60
  self.__config['packers_json'] = json.load(f)['packers']
53
- logger.debug(f"JSON with known section names: {self.__config['sections_json']}")
61
+ if logger:
62
+ logger.debug(f"JSON with known section names: {self.__config['sections_json']}")
54
63
  with open(self.__config['sections_json']) as f:
55
64
  self.__config['sections_json'] = json.load(f)['sections']
56
65
  self.__thresholds = {
@@ -61,30 +70,44 @@ class PyPackerDetect:
61
70
  self.logger = logger
62
71
 
63
72
  def detect(self, pe):
64
- """ Analyze the input PE file for suspicions and/or detections of packers. """
65
- r, s = {'detections': [], 'suspicions': []}, .0
66
- def __add(i, msg):
67
- if self.logger:
68
- self.logger.debug("%s:%s" % (['DETECTION', 'SUSPICION'][i], msg))
69
- r[['detections', 'suspicions'][i]].append(msg)
73
+ """ Analyze the input PE file (path or pefile.PE instance) for suspicions and/or detections of packers. """
70
74
  if not isinstance(pe, PE):
71
75
  path = pe
72
76
  pe = PE(path)
73
77
  pe.path = path
78
+ if getattr(pe, "path", None) is None:
79
+ # a PE instance parsed from a buffer has no path while PEiD and objdump work on files ; use a temporary copy
80
+ from tempfile import NamedTemporaryFile
81
+ with NamedTemporaryFile(suffix=".exe", delete=False) as f:
82
+ f.write(pe.__data__)
83
+ pe.path = f.name
84
+ try:
85
+ return self.detect(pe)
86
+ finally:
87
+ pe.path = None
88
+ os.remove(f.name)
89
+ r = {'detections': [], 'suspicions': []}
90
+ def __add(i, msg):
91
+ if self.logger:
92
+ self.logger.debug("%s:%s" % (['DETECTION', 'SUSPICION'][i], msg))
93
+ r[['detections', 'suspicions'][i]].append(msg)
74
94
  ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint
75
95
  # parse the input PE file
76
96
  d = {'all': [], 'bad': [], 'ep': [], 'packer': [], 'unknown': []}
77
97
  for i, s in enumerate(pe.sections):
78
98
  try:
79
- n = s.Name.decode("ascii").strip().rstrip("\0")
99
+ n = s.Name.decode("ascii").strip().rstrip("\0 ")
80
100
  # special case: for some executables compiled with debug information, some sections may be named with
81
101
  # the format "/[N]" where N is the offset in the string table ; in this case, we compute
82
102
  # the real section names and map them to the shortened ones to compare the relevant names
83
103
  # with the database of known section names
84
- if re.match(r"^\/\d+$", n) and (rsn := _real_section_names(pe.path, logger=self.logger)):
104
+ if re.match(r"^\/\d+$", n) and (rsn := _real_section_names(pe.path, logger=self.logger)) and \
105
+ i < len(rsn):
85
106
  n = rsn[i]
86
107
  d['all'].append(n)
87
- if ep >= s.VirtualAddress and ep <= (s.VirtualAddress + s.Misc_VirtualSize):
108
+ # the loader uses SizeOfRawData when VirtualSize is null ; the upper bound is excluded as it is the start
109
+ # of the next section for contiguous sections
110
+ if s.VirtualAddress <= ep < s.VirtualAddress + (s.Misc_VirtualSize or s.SizeOfRawData):
88
111
  d['ep'].append(n)
89
112
  if n not in self.__config['sections_json']['known']:
90
113
  d['unknown'].append(n)
@@ -93,7 +116,7 @@ class PyPackerDetect:
93
116
  if p is not None and n not in self.__config['sections_json']['known']:
94
117
  d['packer'].append(n)
95
118
  except UnicodeDecodeError:
96
- d['bad'].append(s.Name.decode('latin1').strip().rstrip("\0"))
119
+ d['bad'].append(s.Name.decode('latin1').strip().rstrip("\0 "))
97
120
  __add(1, "Section name with invalid characters")
98
121
  # bad entry point sections
99
122
  __intersect = lambda l1, l2: len(set(l1).intersection(l2)) > 0
@@ -103,7 +126,7 @@ class PyPackerDetect:
103
126
  __add(1, "Null entry point")
104
127
  l_ep = len(d['ep'])
105
128
  if l_ep == 0:
106
- __add(0, "Entry point 0x%x falls in overlapping sections: '%s'" % (ep, ep_sec_list))
129
+ __add(0, "Entry point 0x%x does not fall in any section" % ep)
107
130
  else:
108
131
  if l_ep > 1:
109
132
  __add(0, "Entry point 0x%x falls in overlapping sections: '%s'" % (ep, ep_sec_list))
@@ -126,11 +149,11 @@ class PyPackerDetect:
126
149
  c = 0
127
150
  try:
128
151
  for lib in pe.DIRECTORY_ENTRY_IMPORT:
129
- if lib.dll.decode("ascii").strip().rstrip("\0").lower() == "mscoree.dll":
152
+ if lib.dll.decode("latin-1").strip().rstrip("\0").lower() == "mscoree.dll":
130
153
  c = -1 # .NET assembly, counting imports is misleading as they will have a low number
131
154
  break
132
155
  c += len(lib.imports)
133
- except AttributeError as e:
156
+ except AttributeError: # no import directory
134
157
  pass
135
158
  if (0 <= c <= self.__thresholds['imports']):
136
159
  __add(0, "Too few imports (total: %d)" % c)
@@ -1,8 +1,9 @@
1
1
  # -*- coding: UTF-8 -*-
2
2
  import logging
3
- from argparse import ArgumentParser, RawTextHelpFormatter
3
+ from argparse import ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
4
4
  from ast import literal_eval
5
- from os.path import exists
5
+ from os.path import isfile
6
+ from pefile import PEFormatError
6
7
  from time import perf_counter
7
8
 
8
9
  from .__info__ import __author__, __credits__, __copyright__, __license__, __reference__, __source__, __version__
@@ -10,8 +11,8 @@ from .__init__ import PyPackerDetect
10
11
 
11
12
 
12
13
  def valid_file(path):
13
- if not exists(path):
14
- raise ValueError("input file does not exist")
14
+ if not isfile(path):
15
+ raise ArgumentTypeError(f"input file does not exist: {path}")
15
16
  return path
16
17
 
17
18
 
@@ -19,10 +20,10 @@ class PositiveInt:
19
20
  def __call__(self, string):
20
21
  try:
21
22
  n = literal_eval(string)
22
- except ValueError:
23
- raise ValueError(string)
23
+ except (SyntaxError, ValueError):
24
+ raise ArgumentTypeError(f"invalid positive int value: '{string}'")
24
25
  if not isinstance(n, int) or n < 0:
25
- raise ValueError(string)
26
+ raise ArgumentTypeError(f"invalid positive int value: '{string}'")
26
27
  return int(n)
27
28
 
28
29
  def __repr__(self):
@@ -38,7 +39,7 @@ def main():
38
39
  examples = "usage examples:\n- " + "\n- ".join([
39
40
  "pypackerdetect program.exe",
40
41
  "pypackerdetect program.exe -b",
41
- "pypackerdetect program.exe --low-imports --unknown-sections",
42
+ "pypackerdetect program.exe --low-imports --unknown-sections # disable these two checks",
42
43
  "pypackerdetect program.exe --imports-threshold 5 --bad-sections-threshold 5",
43
44
  "pypackerdetect program.exe --peid-db-path user_db.txt --sections-json sections.json",
44
45
  ])
@@ -53,20 +54,24 @@ def main():
53
54
  "(default: package-embedded JSON)")
54
55
  opt = parser.add_argument_group("optional arguments")
55
56
  opt.add_argument("--bad-ep-sections", action="store_false",
56
- help="check for bad entry point sections (default: True)")
57
- opt.add_argument("--low-imports", action="store_false", help="check for the number of imports (default: True)")
58
- opt.add_argument("--packer-sections", action="store_false", help="check for packer sections (default: True)")
59
- opt.add_argument("--peid", action="store_false", help="detect with PEiD (default: True)")
60
- opt.add_argument("--peid-ep-only", action="store_false", help="check only entry point signatures (default: True)")
57
+ help="disable the check for bad entry point sections (enabled by default)")
58
+ opt.add_argument("--low-imports", action="store_false",
59
+ help="disable the check for a low number of imports (enabled by default)")
60
+ opt.add_argument("--packer-sections", action="store_false",
61
+ help="disable the check for known packer section names (enabled by default)")
62
+ opt.add_argument("--peid", action="store_false", help="disable detection with PEiD (enabled by default)")
63
+ opt.add_argument("--peid-ep-only", action="store_false",
64
+ help="disable entry point-only matching for PEiD, scanning the whole file instead (default: False)")
61
65
  opt.add_argument("--peid-large-db", action="store_true", help="use the large database for PEiD (default: False)")
62
- opt.add_argument("--unknown-sections", action="store_false", help="check for unknown sections (default: True)")
66
+ opt.add_argument("--unknown-sections", action="store_false",
67
+ help="disable the check for unknown section names (enabled by default)")
63
68
  thrs = parser.add_argument_group("threshold arguments")
64
- thrs.add_argument("--bad-sections-threshold", dest="bst", type=PositiveInt, default=2,
65
- help="threshold for the number of bad sections (default: 2)")
66
- thrs.add_argument("--imports-threshold", dest="it", type=PositiveInt, default=10,
69
+ thrs.add_argument("--bad-sections-threshold", dest="bad_sections_threshold", metavar="N", type=PositiveInt(),
70
+ default=2, help="threshold for the number of bad sections (default: 2)")
71
+ thrs.add_argument("--imports-threshold", dest="import_threshold", metavar="N", type=PositiveInt(), default=10,
67
72
  help="threshold for the minimum number of imports (default: 10)")
68
- thrs.add_argument("--unknown-sections-threshold", dest="ust", type=PositiveInt, default=3,
69
- help="threshold for the number of unknown sections (default: 3)")
73
+ thrs.add_argument("--unknown-sections-threshold", dest="unknown_sections_threshold", metavar="N",
74
+ type=PositiveInt(), default=3, help="threshold for the number of unknown sections (default: 3)")
70
75
  extra = parser.add_argument_group("extra arguments")
71
76
  extra.add_argument("-b", "--benchmark", action="store_true",
72
77
  help="enable benchmarking, output in seconds (default: False)")
@@ -86,11 +91,11 @@ def main():
86
91
  PyPackerDetect.report(args.path, r)
87
92
  if dt != "":
88
93
  print(dt)
94
+ except PEFormatError as e:
95
+ args.logger.error(f"Not a valid PE file ({e.value})")
96
+ code = 1
89
97
  except Exception as e:
90
- if "magic not found." in str(e):
91
- e.value = "Not a PE file"
92
- if str(e) != "no result":
93
- args.logger.exception(e)
98
+ args.logger.exception(e)
94
99
  code = 1
95
100
  return code
96
101
 
@@ -50,7 +50,7 @@
50
50
  ".debug$P",
51
51
  ".debug$S",
52
52
  ".debug$T",
53
- ".drectve ",
53
+ ".drectve",
54
54
  ".didat",
55
55
  ".didata",
56
56
  ".edata",
@@ -94,7 +94,7 @@
94
94
  ".vsdata",
95
95
  ".xdata",
96
96
  ".wixburn",
97
- ".wpp_sf ",
97
+ ".wpp_sf",
98
98
  "BSS",
99
99
  "CODE",
100
100
  "DATA",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypackerdetect
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: Re-implementation and packaging of PyPackerDetect
5
5
  Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -691,14 +691,14 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
691
691
  Classifier: Programming Language :: Python :: 3
692
692
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
693
693
  Classifier: Topic :: Security
694
- Requires-Python: <4,>=3.9
694
+ Requires-Python: <4,>=3.10
695
695
  Description-Content-Type: text/markdown
696
696
  License-File: LICENSE
697
697
  Requires-Dist: pefile
698
- Requires-Dist: peid>=2.3.1
698
+ Requires-Dist: peid>=2.4.0
699
699
  Dynamic: license-file
700
700
 
701
- <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/logo.png"></p>
701
+ <p align="center"><img src="https://github.com/packing-box/PyPackerDetect/raw/main/docs/pages/img/logo.png"></p>
702
702
  <h1 align="center">PyPackerDetect <a href="https://twitter.com/intent/tweet?text=PyPackerDetect%20-%20Packer%20detector%20using%20multiple%20heuristics%20and%20PEiD's%20signatures%20database.%0D%0Ahttps%3a%2f%2fgithub%2ecom%2fpacking-box%2fPyPackerDetect%0D%0A&hashtags=python,pe,peid,packer,packingdetection"><img src="https://img.shields.io/badge/Tweet--lightgrey?logo=twitter&style=social" alt="Tweet" height="20"/></a></h1>
703
703
  <h3 align="center">Detect packers on PE files using heuristics and signatures.</h3>
704
704
 
@@ -723,8 +723,9 @@ $ pypackerdetect --help
723
723
  usage examples:
724
724
  - pypackerdetect program.exe
725
725
  - pypackerdetect program.exe -b
726
- - pypackerdetect program.exe --low-imports --unknown-sections
726
+ - pypackerdetect program.exe --low-imports --unknown-sections # disable these two checks
727
727
  - pypackerdetect program.exe --imports-threshold 5 --bad-sections-threshold 5
728
+ - pypackerdetect program.exe --peid-db-path user_db.txt --sections-json sections.json
728
729
  ```
729
730
 
730
731
  ## Detection Mechanisms
@@ -732,7 +733,7 @@ usage examples:
732
733
  - PEID signatures
733
734
  - Known packer section names
734
735
  - Entrypoint in non-standard section
735
- - Threshhold of non-standard sections reached
736
+ - Threshold of non-standard sections reached
736
737
  - Low number of imports
737
738
  - Overlapping entrypoint sections
738
739
 
@@ -1,10 +1,20 @@
1
1
  .gitignore
2
+ .readthedocs.yaml
2
3
  LICENSE
3
4
  README.md
4
5
  pyproject.toml
5
6
  requirements.txt
6
7
  .github/workflows/python-package.yml
7
- docs/logo.png
8
+ docs/mkdocs.yml
9
+ docs/requirements.txt
10
+ docs/pages/api.md
11
+ docs/pages/contributing.md
12
+ docs/pages/index.md
13
+ docs/pages/installation.md
14
+ docs/pages/internals.md
15
+ docs/pages/cli/pypackerdetect.md
16
+ docs/pages/img/icon.png
17
+ docs/pages/img/logo.png
8
18
  src/pypackerdetect/VERSION.txt
9
19
  src/pypackerdetect/__info__.py
10
20
  src/pypackerdetect/__init__.py
@@ -0,0 +1,2 @@
1
+ pefile
2
+ peid>=2.4.0
@@ -1,2 +0,0 @@
1
- pefile
2
- peid>=2.2.1
@@ -1 +0,0 @@
1
- 1.2.0
@@ -1,2 +0,0 @@
1
- pefile
2
- peid>=2.3.1
File without changes
File without changes