Skip to content

Commit bc34f02

Browse files
committed
📦 First release!
1 parent 44f88fa commit bc34f02

File tree

17 files changed

+3729
-1
lines changed

17 files changed

+3729
-1
lines changed

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,6 @@ ENV/
8787

8888
# Rope project settings
8989
.ropeproject
90+
91+
# Ignore Jetbrains editors
92+
.idea/

‎MANIFEST.in‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ooktools/share/template.grc

‎README.md‎

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,93 @@
1-
# ooktools
1+
<h4 align="center">
2+
ooktools
3+
<br>
4+
<img src="images/banner.png">
5+
</h4>
6+
7+
**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).
8+
9+
---
10+
11+
### why?
12+
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.
13+
14+
### major features
15+
16+
- Binary string extraction from wave file recordings.
17+
- Wave file cleanups to remove noise in On-off keying recordings.
18+
- Graphing capabilities for wave files.
19+
- General information extraction of wave files.
20+
- Signal recording and playback using `json` definition files that can be shared.
21+
- Plotting of data from the previously mentioned `json` recordings.
22+
- Signal searching for On-off keying type data.
23+
- Sending signals in both binary, complete PWM formatted or hex strings using an RfCat dongle.
24+
- Gnuradio `.grc` template file generation.
25+
26+
### installation
27+
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/).
28+
29+
#### rfcat
30+
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:
31+
32+
```
33+
$ apt install rfcat
34+
```
35+
36+
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:
37+
38+
```
39+
$ wget -c https://bitbucket.org/atlas0fd00m/rfcat/downloads/rfcat_150225.tgz
40+
$ tar xjvf rfcat_150225.tgz
41+
$ cd rfcat_150225
42+
$ python setup.py install
43+
```
44+
#### ooktools
45+
Pip Package:
46+
```
47+
$ pip install ooktools
48+
```
49+
50+
Using this method, you should have the `ooktools` command available globally.
51+
52+
From source:
53+
```
54+
$ git clone https://github.com/leonjza/ooktools.git
55+
$ cd ooktools
56+
$ pip install -r requirements.txt
57+
```
58+
59+
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.
60+
61+
### usage
62+
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.
63+
64+
```
65+
$ ooktools --help
66+
_ _ _
67+
___ ___| |_| |_ ___ ___| |___
68+
| . | . | '_| _| . | . | |_ -|
69+
|___|___|_,_|_| |___|___|_|___| v0.1
270
On-off keying tools for your SD-arrrR
71+
https://github.com/leonjza/ooktools
72+
73+
Usage: ooktools [OPTIONS] COMMAND [ARGS]...
74+
75+
Options:
76+
--help Show this message and exit.
77+
78+
Commands:
79+
gnuradio GNU Radio Commands.
80+
signal Signal Commands.
81+
wave Wave File Commands.
82+
```
83+
84+
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/).
85+
86+
### known issues
87+
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!
88+
89+
- 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.
90+
- 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.
91+
92+
## license
93+
Please refer to the [LICENSE](https://github.com/leonjza/ooktools/blob/master/LICENSE) file.

‎images/banner.png‎

23.4 KB
Loading

‎ooktools/__init__.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2016 Leon Jacobs
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
import click
24+
from pkg_resources import get_distribution
25+
26+
banner = (""" _ _ _
27+
___ ___| |_| |_ ___ ___| |___
28+
| . | . | '_| _| . | . | |_ -|
29+
|___|___|_,_|_| |___|___|_|___| v{}
30+
On-off keying tools for your SD-arrrR""".format(get_distribution('ooktools').version))
31+
32+
click.secho('{}'.format(banner), bold=True)
33+
click.secho('https://github.com/leonjza/ooktools\n', dim=True)

‎ooktools/commands/__init__.py‎

Whitespace-only changes.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2016 Leon Jacobs
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
from __future__ import absolute_import
24+
25+
import struct
26+
import wave
27+
28+
import click
29+
import numpy
30+
31+
from ..utilities import cleanup_wave_data
32+
33+
34+
def clean_pwm_wave(source, destination):
35+
"""
36+
Clean up a source wave file with PWM encoded data.
37+
38+
The basic idea here is to read the source data and calculate
39+
the average height of the samples. Using this height, we iterate
40+
over all of the samples and replace the actual value with
41+
a 1 or a 0.
42+
43+
Finally, in order to improve the graphing ability, we amplify
44+
the 1's value by 10000 and write the results out to
45+
a new wave file with the same attributes as the source.
46+
47+
:param destination:
48+
:param source:
49+
:return:
50+
"""
51+
52+
# Read the frames into a numpy array
53+
signal = numpy.fromstring(source.readframes(-1), dtype=numpy.int16)
54+
signal = cleanup_wave_data(signal)
55+
56+
# Prepare the output wave file. We will use exactly
57+
# the same parameters as the source
58+
output_wave = wave.open(destination, 'w')
59+
output_wave.setparams(source.getparams())
60+
61+
click.secho('Normalizing values..')
62+
63+
frames = []
64+
65+
for _, value in numpy.ndenumerate(signal):
66+
67+
# If the value is one, amplify it so that it is
68+
# *obviously* not 0. This makes graphing easier too.
69+
if value == 1:
70+
value *= 10000
71+
72+
frames.append(struct.pack('h', value))
73+
74+
click.secho('Writing output to file: {}'.format(destination))
75+
output_wave.writeframes(''.join(frames))
76+
77+
return
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2016 Leon Jacobs
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
from datetime import datetime
24+
25+
import click
26+
import numpy
27+
import peakutils
28+
29+
try:
30+
import matplotlib.pyplot as plt
31+
import matplotlib.animation as animation
32+
33+
plotting = True
34+
35+
except RuntimeError:
36+
plotting = False
37+
38+
39+
def _can_plot():
40+
if not plotting:
41+
click.secho('Plotting library was not sucesfully imported.\n'
42+
'Ensure your python installation can run `import '
43+
'matplotlib.pyplot` without errors.', fg='red')
44+
45+
return False
46+
47+
return True
48+
49+
50+
def generate_wave_graph(source, peaks):
51+
"""
52+
Generate a plot from a wave file source.
53+
Optionally, include peak calculations.
54+
55+
Source:
56+
https://github.com/MonsieurV/py-findpeaks/blob/master/tests/vector.py
57+
58+
:param source:
59+
:param peaks:
60+
:return:
61+
"""
62+
63+
if not _can_plot():
64+
return
65+
66+
click.secho('Reading {} frames from source.'.format(source.getnframes()), fg='green')
67+
click.secho('Preparing plot.', fg='green', dim=True)
68+
69+
# Read the source data
70+
signal = source.readframes(-1)
71+
signal = numpy.fromstring(signal, dtype=numpy.int16)
72+
73+
_, ax = plt.subplots(1, 1, figsize=(8, 4))
74+
ax.plot(signal, 'b', lw=1)
75+
76+
# If we have to include peak information, calculate that
77+
if peaks:
78+
click.secho('Calculating peak information too.', dim=True)
79+
indexes = peakutils.indexes(signal, thres=0.02 / max(signal), min_dist=100)
80+
81+
if indexes.size:
82+
label = 'peak'
83+
label = label + 's' if indexes.size > 1 else label
84+
ax.plot(indexes, signal[indexes], '+', mfc=None, mec='r', mew=2, ms=8,
85+
label='%d %s' % (indexes.size, label))
86+
ax.legend(loc='best', framealpha=.5, numpoints=1)
87+
88+
# Continue graphing the source information
89+
ax.set_xlim(-.02 * signal.size, signal.size * 1.02 - 1)
90+
ymin, ymax = signal[numpy.isfinite(signal)].min(), signal[numpy.isfinite(signal)].max()
91+
yrange = ymax - ymin if ymax > ymin else 1
92+
ax.set_ylim(ymin - 0.1 * yrange, ymax + 0.1 * yrange)
93+
ax.set_xlabel('Frame #', fontsize=14)
94+
ax.set_ylabel('Amplitude', fontsize=14)
95+
96+
# Finally, generate the graph
97+
plt.show()
98+
99+
return
100+
101+
102+
def generage_saved_recording_graphs(source, count, series):
103+
"""
104+
Plot frames from a recording
105+
106+
:param source:
107+
:param count:
108+
:param series:
109+
:return:
110+
"""
111+
112+
if not _can_plot():
113+
return
114+
115+
click.secho('Source Information:')
116+
click.secho('Recording Date: {}'.format(datetime.fromtimestamp(source['date'])), bold=True, fg='green')
117+
click.secho('Recording Frequency: {}'.format(source['frequency']), bold=True, fg='green')
118+
click.secho('Recording Baud: {}'.format(source['baud']), bold=True, fg='green')
119+
click.secho('Recording Framecount: {}'.format(source['framecount']), bold=True, fg='green')
120+
121+
# If we dont have a series to plot, plot the number of frames
122+
# from the start to count
123+
if not series:
124+
data = source['frames'][:count]
125+
click.secho('Preparing Graph for {} plots...'.format(count))
126+
else:
127+
start, end = series
128+
data = source['frames'][start:end]
129+
click.secho('Preparing Graph for {} plots from {} to {}...'.format(len(data), start, end))
130+
131+
# Place holder to check if we have set the first plot yet
132+
fp = False
133+
134+
# Start the plot.
135+
fig = plt.figure(1)
136+
fig.canvas.set_window_title('Frame Data Comparisons')
137+
138+
# Loop over the frames, plotting them
139+
for (index,), frame in numpy.ndenumerate(data):
140+
141+
# If it is not the first plot, set it keep note of the
142+
# axo variable. This is the original plot.
143+
if not fp:
144+
145+
axo = plt.subplot(len(data), 1, index + 1)
146+
axo.grid(True)
147+
axo.set_xlabel('Symbols')
148+
axo.set_ylabel('Aplitude')
149+
axo.xaxis.set_label_position('top')
150+
151+
# Flip the first plot variable as this is done
152+
fp = True
153+
154+
# If we have plotted before, set the new subplot and share
155+
# the X & Y axis with axo
156+
else:
157+
ax = plt.subplot(len(data), 1, index + 1, sharex=axo, sharey=axo)
158+
ax.grid(True)
159+
160+
# Plot the data
161+
plt.plot(numpy.frombuffer(buffer=str(frame), dtype=numpy.int16))
162+
163+
# Show the plot!
164+
click.secho('Launching the graphs!')
165+
plt.show()

0 commit comments

Comments
 (0)