mirror of
https://github.com/QuasarApp/pe-parse.git
synced 2025-04-26 04:14:32 +00:00
Release 1.0 prep work (#113)
Co-authored-by: Eric Kilmer <eric.d.kilmer@gmail.com>
This commit is contained in:
parent
c5e9a09087
commit
1dc2c53566
74
.github/workflows/ci.yml
vendored
74
.github/workflows/ci.yml
vendored
@ -10,10 +10,11 @@ on:
|
|||||||
- cron: '0 12 * * *'
|
- cron: '0 12 * * *'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
pe-parse:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: ["ubuntu-latest", "macos-latest"]
|
platform: ["ubuntu-latest", "macos-latest"]
|
||||||
|
build-type: ["Debug", "Release"]
|
||||||
compiler:
|
compiler:
|
||||||
- { CC: "clang", CXX: "clang++" }
|
- { CC: "clang", CXX: "clang++" }
|
||||||
- { CC: "gcc", CXX: "g++" }
|
- { CC: "gcc", CXX: "g++" }
|
||||||
@ -23,27 +24,80 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Build C
|
- name: build
|
||||||
env:
|
env:
|
||||||
CC: ${{ matrix.compiler.CC }}
|
CC: ${{ matrix.compiler.CC }}
|
||||||
CXX: ${{ matrix.compiler.CXX }}
|
CXX: ${{ matrix.compiler.CXX }}
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake ..
|
cmake -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} ..
|
||||||
make
|
cmake --build .
|
||||||
- name: Build Python
|
- name: test
|
||||||
|
run: |
|
||||||
|
./build/dump-pe/dump-pe ./test/assets/example.exe
|
||||||
|
|
||||||
|
pepy:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: ["ubuntu-latest", "macos-latest"]
|
||||||
|
python:
|
||||||
|
- "3.6"
|
||||||
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
cd python
|
|
||||||
python2 setup.py build
|
|
||||||
python3 setup.py build
|
python3 setup.py build
|
||||||
test-windows:
|
- name: sdist and install
|
||||||
|
run: |
|
||||||
|
python3 setup.py sdist
|
||||||
|
python3 -m pip install --user dist/*.tar.gz
|
||||||
|
- name: test
|
||||||
|
run: |
|
||||||
|
python3 test/test_pepy.py test/assets/example.exe
|
||||||
|
|
||||||
|
pe-parse-windows:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build-type: ["Debug", "Release"]
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Build C
|
- name: build
|
||||||
run: |
|
run: |
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -G "Visual Studio 16 2019" -A x64 ..
|
cmake -G "Visual Studio 16 2019" -A x64 ..
|
||||||
cmake --build .
|
cmake --build . --config ${{ matrix.build-type }}
|
||||||
|
- name: test
|
||||||
|
run: |
|
||||||
|
.\build\dump-pe\${{ matrix.build-type }}\dump-pe.exe .\test\assets\example.exe
|
||||||
|
|
||||||
|
pepy-windows:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python:
|
||||||
|
- "3.6"
|
||||||
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
python setup.py build
|
||||||
|
- name: install
|
||||||
|
run: |
|
||||||
|
python -m pip install --user .
|
||||||
|
- name: test
|
||||||
|
run: |
|
||||||
|
python test/test_pepy.py test/assets/example.exe
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -2,7 +2,7 @@ Makefile
|
|||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
dump-prog/dump-prog
|
dump-prog/dump-prog
|
||||||
*.swp
|
*.swp
|
||||||
python/build
|
build/
|
||||||
.idea
|
.idea
|
||||||
cmake-build-debug
|
cmake-build-debug
|
||||||
cmake-build-release
|
cmake-build-release
|
||||||
@ -11,4 +11,7 @@ CMakeSettings.json
|
|||||||
.vs
|
.vs
|
||||||
.vscode
|
.vscode
|
||||||
examples_build
|
examples_build
|
||||||
|
.DS_Store
|
||||||
|
dist/
|
||||||
|
MANIFEST
|
||||||
|
*.egg-info/
|
||||||
|
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
include VERSION
|
||||||
|
include pepy/README.md
|
||||||
|
include pe-parser-library/include/parser-library/*.h
|
85
README.md
85
README.md
@ -1,9 +1,14 @@
|
|||||||
pe-parse
|
pe-parse
|
||||||
=========================================
|
========
|
||||||
|
|
||||||
[](https://github.com/trailofbits/pe-parse/actions?query=workflow%3ACI)
|
[](https://github.com/trailofbits/pe-parse/actions?query=workflow%3ACI)
|
||||||
|
|
||||||
pe-parse is a principled, lightweight parser for windows portable executable files. It was created to assist in compiled program analysis, potentially of programs of unknown origins. This means that it should be resistant to malformed or maliciously crafted PE files, and it should support questions that analysis software would ask of an executable program container. For example, listing relocations, describing imports and exports, and supporting byte reads from virtual addresses as well as file offsets.
|
pe-parse is a principled, lightweight parser for windows portable executable files.
|
||||||
|
It was created to assist in compiled program analysis, potentially of programs of unknown origins.
|
||||||
|
This means that it should be resistant to malformed or maliciously crafted PE files, and it should
|
||||||
|
support questions that analysis software would ask of an executable program container.
|
||||||
|
For example, listing relocations, describing imports and exports, and supporting byte reads from
|
||||||
|
virtual addresses as well as file offsets.
|
||||||
|
|
||||||
pe-parse supports these use cases via a minimal API that provides methods for
|
pe-parse supports these use cases via a minimal API that provides methods for
|
||||||
* Opening and closing a PE file
|
* Opening and closing a PE file
|
||||||
@ -15,20 +20,34 @@ pe-parse supports these use cases via a minimal API that provides methods for
|
|||||||
* Reading bytes from specified virtual addresses
|
* Reading bytes from specified virtual addresses
|
||||||
* Retrieving the program entry point
|
* Retrieving the program entry point
|
||||||
|
|
||||||
The interface is defined in `parser-library/parse.h`. The program in `dump-prog/dump.cpp` is an example of using the parser-library API to dump information about a PE file.
|
The interface is defined in `parser-library/parse.h`.
|
||||||
|
|
||||||
Internally, the parser-library uses a bounded buffer abstraction to access information stored in the PE file. This should help in constructing a sane parser that allows for detection of the use of bogus values in the PE that would result in out of bounds accesses of the input buffer. Once data is read from the file it is sanitized and placed in C++ STL containers of internal types.
|
The program in `dump-prog/dump.cpp` is an example of using the parser-library API to dump
|
||||||
|
information about a PE file.
|
||||||
|
|
||||||
|
Internally, the parser-library uses a bounded buffer abstraction to access information stored in
|
||||||
|
the PE file. This should help in constructing a sane parser that allows for detection of the use
|
||||||
|
of bogus values in the PE that would result in out of bounds accesses of the input buffer.
|
||||||
|
Once data is read from the file it is sanitized and placed in C++ STL containers of internal types.
|
||||||
|
|
||||||
|
pe-parse includes Python bindings via `pepy`, which can be installed via `pip`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pip3 install pepy
|
||||||
|
```
|
||||||
|
|
||||||
|
More information about `pepy` can be found in its [README](./pepy/README.md).
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
Dependencies
|
|
||||||
========
|
|
||||||
### CMake
|
### CMake
|
||||||
* Debian/Ubuntu: `sudo apt-get install cmake`
|
* Debian/Ubuntu: `sudo apt-get install cmake`
|
||||||
* RedHat/Fedora: `sudo yum install cmake`
|
* RedHat/Fedora: `sudo yum install cmake`
|
||||||
* OSX: `brew install cmake`
|
* OSX: `brew install cmake`
|
||||||
* Windows: Download the installer from the [CMake page](https://cmake.org/download/)
|
* Windows: Download the installer from the [CMake page](https://cmake.org/download/)
|
||||||
|
|
||||||
Building
|
## Building
|
||||||
========
|
|
||||||
### Generic instructions
|
### Generic instructions
|
||||||
```
|
```
|
||||||
git clone https://github.com/trailofbits/pe-parse.git
|
git clone https://github.com/trailofbits/pe-parse.git
|
||||||
@ -38,37 +57,48 @@ mkdir build
|
|||||||
cd build
|
cd build
|
||||||
|
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
cmake --build . --config Release
|
cmake --build .
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
cmake --build . --config Release --target install
|
cmake --build . --target install
|
||||||
```
|
```
|
||||||
|
|
||||||
PE files that have a Resource section with strings for the Type are encoded in UTF-16, but that `std::string` expects UTF-8. Some cross-platform solution
|
PE files that have a Resource section with strings for the Type are encoded in UTF-16, but that
|
||||||
is desired. You can let cmake choose one it finds in your build environment or you can choose one from the following options yourself and specify it with
|
`std::string` expects UTF-8. Some cross-platform solution is desired.
|
||||||
the `-DUNICODE_LIBRARY` argument when generating the project files with cmake:
|
|
||||||
* `icu` (preferred) - "[ICU](http://site.icu-project.org/) is a mature, widely used set of C/C++ and Java libraries providing Unicode and Globalization support for software applications"
|
You can let `cmake` choose one it finds in your build environment or you can choose one from the
|
||||||
* `codecvt` - A C++ library header file ([now deprecated](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0618r0.html)) supported by some C++ runtimes
|
following options yourself and specify it with the `-DUNICODE_LIBRARY` argument when generating the
|
||||||
|
project files with `cmake`:
|
||||||
|
|
||||||
|
* `icu` (preferred) - "[ICU](http://site.icu-project.org/) is a mature, widely used set of C/C++
|
||||||
|
and Java libraries providing Unicode and Globalization support for software applications"
|
||||||
|
* `codecvt` - A C++ library header file
|
||||||
|
([now deprecated](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0618r0.html)) supported
|
||||||
|
by some C++ runtimes
|
||||||
|
|
||||||
### Notes about Windows
|
### Notes about Windows
|
||||||
|
|
||||||
If you are building on Windows with Visual Studio, the generator option can be used to select the compiler version and the output architecture:
|
If you are building on Windows with Visual Studio, the generator option can be used to select the
|
||||||
|
compiler version and the output architecture:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Compile 64-bit binaries with Visual Studio 2017
|
# Compile 64-bit binaries with Visual Studio 2017
|
||||||
cmake -G "Visual Studio 15 2017 Win64" -DCMAKE_BUILD_TYPE=Release ..
|
cmake -G "Visual Studio 15 2017 Win64" ..
|
||||||
|
|
||||||
# Compile 32-bit binaries with Visual Studio 2017
|
# Compile 32-bit binaries with Visual Studio 2017
|
||||||
cmake -G "Visual Studio 15 2017" -DCMAKE_BUILD_TYPE=Release ..
|
cmake -G "Visual Studio 15 2017" ..
|
||||||
```
|
```
|
||||||
|
|
||||||
Visual Studio 2015 or higher is required to use codecvt, but you also have the option of using [ICU](http://site.icu-project.org/). The easiest way to
|
Visual Studio 2015 or higher is required to use codecvt, but you also have the option of using
|
||||||
get started with ICU in Windows is with [vcpkg](https://vcpkg.readthedocs.io/): `vcpkg install icu`. Then add the
|
[ICU](http://site.icu-project.org/). The easiest way to get started with ICU in Windows is with
|
||||||
`-DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake` argument when generating the project files with cmake to add the appropriate
|
[vcpkg](https://vcpkg.readthedocs.io/): `vcpkg install icu`.
|
||||||
library and include directories to the project.
|
|
||||||
|
Then, add the `-DCMAKE_TOOLCHAIN_FILE=C:\src\vcpkg\scripts\buildsystems\vcpkg.cmake` argument when
|
||||||
|
generating the project files with cmake to add the appropriate library and include directories to
|
||||||
|
the project.
|
||||||
|
|
||||||
|
## Using the library
|
||||||
|
|
||||||
Using the library
|
|
||||||
=======
|
|
||||||
Once the library is installed, linking to it is easy! Add the following lines in your CMake project:
|
Once the library is installed, linking to it is easy! Add the following lines in your CMake project:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -80,6 +110,7 @@ target_include_directories(your_target_name PRIVATE ${PEPARSE_INCLUDE_DIRS})
|
|||||||
|
|
||||||
You can see a full example in the examples/peaddrconv folder.
|
You can see a full example in the examples/peaddrconv folder.
|
||||||
|
|
||||||
Authors
|
## Authors
|
||||||
=======
|
|
||||||
pe-parse was designed and implemented by Andrew Ruef (andrew@trailofbits.com), with significant contributions from [Wesley Shields](https://github.com/wxsBSD).
|
pe-parse was designed and implemented by Andrew Ruef (andrew@trailofbits.com), with significant
|
||||||
|
contributions from [Wesley Shields](https://github.com/wxsBSD).
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
project(pe-parser-library)
|
project(pe-parser-library)
|
||||||
|
|
||||||
|
message(STATUS "VERSION file: ${CMAKE_SOURCE_DIR}/VERSION")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_SOURCE_DIR}/VERSION" PEPARSE_VERSION)
|
||||||
|
string(STRIP "${PEPARSE_VERSION}" PEPARSE_VERSION)
|
||||||
|
add_compile_definitions(PEPARSE_VERSION="${PEPARSE_VERSION}")
|
||||||
|
|
||||||
set(UNICODE_LIBRARY "any" CACHE STRING "Select a unicode library")
|
set(UNICODE_LIBRARY "any" CACHE STRING "Select a unicode library")
|
||||||
set_property(CACHE UNICODE_LIBRARY PROPERTY STRINGS "any" "icu" "codecvt")
|
set_property(CACHE UNICODE_LIBRARY PROPERTY STRINGS "any" "icu" "codecvt")
|
||||||
|
|
||||||
|
204
pepy/README.md
Normal file
204
pepy/README.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
pepy
|
||||||
|
====
|
||||||
|
pepy (pronounced p-pie) is a python binding to the pe-parse parser.
|
||||||
|
|
||||||
|
pepy supports Python versions 3.6 and above.
|
||||||
|
|
||||||
|
The easiest way to use pepy is to install it via pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pip3 install pepy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
If you can build pe-parse and have a working python environment (headers and
|
||||||
|
libraries) you can build pepy.
|
||||||
|
|
||||||
|
1. Build pepy:
|
||||||
|
* `python3 setup.py build`
|
||||||
|
2. Install pepy:
|
||||||
|
* `python3 setup.py install`
|
||||||
|
|
||||||
|
**Building on Windows:** Python 3.x is typically installed as _python.exe_,
|
||||||
|
**NOT** _python3.exe_.
|
||||||
|
|
||||||
|
## Using
|
||||||
|
|
||||||
|
### Parsed object
|
||||||
|
|
||||||
|
There are a number of objects involved in pepy. The main one is the **parsed**
|
||||||
|
object. This object is returned by the *parse* method.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pepy
|
||||||
|
p = pepy.parse("/path/to/exe")
|
||||||
|
```
|
||||||
|
|
||||||
|
The **parsed** object has a number of methods:
|
||||||
|
|
||||||
|
* `get_entry_point`: Return the entry point address
|
||||||
|
* `get_machine_as_str`: Return the machine as a human readable string
|
||||||
|
* `get_subsystem_as_str`: Return the subsystem as a human readable string
|
||||||
|
* `get_bytes`: Return the first N bytes at a given address
|
||||||
|
* `get_sections`: Return a list of section objects
|
||||||
|
* `get_imports`: Return a list of import objects
|
||||||
|
* `get_exports`: Return a list of export objects
|
||||||
|
* `get_relocations`: Return a list of relocation objects
|
||||||
|
* `get_resources`: Return a list of resource objects
|
||||||
|
|
||||||
|
The **parsed** object has a number of attributes:
|
||||||
|
|
||||||
|
* `signature`
|
||||||
|
* `machine`
|
||||||
|
* `numberofsections`
|
||||||
|
* `timedatestamp`
|
||||||
|
* `numberofsymbols`
|
||||||
|
* `characteristics`
|
||||||
|
* `magic`
|
||||||
|
* `majorlinkerver`
|
||||||
|
* `minorlinkerver`
|
||||||
|
* `codesize`
|
||||||
|
* `initdatasize`
|
||||||
|
* `uninitdatasize`
|
||||||
|
* `entrypointaddr`
|
||||||
|
* `baseofcode`
|
||||||
|
* `baseofdata`
|
||||||
|
* `imagebase`
|
||||||
|
* `sectionalignement`
|
||||||
|
* `filealignment`
|
||||||
|
* `majorosver`
|
||||||
|
* `minorosver`
|
||||||
|
* `win32ver`
|
||||||
|
* `imagesize`
|
||||||
|
* `headersize`
|
||||||
|
* `checksum`
|
||||||
|
* `subsystem`
|
||||||
|
* `dllcharacteristics`
|
||||||
|
* `stackreservesize`
|
||||||
|
* `stackcommitsize`
|
||||||
|
* `heapreservesize`
|
||||||
|
* `heapcommitsize`
|
||||||
|
* `loaderflags`
|
||||||
|
* `rvasandsize`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import time
|
||||||
|
import pepy
|
||||||
|
|
||||||
|
p = pepy.parse("/path/to/exe")
|
||||||
|
print("Timedatestamp: %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.timedatestamp)))
|
||||||
|
ep = p.get_entry_point()
|
||||||
|
print("Entry point: 0x%x" % ep)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `get_sections`, `get_imports`, `get_exports`, `get_relocations` and
|
||||||
|
`get_resources` methods each return a list of objects. The type of object
|
||||||
|
depends upon the method called. `get_sections` returns a list of `section`
|
||||||
|
objects, `get_imports` returns a list of `import` objects, etc.
|
||||||
|
|
||||||
|
### Section Object
|
||||||
|
|
||||||
|
The `section` object has the following attributes:
|
||||||
|
|
||||||
|
* `base`
|
||||||
|
* `length`
|
||||||
|
* `virtaddr`
|
||||||
|
* `virtsize`
|
||||||
|
* `numrelocs`
|
||||||
|
* `numlinenums`
|
||||||
|
* `characteristics`
|
||||||
|
* `data`
|
||||||
|
|
||||||
|
### Import Object
|
||||||
|
|
||||||
|
The `import` object has the following attributes:
|
||||||
|
|
||||||
|
* `sym`
|
||||||
|
* `name`
|
||||||
|
* `addr`
|
||||||
|
|
||||||
|
### Export Object
|
||||||
|
|
||||||
|
The `export` object has the following attributes:
|
||||||
|
|
||||||
|
* `mod`
|
||||||
|
* `func`
|
||||||
|
* `addr`
|
||||||
|
|
||||||
|
### Relocation Object
|
||||||
|
|
||||||
|
The `relocation` object has the following attributes:
|
||||||
|
|
||||||
|
* `type`
|
||||||
|
* `addr`
|
||||||
|
|
||||||
|
### Resource Object
|
||||||
|
|
||||||
|
The `resource` object has the following attributes:
|
||||||
|
|
||||||
|
* `type_str`
|
||||||
|
* `name_str`
|
||||||
|
* `lang_str`
|
||||||
|
* `type`
|
||||||
|
* `name`
|
||||||
|
* `lang`
|
||||||
|
* `codepage`
|
||||||
|
* `RVA`
|
||||||
|
* `size`
|
||||||
|
* `data`
|
||||||
|
|
||||||
|
The `resource` object has the following methods:
|
||||||
|
|
||||||
|
* `type_as_str`
|
||||||
|
|
||||||
|
Resources are stored in a directory structure. The first three levels of the
|
||||||
|
are called `type`, `name` and `lang`. Each of these levels can have
|
||||||
|
either a pre-defined value or a custom string. The pre-defined values are
|
||||||
|
stored in the `type`, `name` and `lang` attributes. If a custom string is
|
||||||
|
found it will be stored in the `type_str`, `name_str` and `lang_str`
|
||||||
|
attributes. The `type_as_str` method can be used to convert a pre-defined
|
||||||
|
type value to a string representation.
|
||||||
|
|
||||||
|
The following code shows how to iterate through resources:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pepy
|
||||||
|
|
||||||
|
from hashlib import md5
|
||||||
|
import sys
|
||||||
|
|
||||||
|
p = pepy.parse(sys.argv[1])
|
||||||
|
resources = p.get_resources()
|
||||||
|
print("Resources: (%i)" % len(resources))
|
||||||
|
for resource in resources:
|
||||||
|
print("[+] MD5: (%i) %s" % (len(resource.data), md5(resource.data).hexdigest()))
|
||||||
|
if resource.type_str:
|
||||||
|
print("\tType string: %s" % resource.type_str)
|
||||||
|
else:
|
||||||
|
print("\tType: %s (%s)" % (hex(resource.type), resource.type_as_str()))
|
||||||
|
if resource.name_str:
|
||||||
|
print("\tName string: %s" % resource.name_str)
|
||||||
|
else:
|
||||||
|
print("\tName: %s" % hex(resource.name))
|
||||||
|
if resource.lang_str:
|
||||||
|
print("\tLang string: %s" % resource.lang_str)
|
||||||
|
else:
|
||||||
|
print("\tLang: %s" % hex(resource.lang))
|
||||||
|
print("\tCodepage: %s" % hex(resource.codepage))
|
||||||
|
print("\tRVA: %s" % hex(resource.RVA))
|
||||||
|
print("\tSize: %s" % hex(resource.size))
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that some binaries (particularly packed) may have corrupt resource entries.
|
||||||
|
In these cases you may find that `len(resource.data)` is 0 but `resource.size` is
|
||||||
|
greater than 0. The `size` attribute is the size of the data as declared by the
|
||||||
|
resource data entry.
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
pe-parse was designed and implemented by Andrew Ruef (andrew@trailofbits.com).
|
||||||
|
|
||||||
|
pepy was written by Wesley Shields (wxs@atarininja.org).
|
@ -31,7 +31,9 @@
|
|||||||
|
|
||||||
using namespace peparse;
|
using namespace peparse;
|
||||||
|
|
||||||
#define PEPY_VERSION "0.3"
|
/* NOTE(ww): These don't necessarily have to be the same, but currently are.
|
||||||
|
*/
|
||||||
|
#define PEPY_VERSION PEPARSE_VERSION
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add some definition for compatibility between python2 and python3
|
* Add some definition for compatibility between python2 and python3
|
||||||
@ -1384,6 +1386,8 @@ static PyObject *pepi_module_init(void) {
|
|||||||
PyModule_AddObject(m, "pepy_resource", (PyObject *) &pepy_resource_type);
|
PyModule_AddObject(m, "pepy_resource", (PyObject *) &pepy_resource_type);
|
||||||
|
|
||||||
PyModule_AddStringMacro(m, PEPY_VERSION);
|
PyModule_AddStringMacro(m, PEPY_VERSION);
|
||||||
|
PyModule_AddStringMacro(m, PEPARSE_VERSION);
|
||||||
|
PyModule_AddStringConstant(m, "__version__", PEPY_VERSION);
|
||||||
|
|
||||||
PyModule_AddIntMacro(m, MZ_MAGIC);
|
PyModule_AddIntMacro(m, MZ_MAGIC);
|
||||||
PyModule_AddIntMacro(m, NT_MAGIC);
|
PyModule_AddIntMacro(m, NT_MAGIC);
|
217
python/README.md
217
python/README.md
@ -1,217 +0,0 @@
|
|||||||
pepy
|
|
||||||
====
|
|
||||||
pepy (pronounced p-pie) is a python binding to the pe-parse parser.
|
|
||||||
|
|
||||||
Building
|
|
||||||
========
|
|
||||||
If you can build pe-parse and have a working python environment (headers and
|
|
||||||
libraries) you can build pepy.
|
|
||||||
|
|
||||||
Python 2.7
|
|
||||||
----------
|
|
||||||
1. Build pepy:
|
|
||||||
* python setup.py build
|
|
||||||
2. Install pepy:
|
|
||||||
* python setup.py install
|
|
||||||
|
|
||||||
**Building on Windows:** If you get a build error of 'Unable to find
|
|
||||||
vcvarsall.bat', you must set the `VS90COMNTOOLS` environment variable prior
|
|
||||||
to the appropriate path as per
|
|
||||||
[this SO article](http://stackoverflow.com/questions/2817869/error-unable-to-find-vcvarsall-bat):
|
|
||||||
> While running setup.py for package installations, Python 2.7 searches for an
|
|
||||||
> installed Visual Studio 2008. You can trick Python to use a newer Visual
|
|
||||||
> Studio by setting the correct path in VS90COMNTOOLS environment variable
|
|
||||||
> before calling setup.py.
|
|
||||||
>
|
|
||||||
> Execute the following command based on the version of Visual Studio installed:
|
|
||||||
> * Visual Studio 2010 (VS10): `SET VS90COMNTOOLS=%VS100COMNTOOLS%`
|
|
||||||
> * Visual Studio 2012 (VS11): `SET VS90COMNTOOLS=%VS110COMNTOOLS%`
|
|
||||||
> * Visual Studio 2013 (VS12): `SET VS90COMNTOOLS=%VS120COMNTOOLS%`
|
|
||||||
> * Visual Studio 2015/2017 (VS14): `SET VS90COMNTOOLS=%VS140COMNTOOLS%`
|
|
||||||
|
|
||||||
Python 3.x
|
|
||||||
----------
|
|
||||||
1. Build pepy:
|
|
||||||
* python3 setup.py build
|
|
||||||
2. Install pepy:
|
|
||||||
* python3 setup.py install
|
|
||||||
|
|
||||||
**Building on Windows:** Python 3.x is typically installed as _python.exe_
|
|
||||||
**NOT** _python3.exe_.
|
|
||||||
|
|
||||||
Using
|
|
||||||
=====
|
|
||||||
Parsed object
|
|
||||||
-------------
|
|
||||||
There are a number of objects involved in pepy. The main one is the **parsed**
|
|
||||||
object. This object is returned by the *parse* method.
|
|
||||||
|
|
||||||
```
|
|
||||||
import pepy
|
|
||||||
p = pepy.parse("/path/to/exe")
|
|
||||||
```
|
|
||||||
|
|
||||||
The **parsed** object has a number of methods:
|
|
||||||
|
|
||||||
* get_entry_point: Return the entry point address
|
|
||||||
* get_machine_as_str: Return the machine as a human readable string
|
|
||||||
* get_subsystem_as_str: Return the subsystem as a human readable string
|
|
||||||
* get_bytes: Return the first N bytes at a given address
|
|
||||||
* get_sections: Return a list of section objects
|
|
||||||
* get_imports: Return a list of import objects
|
|
||||||
* get_exports: Return a list of export objects
|
|
||||||
* get_relocations: Return a list of relocation objects
|
|
||||||
* get_resources: Return a list of resource objects
|
|
||||||
|
|
||||||
The **parsed** object has a number of attributes:
|
|
||||||
|
|
||||||
* signature
|
|
||||||
* machine
|
|
||||||
* numberofsections
|
|
||||||
* timedatestamp
|
|
||||||
* numberofsymbols
|
|
||||||
* characteristics
|
|
||||||
* magic
|
|
||||||
* majorlinkerver
|
|
||||||
* minorlinkerver
|
|
||||||
* codesize
|
|
||||||
* initdatasize
|
|
||||||
* uninitdatasize
|
|
||||||
* entrypointaddr
|
|
||||||
* baseofcode
|
|
||||||
* baseofdata
|
|
||||||
* imagebase
|
|
||||||
* sectionalignement
|
|
||||||
* filealignment
|
|
||||||
* majorosver
|
|
||||||
* minorosver
|
|
||||||
* win32ver
|
|
||||||
* imagesize
|
|
||||||
* headersize
|
|
||||||
* checksum
|
|
||||||
* subsystem
|
|
||||||
* dllcharacteristics
|
|
||||||
* stackreservesize
|
|
||||||
* stackcommitsize
|
|
||||||
* heapreservesize
|
|
||||||
* heapcommitsize
|
|
||||||
* loaderflags
|
|
||||||
* rvasandsize
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
import time
|
|
||||||
import pepy
|
|
||||||
|
|
||||||
p = pepy.parse("/path/to/exe")
|
|
||||||
print "Timedatestamp: %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.timedatestamp))
|
|
||||||
ep = p.get_entry_point()
|
|
||||||
print "Entry point: 0x%x" % ep
|
|
||||||
```
|
|
||||||
|
|
||||||
The *get_sections*, *get_imports*, *get_exports*, *get_relocations* and
|
|
||||||
*get_resources* methods each return a list of objects. The type of object
|
|
||||||
depends upon the method called. *get_sections* returns a list of **section**
|
|
||||||
objects, *get_imports* returns a list of **import** objects, etc.
|
|
||||||
|
|
||||||
Section Object
|
|
||||||
--------------
|
|
||||||
The **section** object has the following attributes:
|
|
||||||
|
|
||||||
* base
|
|
||||||
* length
|
|
||||||
* virtaddr
|
|
||||||
* virtsize
|
|
||||||
* numrelocs
|
|
||||||
* numlinenums
|
|
||||||
* characteristics
|
|
||||||
* data
|
|
||||||
|
|
||||||
Import Object
|
|
||||||
-------------
|
|
||||||
The **import** object has the following attributes:
|
|
||||||
|
|
||||||
* sym
|
|
||||||
* name
|
|
||||||
* addr
|
|
||||||
|
|
||||||
Export Object
|
|
||||||
-------------
|
|
||||||
The **export** object has the following attributes:
|
|
||||||
|
|
||||||
* mod
|
|
||||||
* func
|
|
||||||
* addr
|
|
||||||
|
|
||||||
Relocation Object
|
|
||||||
-----------------
|
|
||||||
The **relocation** object has the following attributes:
|
|
||||||
|
|
||||||
* type
|
|
||||||
* addr
|
|
||||||
|
|
||||||
Resource Object
|
|
||||||
---------------
|
|
||||||
The **resource** object has the following attributes:
|
|
||||||
|
|
||||||
* type_str
|
|
||||||
* name_str
|
|
||||||
* lang_str
|
|
||||||
* type
|
|
||||||
* name
|
|
||||||
* lang
|
|
||||||
* codepage
|
|
||||||
* RVA
|
|
||||||
* size
|
|
||||||
* data
|
|
||||||
|
|
||||||
The **resource** object has the following methods:
|
|
||||||
|
|
||||||
* type_as_str
|
|
||||||
|
|
||||||
Resources are stored in a directory structure. The first three levels of the
|
|
||||||
are called **type**, **name** and **lang**. Each of these levels can have
|
|
||||||
either a pre-defined value or a custom string. The pre-defined values are
|
|
||||||
stored in the *type*, *name* and *lang* attributes. If a custom string is
|
|
||||||
found it will be stored in the *type_str*, *name_str* and *lang_str*
|
|
||||||
attributes. The *type_as_str* method can be used to convert a pre-defined
|
|
||||||
type value to a string representation.
|
|
||||||
|
|
||||||
The following code shows how to iterate through resources:
|
|
||||||
|
|
||||||
```
|
|
||||||
import pepy
|
|
||||||
|
|
||||||
from hashlib import md5
|
|
||||||
|
|
||||||
p = pepy.parse(sys.argv[1])
|
|
||||||
resources = p.get_resources()
|
|
||||||
print "Resources: (%i)" % len(resources)
|
|
||||||
for resource in resources:
|
|
||||||
print "[+] MD5: (%i) %s" % (len(resource.data), md5(resource.data).hexdigest())
|
|
||||||
if resource.type_str:
|
|
||||||
print "\tType string: %s" % resource.type_str
|
|
||||||
else:
|
|
||||||
print "\tType: %s (%s)" % (hex(resource.type), resource.type_as_str())
|
|
||||||
if resource.name_str:
|
|
||||||
print "\tName string: %s" % resource.name_str
|
|
||||||
else:
|
|
||||||
print "\tName: %s" % hex(resource.name)
|
|
||||||
if resource.lang_str:
|
|
||||||
print "\tLang string: %s" % resource.lang_str
|
|
||||||
else:
|
|
||||||
print "\tLang: %s" % hex(resource.lang)
|
|
||||||
print "\tCodepage: %s" % hex(resource.codepage)
|
|
||||||
print "\tRVA: %s" % hex(resource.RVA)
|
|
||||||
print "\tSize: %s" % hex(resource.size)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that some binaries (particularly packed) may have corrupt resource entries.
|
|
||||||
In these cases you may find that len(resource.data) is 0 but resource.size is
|
|
||||||
greater than 0. The *size* attribute is the size of the data as declared by the
|
|
||||||
resource data entry.
|
|
||||||
|
|
||||||
Authors
|
|
||||||
=======
|
|
||||||
pe-parse was designed and implemented by Andrew Ruef (andrew@trailofbits.com)
|
|
||||||
pepy was written by Wesley Shields (wxs@atarininja.org)
|
|
@ -1,99 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import pepy
|
|
||||||
import binascii
|
|
||||||
|
|
||||||
from hashlib import md5
|
|
||||||
|
|
||||||
try:
|
|
||||||
p = pepy.parse(sys.argv[1])
|
|
||||||
except pepy.error as e:
|
|
||||||
print e
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print "Magic: %s" % hex(p.magic)
|
|
||||||
print "Signature: %s" % hex(p.signature)
|
|
||||||
print "Machine: %s (%s)" % (hex(p.machine), p.get_machine_as_str())
|
|
||||||
print "Number of sections: %s" % p.numberofsections
|
|
||||||
print "Number of symbols: %s" % p.numberofsymbols
|
|
||||||
print "Characteristics: %s" % hex(p.characteristics)
|
|
||||||
print "Timedatestamp: %s" % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(p.timedatestamp))
|
|
||||||
print "Major linker version: %s" % hex(p.majorlinkerver)
|
|
||||||
print "Minor linker version: %s" % hex(p.minorlinkerver)
|
|
||||||
print "Size of code: %s" % hex(p.codesize)
|
|
||||||
print "Size of initialized data: %s" % hex(p.initdatasize)
|
|
||||||
print "Size of uninitialized data: %s" % hex(p.uninitdatasize)
|
|
||||||
print "Address of entry point: %s" % hex(p.entrypointaddr)
|
|
||||||
print "Base address of code: %s" % hex(p.baseofcode)
|
|
||||||
try:
|
|
||||||
print "Base address of data: %s" % hex(p.baseofdata)
|
|
||||||
except:
|
|
||||||
# Not available on PE32+, ignore it.
|
|
||||||
pass
|
|
||||||
print "Image base address: %s" % hex(p.imagebase)
|
|
||||||
print "Section alignment: %s" % hex(p.sectionalignement)
|
|
||||||
print "File alignment: %s" % hex(p.filealignment)
|
|
||||||
print "Major OS version: %s" % hex(p.majorosver)
|
|
||||||
print "Minor OS version: %s" % hex(p.minorosver)
|
|
||||||
print "Win32 version: %s" % hex(p.win32ver)
|
|
||||||
print "Size of image: %s" % hex(p.imagesize)
|
|
||||||
print "Size of headers: %s" % hex(p.headersize)
|
|
||||||
print "Checksum: %s" % hex(p.checksum)
|
|
||||||
print "Subsystem: %s (%s)" % (hex(p.subsystem), p.get_subsystem_as_str())
|
|
||||||
print "DLL characteristics: %s" % hex(p.dllcharacteristics)
|
|
||||||
print "Size of stack reserve: %s" % hex(p.stackreservesize)
|
|
||||||
print "Size of stack commit: %s" % hex(p.stackcommitsize)
|
|
||||||
print "Size of heap reserve: %s" % hex(p.heapreservesize)
|
|
||||||
print "Size of heap commit: %s" % hex(p.heapcommitsize)
|
|
||||||
print "Loader flags: %s" % hex(p.loaderflags)
|
|
||||||
print "Number of RVA and sizes: %s" % hex(p.rvasandsize)
|
|
||||||
ep = p.get_entry_point()
|
|
||||||
byts = p.get_bytes(ep, 8)
|
|
||||||
print "Bytes at %s: %s" % (hex(ep), ' '.join(['0x' + binascii.hexlify(b) for b in str(byts)]))
|
|
||||||
sections = p.get_sections()
|
|
||||||
print "Sections: (%i)" % len(sections)
|
|
||||||
for sect in sections:
|
|
||||||
print "[+] %s" % sect.name
|
|
||||||
print "\tBase: %s" % hex(sect.base)
|
|
||||||
print "\tLength: %s" % sect.length
|
|
||||||
print "\tVirtual address: %s" % hex(sect.virtaddr)
|
|
||||||
print "\tVirtual size: %i" % sect.virtsize
|
|
||||||
print "\tNumber of Relocations: %i" % sect.numrelocs
|
|
||||||
print "\tNumber of Line Numbers: %i" % sect.numlinenums
|
|
||||||
print "\tCharacteristics: %s" % hex(sect.characteristics)
|
|
||||||
if sect.length:
|
|
||||||
print "\tFirst 10 bytes: 0x%s" % binascii.hexlify(sect.data[:10])
|
|
||||||
print "\tMD5: %s" % md5(sect.data).hexdigest()
|
|
||||||
imports = p.get_imports()
|
|
||||||
print "Imports: (%i)" % len(imports)
|
|
||||||
for imp in imports:
|
|
||||||
print "[+] Symbol: %s (%s %s)" % (imp.sym, imp.name, hex(imp.addr))
|
|
||||||
exports = p.get_exports()
|
|
||||||
print "Exports: (%i)" % len(exports)
|
|
||||||
for exp in exports:
|
|
||||||
print "[+] Module: %s (%s %s)" % (exp.mod, exp.func, hex(exp.addr))
|
|
||||||
relocations = p.get_relocations()
|
|
||||||
print "Relocations: (%i)" % len(relocations)
|
|
||||||
for reloc in relocations:
|
|
||||||
print "[+] Type: %s (%s)" % (reloc.type, hex(reloc.addr))
|
|
||||||
resources = p.get_resources()
|
|
||||||
print "Resources: (%i)" % len(resources)
|
|
||||||
for resource in resources:
|
|
||||||
print "[+] MD5: (%i) %s" % (len(resource.data), md5(resource.data).hexdigest())
|
|
||||||
if resource.type_str:
|
|
||||||
print "\tType string: %s" % resource.type_str
|
|
||||||
else:
|
|
||||||
print "\tType: %s (%s)" % (hex(resource.type), resource.type_as_str())
|
|
||||||
if resource.name_str:
|
|
||||||
print "\tName string: %s" % resource.name_str
|
|
||||||
else:
|
|
||||||
print "\tName: %s" % hex(resource.name)
|
|
||||||
if resource.lang_str:
|
|
||||||
print "\tLang string: %s" % resource.lang_str
|
|
||||||
else:
|
|
||||||
print "\tLang: %s" % hex(resource.lang)
|
|
||||||
print "\tCodepage: %s" % hex(resource.codepage)
|
|
||||||
print "\tRVA: %s" % hex(resource.RVA)
|
|
||||||
print "\tSize: %s" % hex(resource.size)
|
|
@ -23,42 +23,47 @@
|
|||||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
# SUCH DAMAGE.
|
# SUCH DAMAGE.
|
||||||
|
|
||||||
from distutils.core import setup, Extension
|
from setuptools import setup, Extension
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.dirname(__file__)
|
||||||
|
pepy = os.path.join(here, "pepy")
|
||||||
|
|
||||||
|
with open(os.path.join(pepy, "README.md")) as f:
|
||||||
|
README = f.read()
|
||||||
|
|
||||||
|
with open(os.path.join(here, "VERSION")) as f:
|
||||||
|
VERSION = f.read().strip()
|
||||||
|
|
||||||
SOURCE_FILES = [
|
SOURCE_FILES = [
|
||||||
os.path.join(here, "pepy.cpp"),
|
os.path.join(pepy, "pepy.cpp"),
|
||||||
os.path.abspath(os.path.join(here, "..", "pe-parser-library", "src", "parse.cpp")),
|
os.path.join(here, "pe-parser-library", "src", "parse.cpp"),
|
||||||
os.path.abspath(os.path.join(here, "..", "pe-parser-library", "src", "buffer.cpp")),
|
os.path.join(here, "pe-parser-library", "src", "buffer.cpp"),
|
||||||
os.path.abspath(
|
os.path.join(here, "pe-parser-library", "src", "unicode_codecvt.cpp"),
|
||||||
os.path.join(here, "..", "pe-parser-library", "src", "unicode_codecvt.cpp")
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
INCLUDE_DIRS = [
|
INCLUDE_DIRS = [
|
||||||
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "include")),
|
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "include")),
|
||||||
os.path.abspath(os.path.join(here, "..", "pe-parser-library", "include")),
|
os.path.join(here, "pe-parser-library", "include"),
|
||||||
"C:\\usr\\include",
|
"C:\\usr\\include",
|
||||||
]
|
]
|
||||||
LIBRARY_DIRS = [
|
LIBRARY_DIRS = [
|
||||||
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "libs")),
|
os.path.abspath(os.path.join(os.path.dirname(sys.executable), "libs")),
|
||||||
"C:\\usr\\lib",
|
"C:\\usr\\lib",
|
||||||
]
|
]
|
||||||
COMPILE_ARGS = ["/EHsc"]
|
COMPILE_ARGS = ["/EHsc", f'/D"PEPARSE_VERSION=\\"{VERSION}\\""']
|
||||||
else:
|
else:
|
||||||
INCLUDE_DIRS = [
|
INCLUDE_DIRS = [
|
||||||
"/usr/local/include",
|
"/usr/local/include",
|
||||||
"/opt/local/include",
|
"/opt/local/include",
|
||||||
"/usr/include",
|
"/usr/include",
|
||||||
os.path.abspath(os.path.join(here, "..", "pe-parser-library", "include")),
|
os.path.join(here, "pe-parser-library", "include"),
|
||||||
]
|
]
|
||||||
LIBRARY_DIRS = ["/usr/lib", "/usr/local/lib"]
|
LIBRARY_DIRS = ["/usr/lib", "/usr/local/lib"]
|
||||||
COMPILE_ARGS = ["-std=c++11", "-g", "-O0"] # Debug only
|
COMPILE_ARGS = ["-std=c++11", f'-DPEPARSE_VERSION="{VERSION}"']
|
||||||
|
|
||||||
extension_mod = Extension(
|
extension_mod = Extension(
|
||||||
"pepy",
|
"pepy",
|
||||||
@ -69,14 +74,15 @@ extension_mod = Extension(
|
|||||||
library_dirs=LIBRARY_DIRS,
|
library_dirs=LIBRARY_DIRS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="pepy",
|
name="pepy",
|
||||||
version="0.1",
|
python_requires=">= 3.6",
|
||||||
description="python bindings for pe-parse",
|
version=VERSION,
|
||||||
|
description="Python bindings for pe-parse",
|
||||||
|
long_description=README,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
author="Wesley Shields",
|
author="Wesley Shields",
|
||||||
author_email="wxs@atarininja.org",
|
author_email="wxs@atarininja.org",
|
||||||
license="BSD",
|
license="BSD",
|
||||||
long_description="Python bindings for pe-parse",
|
|
||||||
ext_modules=[extension_mod],
|
ext_modules=[extension_mod],
|
||||||
)
|
)
|
BIN
test/assets/example.exe
Normal file
BIN
test/assets/example.exe
Normal file
Binary file not shown.
43
util/release
Executable file
43
util/release
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# release: perform the chore work required for a pe-parse/pepy release
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
function installed {
|
||||||
|
cmd=$(command -v "${1}")
|
||||||
|
|
||||||
|
[[ -n "${cmd}" ]] && [[ -f "${cmd}" ]]
|
||||||
|
return ${?}
|
||||||
|
}
|
||||||
|
|
||||||
|
function die {
|
||||||
|
>&2 echo "Barf: ${*}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Fail early if we don't have the expected tools.
|
||||||
|
for tool in git python3 twine; do
|
||||||
|
installed "${tool}" || die "Missing dependency: ${tool}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Fail early if `git status` reports any untracked changes.
|
||||||
|
[[ -n $(git status -s) ]] && die "Untracked changes in repo"
|
||||||
|
|
||||||
|
# Next, check the VERSION in version and make sure it doesn't already have a git tag.
|
||||||
|
[[ -f ./VERSION ]] || die "Missing VERSION file; wrong directory?"
|
||||||
|
version=$(<./VERSION)
|
||||||
|
[[ -n $(git tag -l "${version}") ]] && die "git tag for ${version} already exists!"
|
||||||
|
|
||||||
|
# Next, craft a tag for the current HEAD. Push both the current commit and the tag.
|
||||||
|
git tag "${version}"
|
||||||
|
git push
|
||||||
|
git push origin "${version}"
|
||||||
|
|
||||||
|
# Finally, build pepy and publish it to PyPI.
|
||||||
|
# Nuke the dist/ directory before, to avoid old sdists.
|
||||||
|
rm -rf dist/
|
||||||
|
python3 setup.py sdist
|
||||||
|
twine upload dist/*
|
||||||
|
|
||||||
|
echo OK
|
Loading…
x
Reference in New Issue
Block a user