-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
3,729 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,3 +87,6 @@ ENV/ | |
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# Ignore Jetbrains editors | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include ooktools/share/template.grc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,93 @@ | ||
# ooktools | ||
<h4 align="center"> | ||
ooktools | ||
<br> | ||
<img src="images/banner.png"> | ||
</h4> | ||
|
||
**ooktools** aims to help with the reverse engineering of [on-off keying](https://en.wikipedia.org/wiki/On-off_keying) data sources such as wave files or raw frames captured using [RfCat](https://bitbucket.org/atlas0fd00m/rfcat). | ||
|
||
--- | ||
|
||
### why? | ||
I recently [played around a little with static key remotes](https://virtualenv.pypa.io/en/stable/), and wrote some code to help with the reverse engineering thereof. | ||
|
||
### major features | ||
|
||
- Binary string extraction from wave file recordings. | ||
- Wave file cleanups to remove noise in On-off keying recordings. | ||
- Graphing capabilities for wave files. | ||
- General information extraction of wave files. | ||
- Signal recording and playback using `json` definition files that can be shared. | ||
- Plotting of data from the previously mentioned `json` recordings. | ||
- Signal searching for On-off keying type data. | ||
- Sending signals in both binary, complete PWM formatted or hex strings using an RfCat dongle. | ||
- Gnuradio `.grc` template file generation. | ||
|
||
### installation | ||
You can install `ooktools` in two ways. Either from `pip` or from source. In case of a source installation, you may want to optionally consider installing it in a [virtualenv](https://virtualenv.pypa.io/en/stable/). | ||
|
||
#### rfcat | ||
In both installation cases, you need to install [RfCat](https://bitbucket.org/atlas0fd00m/rfcat). This too can be done in two ways. On Kali Linux, you can install it with a simple `apt` command: | ||
|
||
``` | ||
$ apt install rfcat | ||
``` | ||
|
||
Or, if you need to manually install it, download the latest [RfCat sources](https://bitbucket.org/atlas0fd00m/rfcat/downloads) and run the `setup.py` script: | ||
|
||
``` | ||
$ wget -c https://bitbucket.org/atlas0fd00m/rfcat/downloads/rfcat_150225.tgz | ||
$ tar xjvf rfcat_150225.tgz | ||
$ cd rfcat_150225 | ||
$ python setup.py install | ||
``` | ||
#### ooktools | ||
Pip Package: | ||
``` | ||
$ pip install ooktools | ||
``` | ||
|
||
Using this method, you should have the `ooktools` command available globally. | ||
|
||
From source: | ||
``` | ||
$ git clone https://github.com/leonjza/ooktools.git | ||
$ cd ooktools | ||
$ pip install -r requirements.txt | ||
``` | ||
|
||
If you installed from source then you can invoke `ooktools` with as a module using `python -m ooktools.console` from the directory you cloned to. | ||
|
||
### usage | ||
There are a number of sub commands that are grouped by major category. At anytime, add the `--help` argument to get a full description of any other sub commands and or arguments available. | ||
|
||
``` | ||
$ ooktools --help | ||
_ _ _ | ||
___ ___| |_| |_ ___ ___| |___ | ||
| . | . | '_| _| . | . | |_ -| | ||
|___|___|_,_|_| |___|___|_|___| v0.1 | ||
On-off keying tools for your SD-arrrR | ||
https://github.com/leonjza/ooktools | ||
Usage: ooktools [OPTIONS] COMMAND [ARGS]... | ||
Options: | ||
--help Show this message and exit. | ||
Commands: | ||
gnuradio GNU Radio Commands. | ||
signal Signal Commands. | ||
wave Wave File Commands. | ||
``` | ||
|
||
For examples, please refer to the blogpost [here](https://leonjza.github.io/blog/2016/10/08/introducing-ooktools.-on-off-keying-tools-for-your-sdr/). | ||
|
||
### known issues | ||
Nothing is perfect I guess. One of the biggest problems would be test cases and variations. So, here is the stuff that I know is not 100% perfect. Pull requests welcome! | ||
|
||
- Wave file operations such as `graph` and `clean` break when the wave file is too long. ~50M samples seem to start hitting the point of breakage. | ||
- The `matplotlib` usage is silly from a performance perspective. Its the main reason I don't have live graphs in too as I just cant get it working great. | ||
|
||
## license | ||
Please refer to the [LICENSE](https://github.com/leonjza/ooktools/blob/master/LICENSE) file. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# The MIT License (MIT) | ||
# | ||
# Copyright (c) 2016 Leon Jacobs | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
|
||
import click | ||
from pkg_resources import get_distribution | ||
|
||
banner = (""" _ _ _ | ||
___ ___| |_| |_ ___ ___| |___ | ||
| . | . | '_| _| . | . | |_ -| | ||
|___|___|_,_|_| |___|___|_|___| v{} | ||
On-off keying tools for your SD-arrrR""".format(get_distribution('ooktools').version)) | ||
|
||
click.secho('{}'.format(banner), bold=True) | ||
click.secho('https://github.com/leonjza/ooktools\n', dim=True) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# The MIT License (MIT) | ||
# | ||
# Copyright (c) 2016 Leon Jacobs | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
|
||
from __future__ import absolute_import | ||
|
||
import struct | ||
import wave | ||
|
||
import click | ||
import numpy | ||
|
||
from ..utilities import cleanup_wave_data | ||
|
||
|
||
def clean_pwm_wave(source, destination): | ||
""" | ||
Clean up a source wave file with PWM encoded data. | ||
The basic idea here is to read the source data and calculate | ||
the average height of the samples. Using this height, we iterate | ||
over all of the samples and replace the actual value with | ||
a 1 or a 0. | ||
Finally, in order to improve the graphing ability, we amplify | ||
the 1's value by 10000 and write the results out to | ||
a new wave file with the same attributes as the source. | ||
:param destination: | ||
:param source: | ||
:return: | ||
""" | ||
|
||
# Read the frames into a numpy array | ||
signal = numpy.fromstring(source.readframes(-1), dtype=numpy.int16) | ||
signal = cleanup_wave_data(signal) | ||
|
||
# Prepare the output wave file. We will use exactly | ||
# the same parameters as the source | ||
output_wave = wave.open(destination, 'w') | ||
output_wave.setparams(source.getparams()) | ||
|
||
click.secho('Normalizing values..') | ||
|
||
frames = [] | ||
|
||
for _, value in numpy.ndenumerate(signal): | ||
|
||
# If the value is one, amplify it so that it is | ||
# *obviously* not 0. This makes graphing easier too. | ||
if value == 1: | ||
value *= 10000 | ||
|
||
frames.append(struct.pack('h', value)) | ||
|
||
click.secho('Writing output to file: {}'.format(destination)) | ||
output_wave.writeframes(''.join(frames)) | ||
|
||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
# The MIT License (MIT) | ||
# | ||
# Copyright (c) 2016 Leon Jacobs | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
|
||
from datetime import datetime | ||
|
||
import click | ||
import numpy | ||
import peakutils | ||
|
||
try: | ||
import matplotlib.pyplot as plt | ||
import matplotlib.animation as animation | ||
|
||
plotting = True | ||
|
||
except RuntimeError: | ||
plotting = False | ||
|
||
|
||
def _can_plot(): | ||
if not plotting: | ||
click.secho('Plotting library was not sucesfully imported.\n' | ||
'Ensure your python installation can run `import ' | ||
'matplotlib.pyplot` without errors.', fg='red') | ||
|
||
return False | ||
|
||
return True | ||
|
||
|
||
def generate_wave_graph(source, peaks): | ||
""" | ||
Generate a plot from a wave file source. | ||
Optionally, include peak calculations. | ||
Source: | ||
https://github.com/MonsieurV/py-findpeaks/blob/master/tests/vector.py | ||
:param source: | ||
:param peaks: | ||
:return: | ||
""" | ||
|
||
if not _can_plot(): | ||
return | ||
|
||
click.secho('Reading {} frames from source.'.format(source.getnframes()), fg='green') | ||
click.secho('Preparing plot.', fg='green', dim=True) | ||
|
||
# Read the source data | ||
signal = source.readframes(-1) | ||
signal = numpy.fromstring(signal, dtype=numpy.int16) | ||
|
||
_, ax = plt.subplots(1, 1, figsize=(8, 4)) | ||
ax.plot(signal, 'b', lw=1) | ||
|
||
# If we have to include peak information, calculate that | ||
if peaks: | ||
click.secho('Calculating peak information too.', dim=True) | ||
indexes = peakutils.indexes(signal, thres=0.02 / max(signal), min_dist=100) | ||
|
||
if indexes.size: | ||
label = 'peak' | ||
label = label + 's' if indexes.size > 1 else label | ||
ax.plot(indexes, signal[indexes], '+', mfc=None, mec='r', mew=2, ms=8, | ||
label='%d %s' % (indexes.size, label)) | ||
ax.legend(loc='best', framealpha=.5, numpoints=1) | ||
|
||
# Continue graphing the source information | ||
ax.set_xlim(-.02 * signal.size, signal.size * 1.02 - 1) | ||
ymin, ymax = signal[numpy.isfinite(signal)].min(), signal[numpy.isfinite(signal)].max() | ||
yrange = ymax - ymin if ymax > ymin else 1 | ||
ax.set_ylim(ymin - 0.1 * yrange, ymax + 0.1 * yrange) | ||
ax.set_xlabel('Frame #', fontsize=14) | ||
ax.set_ylabel('Amplitude', fontsize=14) | ||
|
||
# Finally, generate the graph | ||
plt.show() | ||
|
||
return | ||
|
||
|
||
def generage_saved_recording_graphs(source, count, series): | ||
""" | ||
Plot frames from a recording | ||
:param source: | ||
:param count: | ||
:param series: | ||
:return: | ||
""" | ||
|
||
if not _can_plot(): | ||
return | ||
|
||
click.secho('Source Information:') | ||
click.secho('Recording Date: {}'.format(datetime.fromtimestamp(source['date'])), bold=True, fg='green') | ||
click.secho('Recording Frequency: {}'.format(source['frequency']), bold=True, fg='green') | ||
click.secho('Recording Baud: {}'.format(source['baud']), bold=True, fg='green') | ||
click.secho('Recording Framecount: {}'.format(source['framecount']), bold=True, fg='green') | ||
|
||
# If we dont have a series to plot, plot the number of frames | ||
# from the start to count | ||
if not series: | ||
data = source['frames'][:count] | ||
click.secho('Preparing Graph for {} plots...'.format(count)) | ||
else: | ||
start, end = series | ||
data = source['frames'][start:end] | ||
click.secho('Preparing Graph for {} plots from {} to {}...'.format(len(data), start, end)) | ||
|
||
# Place holder to check if we have set the first plot yet | ||
fp = False | ||
|
||
# Start the plot. | ||
fig = plt.figure(1) | ||
fig.canvas.set_window_title('Frame Data Comparisons') | ||
|
||
# Loop over the frames, plotting them | ||
for (index,), frame in numpy.ndenumerate(data): | ||
|
||
# If it is not the first plot, set it keep note of the | ||
# axo variable. This is the original plot. | ||
if not fp: | ||
|
||
axo = plt.subplot(len(data), 1, index + 1) | ||
axo.grid(True) | ||
axo.set_xlabel('Symbols') | ||
axo.set_ylabel('Aplitude') | ||
axo.xaxis.set_label_position('top') | ||
|
||
# Flip the first plot variable as this is done | ||
fp = True | ||
|
||
# If we have plotted before, set the new subplot and share | ||
# the X & Y axis with axo | ||
else: | ||
ax = plt.subplot(len(data), 1, index + 1, sharex=axo, sharey=axo) | ||
ax.grid(True) | ||
|
||
# Plot the data | ||
plt.plot(numpy.frombuffer(buffer=str(frame), dtype=numpy.int16)) | ||
|
||
# Show the plot! | ||
click.secho('Launching the graphs!') | ||
plt.show() |
Oops, something went wrong.