PortEx, a Java library for reading malformed PE files
Java library to analyse Portable Executable files with a special focus on malware analysis and PE malformation robustness
At a glance
- What is it?
- PortEx is a Java and Scala library for static analysis of Windows Portable Executable files, and its distinguishing feature is that it is built to survive malformed input: a file that a strict parser would reject is parsed anyway, and the structural deviations are reported as anomalies. It ships as a Maven and sbt library with a command line analyser on top.
- Who is it for?
- PortEx suits a malware analyst building tooling that has to handle hostile input, since a parser that throws on a malformed header is useless on a file designed to break parsers, and it suits a Java team that wants PE structure, hashes and signature scanning in one library rather than several command line tools. It does not suit a general file inspector, since almost every feature is oriented at PE analysis.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A PE parser that expects the file to be broken
PortEx is a Java library for static malware analysis of Portable Executable files, and the project states its focus in one line: PE malformation robustness, and anomaly detection. It is written in Java and Scala and is targeted at Java applications.
That focus is the whole design, and it is unusual. A general purpose PE parser is written to read a well-formed file, and it can afford to throw an exception when the structure does not match expectations. A parser used on malware cannot, because malformed headers are a technique rather than a defect. A file with an inconsistent section table, an optional header whose declared sizes do not match the file, an overlay that overlaps a section, or a resource directory pointing outside the file is doing something on purpose.
So PortEx parses anyway, and reports what it found. The scanning feature is described as scanning for file format anomalies, including structural anomalies, and deprecated, reserved, wrong or non-default values. A value that is reserved and a value that is non-default are both flagged, which is a distinction that matters: reserved means the specification says it must not be used, and non-default means it is legal but unusual, which is a weaker signal and often a stronger one.
The audience is a malware analyst or a security engineer building detection tooling, and the secondary audience is a Java developer who needs to inspect PE files in an application. The licence is Apache-2.0, the primary language is Java, and the last push to the repository was on 2026-10-18 with a recorded release of v5.0.7 from 2026-10-11.
The author is Karsten Hahn, and the README is candid that this is a hobby project: the donations section says PortEx and PortexAnalyzer are developed as a hobby in free time, with a link to buy a coffee. There is also a YouTube channel for the project, MalwareAnalysisForHedgehogs, which is where the teaching side of this work lives.
What it reads out of a PE file
The feature list covers the format comprehensively, and it is worth walking through because the coverage tells you what an analyst actually needs.
At the header level it reads the MSDOS header, the COFF file header, the optional header and the section table. Those are the four structures that describe the file's shape, and everything else hangs off them.
Then the PE structures proper: imports, resources, exports, the debug directory, relocations, delay load imports and bound imports. The last two are the ones that trip up simpler parsers. Delay load imports are the entries for DLLs loaded at first use rather than at load time, and bound imports are the pre-resolved addresses a linker writes so the loader can skip the fixup. Both are legitimate features that malware abuses, and both are places where a hand-written parser commonly gets it wrong.
Dumping is the other half of the reading surface. The library can dump sections, resources, the overlay, certificates, and embedded ZIP, JAR or .class files. The overlay is the data appended after the last section, and in a normal file it holds certificates and other trailing data, and in a packed or tampered file it is often where the payload is, so being able to extract it separately is the difference between seeing and not seeing.
The extraction features are more specific. It extracts Unicode and ASCII strings, and converts .ICO files from the icons in the resource section, and extracts version information and the manifest. The manifest is worth calling out, because it is where the requested execution level is declared, and a mismatch between what the manifest says and what the binary does is a classic indicator.
There is also .NET support, described as reading .NET metadata and streams, and marked as Alpha.
And there is a visualizer, which the README describes as visualizing the PE file structure, local entropies and a byteplot of the file with variable colours and sizes. The byteplot is the interesting one, because plotting entropy per region as a picture makes packed and encrypted sections obvious at a glance, in a way that a table of numbers does not.
Hashes, entropy and the Rich header
The measuring features are the ones that turn a parsed file into something you can query or compare.
It calculates Shannon entropy and Chi Squared for files and sections. Entropy per section is the standard first pass for a packer check, because a compressed or encrypted section has entropy near the maximum, and a section of mostly zeroes or mostly repeated bytes has entropy near zero. Chi squared is a second statistic that compares the observed byte distribution against the expected uniform one, and it catches a distribution that is uneven in a way entropy alone smooths over.
It also calculates ImpHash and Rich and RichPV hash values for files and sections. ImpHash is an import-based hash: take the DLL names and the imported function names, normalise them, and hash the result. Two samples with the same ImpHash are very likely the same malware family compiled differently, and because it ignores timestamps, paths and most header noise, it survives the trivial recompilations that break file-level hashes. Rich and RichPV are hashes of the Rich header, which is the undocumented block of compiler and linker metadata between the DOS stub and the PE header, and they identify the toolchain that produced a binary.
The Rich header is also parsed and its checksum verified, and this is a nice detail for an analyst. The Rich header is deliberately obfuscated: its XOR key is stored in it, and if a packer or a crypter has modified the header without fixing the key, the checksum will not verify. So a failed Rich checksum is a signal that the header has been tampered with, which is a cheap and reliable indicator that the file has been processed.
The Optional Header checksum is also calculated and verified, and that is the second built-in consistency check on the file's self-description.
So the library gives you three independent ways to check whether a file's own metadata agrees with itself, and that is the practical value of the anomaly scanning: it does not need a reference sample to tell you that something is off.
Authenticode, and what the verification does not do
The Authenticode support is the most detailed single feature in the list, and it is worth reading the caveat at the end of it carefully.
PortEx parses Authenticode signatures, which means the certificate table, PKCS#7, X.509 certificates, timestamps and nested signatures, and it verifies their integrity. The integrity verification covers the image digest, the message digest, and the signer and certificate signatures.
The parenthetical at the end is the important part: no trust or revocation checks. Malformed and manipulated signatures are reported as anomalies.
That distinction is the difference between a cryptographic check and a trust decision, and this library does the first. It will tell you that the digests in the signature do not match the file, that the message digest does not match the signed attributes, that the signature over the certificate does not verify, or that the structure is malformed. All of those are strong indicators that the file has been modified after signing, which is exactly what a packer does and what a crypter does.
What it will not tell you is that the signature chains to a trusted root, that the certificate has not been revoked, or that the timestamp is valid. Those need a trust store and a revocation check, which is a different subsystem and a different set of network dependencies. So the correct use is: PortEx tells you whether the signature is internally consistent, and you combine that with a platform verification tool to decide whether to trust it.
The nested signature handling matters here too, because signing an already-signed binary is a normal thing for a build pipeline to do, and a parser that cannot represent a signature inside a signature will either fail or silently discard the outer one. And the reporting of malformed signatures as anomalies rather than as exceptions is the same principle that runs through the whole library, applied to the one structure where malformations are most common in the wild.
Signature scanning and wrapper detection
The signature scanning feature is the one with the most configuration in it, and the documentation explains what makes it different from a plain byte-pattern search.
PortEx can scan for packer, installer and file type signatures, or for your own signature database, with patterns tied to locations such as the entry point, the overlay, resources, sections or .NET streams, and the format is described in a separate document at `docs/SIGNATURES.md`. It can also import PEiD databases.
Tying a pattern to a location is the important part. A byte sequence at the entry point means something quite different from the same bytes in the middle of a section, and a signature that only matches in the overlay is a different signal again. A signature engine that searches the whole file for bytes will produce a lot of hits that are meaningless, and the location constraint is what makes the output worth reading.
Importing PEiD databases is a compatibility feature, and it is worth understanding what it means. PEiD signatures are a long-established format for describing packer entry points, and importing them means a user does not have to rediscover thousands of known packer patterns. It also means PortEx inherits the coverage and the false positives of that database, and the sign of the times is that the project lets you use your own database instead if you want to.
There is a separate feature for scanning for Jar to EXE wrappers, with four named: exe4j, jsmooth, jar2exe and launch4j. That is a specific and practical thing to look for, because a Java application packaged as a Windows executable has a recognisable structure, and finding it tells an analyst that the interesting logic is in a JAR inside the wrapper rather than in the native code.
For a malware analyst this combination is the difference between a file that is opaque and a file that has been classified. The signature scan gives a first hypothesis, the anomaly scan gives evidence for or against it, and the entropy and hash features let you cluster what you find.
Using it as a dependency, and building it
The library is published for Java projects through Maven, and the coordinates carry the Scala version in the artifact name.
<dependency>
<groupId>io.github.struppigel</groupId>
<artifactId>portex_2.12</artifactId>
<version>5.0.7</version>
</dependency> The `_2.12` suffix is the standard cross-build suffix for a Scala library, and it tells you that this is a Scala artifact consumed from Java, which is why the project is described as written in Java and Scala but targeted at Java applications.
A local build is added as a system-scoped dependency pointing at the jar in the target directory.
<dependency>
<groupId>io.github.struppigel</groupId>
<artifactId>portex_2.12</artifactId>
<version>5.0.7</version>
<scope>system</scope>
<systemPath>$PORTEXDIR/target/scala-2.12/portex_2.12-5.0.7.jar</systemPath>
</dependency> And for sbt projects, which is how the project itself is built, the dependency is a single line.
libraryDependencies += "io.github.struppigel" % "portex_2.12" % "5.0.7"Building from source needs sbt, and there are three commands that map onto three outputs.
$ sbt compile$ sbt package$ sbt assemblyCompile builds the classes, package produces the ordinary jar, and assembly produces the fat jar that can be used as a command line tool. There is also an Eclipse integration path through the sbteclipse plugin, which is a dated convenience but tells you the project has been developed from Eclipse for a long time.
On top of the library there is PortexAnalyzer, a command line tool that runs the library under the hood. The README is clear about which artefact to use: the newest version of the jar is in the repository at `progs/PortexAnalyzer.jar`, while the long-term release version of the CLI is on the releases page. And there is a separate GUI, PortexAnalyzerGUI, in its own repository, which is the piece that most people actually use for looking at a file.
The repository also carries a `masterthesis/` directory, which explains the origin of the project, a `THIRD-PARTY-NOTICES.md` for the bundled components, an `iconwhitelist.txt` for the resource extraction allowlist, and TestNG configuration files for the test suite and for performance tests. The documentation includes a migration document for upgrading from the 5.x line, which is a sign that a 6.x is coming and that the API will change.
Editorial conclusion
PortEx suits a malware analyst building tooling that has to handle hostile input, since a parser that throws on a malformed header is useless on a file designed to break parsers, and it suits a Java team that wants PE structure, hashes and signature scanning in one library rather than several command line tools. It does not suit a general file inspector, since almost every feature is oriented at PE analysis. Verify first which version you depend on, because a migration document exists for upgrading from the 5.x line, that the Authenticode verification covers digests and signatures but performs no trust or revocation checks, so it will not tell you a signature is trusted, and that the signature database you point it at is the part you are responsible for keeping current.
Frequently asked questions
What is PortEx?
It is a Java and Scala library for static malware analysis of Windows Portable Executable files, written for Java applications. Its focus is PE malformation handling and anomaly detection, so it parses files that a strict parser would reject and reports the structural deviations instead of throwing.
What can PortEx read out of a PE file?
The MSDOS header, COFF file header, optional header and section table, plus imports, resources, exports, the debug directory, relocations, delay load imports and bound imports. It can also dump sections, resources, the overlay, certificates and embedded ZIP, JAR or class files, and extract strings, icons, version information and the manifest.
Does PortEx verify Authenticode signatures?
It parses the certificate table, PKCS#7, X.509 certificates, timestamps and nested signatures, and verifies the image digest, the message digest and the signer and certificate signatures. It performs no trust or revocation checks, and malformed or manipulated signatures are reported as anomalies.
How do I add PortEx to a Maven project?
Add the dependency with the group id io.github.struppigel, the artifact id portex_2.12 and the version, for example 5.0.7. For an sbt project the equivalent is libraryDependencies += "io.github.struppigel" % "portex_2.12" % "5.0.7".
How do I build PortEx from source?
The project is built with sbt. Running sbt compile builds the classes, sbt package produces the jar, and sbt assembly produces the fat jar that can be used as a command line tool.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/struppigel-portex)Was this useful?