mirror of
https://github.com/DarrylNixon/binhop
synced 2024-04-22 12:37:06 -07:00
pivot to web!
This commit is contained in:
parent
53f960b3b2
commit
60524bf2dc
15 changed files with 254 additions and 137 deletions
39
README.md
39
README.md
|
@ -1,61 +1,52 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="doc/binhop.png" alt="Binhop Logo">
|
<img src="static/binhop.png" alt="binhop logo">
|
||||||
|
|
||||||
# binhop
|
# binhop
|
||||||
|
|
||||||
binhop is a **file carving visualizer**, powered by binwalk.
|
`binhop` is a **file carving visualizer**, powered by `binwalk`.
|
||||||
|
|
||||||
It makes clear what parts of a blob can be extracted, so you can
|
It makes clear what parts of a blob can be extracted, so you can
|
||||||
take action on the parts that didn't.<br />
|
take action on the parts that didn't.<br />
|
||||||
binhop relies entirely on a functioning installation of binwalk.
|
`binhop` relies entirely on a functioning installation of `binwalk`.
|
||||||
|
|
||||||
[Installation](#installation) •
|
[Installation and Usage](#installation) •
|
||||||
[Usage](#usage) •
|
|
||||||
[FAQ](#faq) •
|
[FAQ](#faq) •
|
||||||
[Contributing](#contributing) •
|
[Contributing](#contributing) •
|
||||||
[License](#license)
|
[License](#license)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Installation
|
## Installation and Usage
|
||||||
|
|
||||||
To use this script, you need Python 3 and a functioning and "recent" version of `binwalk` installed on your system. You'll probably also want to install optional `binwalk` dependencies such as `sasquatch`, `jefferson`, and others. You can learn how to do that in the [binwalk documentation](https://github.com/ReFirmLabs/binwalk/blob/master/INSTALL.md).
|
To use this script, you need Python 3 and a functioning and "recent" version of `binwalk` installed on your system.
|
||||||
|
|
||||||
When that's done, install the Python dependencies for `binhop`, running something like:
|
You'll probably also want to install optional `binwalk` dependencies such as `sasquatch`, `jefferson`, and others. You can learn how to do that in [binwalk's INSTALL.md](https://github.com/ReFirmLabs/binwalk/blob/master/INSTALL.md). `binhop` only "requires" `binwalk`, but it'll fail on binaries for which `binwalk` is dependent on optional modules.
|
||||||
|
|
||||||
|
When that's done, get `binhop` running with something like:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/darrylnixon/binhop.git
|
git clone https://github.com/darrylnixon/binhop.git
|
||||||
cd binhop
|
cd binhop
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install .
|
./binhop.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
Once running, browse to [http://localhost:8080](http://localhost:8080) to upload a blob.
|
||||||
|
|
||||||
To use the script, run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
binhop <path-to-binary-file>
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can run binhop on data from STDIN like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
binhop < <path-to-binary-file-or-stream>
|
|
||||||
```
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**What problem does binhop solve?**
|
**What problem does binhop solve?**
|
||||||
|
|
||||||
binhop was written under the assumption that reverse engineers are blindly running `binwalk -qeM` on firmware images without validating what percentage of the image successfully extracted. I'm guilty of this in the past. binhop makes it easier to determine "coverage" of a walk/carve so that pieces that did not match any magic bytes can be analyzed further.
|
`binhop` was written under the assumption that reverse engineers are blindly running `binwalk -qeM` on firmware images without validating what percentage of the image successfully extracted. I'm guilty of this in the past. `binhop` makes it easier to determine "coverage" of a walk/carve so that pieces that did not match any magic bytes can be analyzed further.
|
||||||
|
|
||||||
**What are the future plans for binhop?**
|
**What are the future plans for binhop?**
|
||||||
|
|
||||||
This repository is part of my coursework for CSC 842 - Security Tool Development at Dakota State University. Consequently, I may choose not to maintain this tool beyond the length of the course, but have selected a license that enables open contributions in any case.
|
This repository is part of my coursework for CSC 842 - Security Tool Development at Dakota State University. Consequently, I may choose not to maintain this tool beyond the length of the course, but have selected a license that enables open contributions in any case.
|
||||||
|
|
||||||
|
For aesthetics, the interface is browser-based. It'd be ideal to make it command-line only, but I ran out of time trying to summarize an arbitrarily large number of bytes and sections into a human-consumable CLI output. I'm open to ideas.
|
||||||
|
|
||||||
**Why did you select GPLv3? MIT is so much better.**
|
**Why did you select GPLv3? MIT is so much better.**
|
||||||
|
|
||||||
GPLv3 still gives you the right to use, modify, and share binhop. It also has the benefit of requiring you to open-source software that uses it and share back any significant modifications or improvements to the code, and I like that.
|
GPLv3 still gives you the right to use, modify, and share `binhop`. It also has the benefit of requiring you to open-source software that uses it and share back any significant modifications or improvements to the code, and I like that.
|
||||||
|
|
||||||
**How can I report a bug or request new features?**
|
**How can I report a bug or request new features?**
|
||||||
|
|
||||||
|
|
82
binhop.py
Normal file
82
binhop.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import asyncio
|
||||||
|
import hashlib
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
async def upload_file(request):
|
||||||
|
reader = await request.multipart()
|
||||||
|
field = await reader.next()
|
||||||
|
assert field.name == 'file'
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
filename = field.filename
|
||||||
|
file_size = 0
|
||||||
|
sha1_hash = hashlib.sha1()
|
||||||
|
md5_hash = hashlib.md5()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
chunk = await field.read_chunk()
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
file_size += len(chunk)
|
||||||
|
sha1_hash.update(chunk)
|
||||||
|
md5_hash.update(chunk)
|
||||||
|
|
||||||
|
size_suffixes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
size_suffix_index = math.floor(math.log(file_size, 1024))
|
||||||
|
human_readable_size = f'{file_size / (1024 ** size_suffix_index):.2f} {size_suffixes[size_suffix_index]}'
|
||||||
|
|
||||||
|
# await asyncio.sleep(2)
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
'meta': {
|
||||||
|
'name': filename,
|
||||||
|
'sizeb': file_size,
|
||||||
|
'sizeh': human_readable_size,
|
||||||
|
'sha1': sha1_hash.hexdigest(),
|
||||||
|
'md5': md5_hash.hexdigest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processing_time = time.time() - start_time
|
||||||
|
minutes, seconds = divmod(processing_time, 60)
|
||||||
|
milliseconds = processing_time - int(processing_time)
|
||||||
|
response_data['meta']['duration'] = f"{int(minutes):02d}:{int(seconds):02d}.{int(milliseconds * 1000):03d}"
|
||||||
|
return web.json_response(response_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def serve_index(request):
|
||||||
|
return web.FileResponse('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
async def serve_static(request):
|
||||||
|
path = request.path[1:]
|
||||||
|
if not path.startswith('static/'):
|
||||||
|
return web.HTTPNotFound()
|
||||||
|
return web.FileResponse(path)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
app = web.Application()
|
||||||
|
|
||||||
|
app.add_routes([
|
||||||
|
web.get('/', serve_index),
|
||||||
|
web.post('/api/upload_file', upload_file),
|
||||||
|
web.get('/static/{tail:.*}', serve_static)
|
||||||
|
])
|
||||||
|
|
||||||
|
runner = web.AppRunner(app)
|
||||||
|
await runner.setup()
|
||||||
|
site = web.TCPSite(runner, 'localhost', 8080)
|
||||||
|
await site.start()
|
||||||
|
|
||||||
|
print('binhop is running at http://localhost:8080')
|
||||||
|
await asyncio.Event().wait()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
asyncio.run(main())
|
|
@ -1,13 +0,0 @@
|
||||||
"""
|
|
||||||
Initialization constants for binhop.
|
|
||||||
|
|
||||||
Constants:
|
|
||||||
_PROJECT (str): Name for the project
|
|
||||||
__version__ (str): The current version of the binhop package
|
|
||||||
"""
|
|
||||||
import binwalk.core.magic as binwalk_magic
|
|
||||||
|
|
||||||
_PROJECT = "binhop"
|
|
||||||
__version__ = "0.0.1"
|
|
||||||
|
|
||||||
MAGIC = binwalk_magic.Magic()
|
|
|
@ -1,45 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
binhop is a visualization tool for binwalk.
|
|
||||||
it might help you find silent failures to extract files.
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
from .core import process_file
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""
|
|
||||||
Execute binhop, a visualization tool for binwalk, on the specified input blob.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
binhop [INPUT_FILE]
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
INPUT_FILE Optional path to the binary file to analyze or blank for STDIN.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"file",
|
|
||||||
nargs="?",
|
|
||||||
default="-",
|
|
||||||
metavar="INPUT_FILE",
|
|
||||||
type=argparse.FileType("r"),
|
|
||||||
help="path to target binary (or omit for STDIN)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
process_file(args.file)
|
|
||||||
|
|
||||||
try:
|
|
||||||
args.file.close()
|
|
||||||
except ValueError:
|
|
||||||
# Continue if the file handle already closed
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
|
@ -1,31 +0,0 @@
|
||||||
"""
|
|
||||||
The core module provides functions for processing and visualizing
|
|
||||||
binaries using binwalk for the heavy lifting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import IO
|
|
||||||
import binwalk.core.compat as compat
|
|
||||||
from . import MAGIC
|
|
||||||
|
|
||||||
|
|
||||||
def process_file(bin_file: IO[str]) -> None:
|
|
||||||
"""
|
|
||||||
process_file(bin: IO[str]) -> None
|
|
||||||
|
|
||||||
Processes a binary file and reads its data using the given binary input stream.
|
|
||||||
|
|
||||||
:param bin: A file object representing the binary input stream.
|
|
||||||
:type bin: IO[str]
|
|
||||||
:returns: None
|
|
||||||
"""
|
|
||||||
with open(bin_file, "rb") as bin_data:
|
|
||||||
data = bin_data.read()
|
|
||||||
|
|
||||||
scan_length = len(data)
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for offset, description in compat.iteritems(MAGIC.scan(data, length=scan_length)):
|
|
||||||
results.append((offset, description))
|
|
||||||
|
|
||||||
for offset, description in results:
|
|
||||||
print(f"Found signature {description} at offset {offset}")
|
|
62
index.html
Normal file
62
index.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>binhop</title>
|
||||||
|
<link rel="icon" type="image/ico" href="static/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="static/pico.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.button-center {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="https://github.com/DarrylNixon/binhop" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:#fff; color:#11191f; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>
|
||||||
|
<div class="header">
|
||||||
|
<img class="logo" src="static/binhop.png" alt="binhop logo">
|
||||||
|
<hgroup>
|
||||||
|
<h1>binhop</h1>
|
||||||
|
<h2>visualize your blob.</h2>
|
||||||
|
</hgroup>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="button-center">
|
||||||
|
<button id="upload-button" onclick="return upload_file()">give me the blob</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="static/binhop.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1 +1,2 @@
|
||||||
|
aiohttp
|
||||||
binwalk
|
binwalk
|
24
setup.py
24
setup.py
|
@ -1,24 +0,0 @@
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
with open("requirements.txt", "r", encoding="utf-8") as f:
|
|
||||||
required = [x for x in f.read().splitlines() if not x.startswith("#")]
|
|
||||||
|
|
||||||
from binhop import __version__, _PROJECT
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name=_PROJECT,
|
|
||||||
version=__version__,
|
|
||||||
packages=["binhop"],
|
|
||||||
description="binhop is a visualization tool for binwalk.",
|
|
||||||
url="https://github.com/darrylnixon/binhop",
|
|
||||||
author="Darryl Nixon",
|
|
||||||
author_email="binhop@nixon.mozmail.com",
|
|
||||||
license="GPLv3",
|
|
||||||
entry_points=f"""
|
|
||||||
[console_scripts]
|
|
||||||
{_PROJECT} = binhop.binhop:main
|
|
||||||
""",
|
|
||||||
keywords=[],
|
|
||||||
tests_require=[],
|
|
||||||
zip_safe=False,
|
|
||||||
)
|
|
82
static/binhop.js
Normal file
82
static/binhop.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
async function process_upload(file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
button.setAttribute('aria-busy', 'true');
|
||||||
|
button.textContent = `processing ${file.name}...`;
|
||||||
|
|
||||||
|
const response = await fetch('api/upload_file', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
timeout: 600000 // 10 minutes
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const container = document.querySelector('.container');
|
||||||
|
|
||||||
|
// visualization
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="grid">
|
||||||
|
<div id="blob-column">
|
||||||
|
<canvas id="blob" width="600px" height="600px" style="background-color: #18232c;"></canvas>
|
||||||
|
</div>
|
||||||
|
<div id="info-column">
|
||||||
|
<small>
|
||||||
|
<h1>${data.meta.name}</h1>
|
||||||
|
<details open>
|
||||||
|
<summary>blob meta</summary>
|
||||||
|
<figure>
|
||||||
|
<table role="grid">
|
||||||
|
<tr>
|
||||||
|
<th><strong>filename:</strong></th>
|
||||||
|
<td>${data.meta.name}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><strong>process duration:</strong></th>
|
||||||
|
<td>${data.meta.duration}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><strong>size:</strong></th>
|
||||||
|
<td>${data.meta.sizeb} (${data.meta.sizeh})</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><strong>sha1:</strong></th>
|
||||||
|
<td>${data.meta.sha1}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><strong>md5:</strong></th>
|
||||||
|
<td>${data.meta.md5}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</figure>
|
||||||
|
</details>
|
||||||
|
<article>
|
||||||
|
<header>Header</header>
|
||||||
|
Body
|
||||||
|
<footer>Footer</footer>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<header>Header</header>
|
||||||
|
Body
|
||||||
|
<footer>Footer</footer>
|
||||||
|
</article>
|
||||||
|
<pre>${JSON.stringify(data, null, 2)}</pre>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else {
|
||||||
|
console.error('error uploading file');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function upload_file() {
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.addEventListener('change', (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
process_upload(file);
|
||||||
|
});
|
||||||
|
fileInput.click();
|
||||||
|
}
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
5
static/pico.classless.min.css
vendored
Normal file
5
static/pico.classless.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/pico.classless.min.css.map
Normal file
1
static/pico.classless.min.css.map
Normal file
File diff suppressed because one or more lines are too long
5
static/pico.min.css
vendored
Normal file
5
static/pico.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/pico.min.css.map
Normal file
1
static/pico.min.css.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue