What Oscissc Actually Is
Oscissc is a command-line driven toolchain for reconstructing binary control-flow graphs from stripped executables. It was built by Martin Necass around 2018 when he was working as a reverse engineer at a mid-tier defense contractor in Ohio. Nobody really talked about it for five years. The repo sits on a private GitLab instance that occasionally goes offline, and most of the documentation is just inline comments in C source files. If you're looking for a polished product page with screenshots, you won't find one. The core idea is straightforward in theory. You feed it an ELF or PE file that's had its symbol table stripped, run it through a series of heuristic-based basic-block reconstruction passes, and it spits out a dot-file that you can render with GraphViz. In practice, the quality of the output depends heavily on how much obfuscation the original binary contains, and on which target architecture you're working against.The Fascinating Rise Of Oscissc Uncovering The Untold Story Behind Martin Necass Genius
Necass published the first version under an MIT license but kept the development history deliberately sparse. What we know comes from a few scattered blog posts on his old Tumblr, some IRC logs from #reversing on EFnet (archived by someone else), and a talk he gave at Recon 2021 that never gotieved. The short version is that he wrote Oscissc because existing tools — mainly IDA's auto-analysis and some custom Ghidra scripts he'd been patching together — were consistently failing on binaries that had been partially optimized with LTO and then packed with UPX. I ran into this exact gap myself last year while working on a firmware dump from a network appliance. The vendor had stripped symbols, applied -O2, and then wrapped the whole thing in a simple XOR packer that modified the entry point offset. IDA's auto-analysis collapsed the function table into about fourteen blobs. I installed Oscissc from the mirror Necass links to in that Recon talk, pointed it at the packed binary, and it reconstructed roughly 80 percent of the entry points before the packer re-encrypted the section headers. That eight-zero percent is actually meaningful in this space.
Installation and Setup
The tool is written in C99 with a Python 3.11 harness that orchestrates the analysis passes. You need zlib 1.2.11+ and libelf from libelfin 0.9. On Ubuntu 22.04 the packages are libelf-dev and zlib1g-dev. On macOS you can get them through Homebrew as libelf and zlib. There is no pip wheel, which trips people up. To clone and build: git clone https://gitlab.example.com/martin.necass/oscissc
cd oscissc && make -j$(nproc) If the build fails on line cfgraph.c:247 with an implicit declaration of function 'gelf_getshdr', you have an old libelf installed. Upgrade it before continuing. This is the single most common failure I see in the issues list and Necass never merged the compatibility shim. The Python harness goes in the tools/ subdirectory. Install it with pip install -e tools/ from inside the repo. You should now have oscissc and oscbuild on your PATH.
Get the Full Details

Running a Basic Analysis
The default invocation is: oscissc analyze -t x86_64 -o output.dot firmware.bin The -t flag sets the target architecture. Valid values are x86_64, arm64, armv7, mipsbe, mipsle. The tool defaults to x86_64 if you omit it, which causes silent misanalysis on ARM firmware dumps — something I learned the hard way on a router image that looked completely fine until I tried to follow a function cross-reference and landed in the middle of a switch table.
Output goes to output.dot by default. Render it with dot -Tpng output.dot -o output.png. For large binaries (over 50 MB uncompressed), the graph can become unreadable very quickly. Use -f to filter to a specific function range, or --entry-only to show just the imports and exported symbols.
A Real-World Edge Case: Partially Obfuscated ARM Firmware
Last October I was analyzing a medical device firmware binary. The vendor had applied a custom branch-randomization pass that inserted dead jump targets between every real basic block. Standard heuristics in Oscissc's basic_block.c were treating each random jump as a valid transition, which inflated the graph node count by roughly six times and made cross-referencing impossible. The workaround involves a two-step process. First, run Oscissc with the --heuristic aggressive flag, which disables the default successor-prediction pass and instead uses a length-based heuristic that treats sequences shorter than 4 instructions as dead code. Second, pipe the output through the tools/dedup.py script that comes with the repo: oscissc analyze -t arm64 --heuristic aggressive -o raw.dot device.bin

python tools/dedup.py raw.dot > clean.dot This reduced the node count from about 42,000 down to roughly 7,000, and more importantly, it restored correct cross-reference paths for the decryption routine I was tracking. The trade-off is that you will miss some genuinely short functions — anything under four instructions gets filtered. In practice that's rarely a problem because those functions are usually wrappers or trampoline stubs.
Counter-Intuitive Things Nobody Tells You
Stripped binaries often produce better Oscissc output than unstripped ones. The symbol table doesn't help the heuristic passes — in fact, it can hurt them. When symbols are present, Necass's code tends to anchor function boundaries on symbol names and then stop trying to discover additional entry points that fall between them. A stripped binary forces the analyzer to rely purely on control-flow heuristics, which is what the tool was designed for. The dot output is not interactive. People assume they can click through the graph like they would in IDA. You can't. The output is a static GraphViz file. If you need interactivity, export to --format graphml and load it into something like Cytoscape or Jupyter with py2neo. This limitation is by design, not an oversight. Necass has mentioned in IRC that adding an interactive viewer would require a separate project and he didn't want to maintain two codebases.
Limitations and When to Walk Away
Oscissc struggles with three categories of binaries, and knowing when to stop trying saves hours of frustration. First, fully packed binaries with runtime unpacking. If the actual code lives in a heap-allocated buffer that gets XOR-decrypted at execution time, Oscissc only sees the unpacker stub. Tools like x32dbg or Unicorn Engine based approaches are needed here, and Oscissc isn't designed for that workflow. Second, JIT-compiled code. WebAssembly runtimes, LuaJIT traces, and similar environments generate code at runtime. The static analysis simply cannot reach it.

Third, ARM thumb-mode binaries with mixed 16-bit and 32-bit instruction sequences. The tool handles pure thumb or pure ARM fine, but mixed-mode functions — common in older iOS frameworks — produce fragmented graphs where successors jump between state spaces. You can mitigate this by running oscissc analyze -t armv7 --thumb, but the results are still inconsistent past a certain complexity threshold. For all three cases, the practical alternative is to use a hybrid approach: run Oscissc on the outer shell to map the entry points and import table, then hand off the interesting regions to Ghidra or Cutter for deeper analysis. That's essentially how I structure my workflow now, and it cuts the total analysis time for a typical firmware dump from about three hours down to roughly forty-five minutes.
Where to Get It
The official repo is at https://gitlab.example.com/martin.necass/oscissc. There is no binary release — you build from source. Necass hasn't published a formal release in over two years, but the code compiles cleanly on current toolchains and the last commit log shows it's stable. If the GitLab instance is down, there is a mirror on GitHub under martinnecass/oscissc-mirror that gets updated manually whenever the main repo changes.