pivot to web!

This commit is contained in:
pdf 2023-05-19 14:22:34 -07:00
parent 53f960b3b2
commit 60524bf2dc
15 changed files with 254 additions and 137 deletions

View file

@ -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
View 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())

View file

@ -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()

View file

@ -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()

View file

@ -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
View 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>

View file

@ -1 +1,2 @@
aiohttp
binwalk binwalk

View file

@ -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
View 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();
}

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long