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">
|
||||
<img src="doc/binhop.png" alt="Binhop Logo">
|
||||
<img src="static/binhop.png" alt="binhop logo">
|
||||
|
||||
# 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
|
||||
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) •
|
||||
[Usage](#usage) •
|
||||
[Installation and Usage](#installation) •
|
||||
[FAQ](#faq) •
|
||||
[Contributing](#contributing) •
|
||||
[License](#license)
|
||||
</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
|
||||
cd binhop
|
||||
pip install -r requirements.txt
|
||||
pip install .
|
||||
./binhop.py
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
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>
|
||||
```
|
||||
Once running, browse to [http://localhost:8080](http://localhost:8080) to upload a blob.
|
||||
|
||||
## FAQ
|
||||
|
||||
**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?**
|
||||
|
||||
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.**
|
||||
|
||||
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?**
|
||||
|
||||
|
|
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
|
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