Hacking Biofeedback Machines with Python

Overview

Screen Shot 2014-12-16 at 8.58.59 PMThis walk-through will make use of Python and Wireshark to sniff data packets out of a commercial product with bio sensors (heart rate and GSR.)  We will pull data from an iOM device that has no public endpoints and a closed API.  We will make use of a packet sniffer (Wireshark) and Python to write a few lines of code to listen to the port and send the commands we discover in the sniffing to activate the hardware.

We can’t use PyUsb, libusb nor libhid … details at the bottom, as to why.

In this post I’ll show how to get the data from USB hardware with a non-public API. In part II (coming later) I’ll go over parsing the data.

The Situation

I bought a Biofeedback machine.  I choose iOM because it had both GSR (Galvanic Skin Response) & heart rate sensors.   I wanted to pull in data from the machine myself.  One reason I wanted to access the data myself, was that their software (Grapher) only shows limited GSR results.  It displays the GSR to one decimal place.  When the individual doesn’t register much on GSR it’s hard to get a resolution in the graph to see emotional changes.  There was some windows software that was able to get to 2 decimal places, so I knew this was doable.

I reached out to the development team of iOM (Wild Divine.)  They responded that they didn’t have any public API endpoints to work with, but they did tell me I could find some tools around that might be useful.  Unfortunately all the tools out there with an API were Windows only and I wanted to get this working on OSX.

If you want to see the whole history of what happened, scroll to the bottom.

Part I: Listen to the Port

Screen Shot 2014-12-16 at 8.19.31 PMSince we don’t have an open API, we need to know the port that the device talks on.  By going to the iOM driver utility options, it tells us that the iOM communicates on port 8888.

So let’s open a connection to our local port 8888, in Python.

import socket
 
s = socket.socket()
port = 8888
 
s.connect(('127.0.0.1', port))

Let’s add a loop so we can print out each line of data that comes from the iOM:

while True:
    print(s.recv(1024))

We’ll leave it True for now, just so we get everything.  We’ll manually shut it off as needed for now.

If you run that code, it will most likely show no data coming in.  We’re making a socket connection but nothing else is happening.  But check this out:

Open your iOM Driver’s Device Status window and click Start/Stop.

You’ll see some info splash in the output!  So we’re communicating with the socket, but we aren’t getting any bio data from the sensors.  Why?

Part II: Sending the Right Event

It took me a few hours, but I figured something out… If you have this little scrip running and then go to the URL below, you’ll see the bio data come in!:
http://www.wilddivine.com/content/grapher/grapher.html

What’s happening is that the page, or the SWF itself is sending an event to the hardware.  I happened to notice that this was coming early in the data from the socket:

b’\x00′

So I tried to send that after I connected to the socket:
s.send(b’\x00′)

Nothing.  We need to do more!  We need a sniffer to help us find the command coming from the software to the hardware.

Screen Shot 2014-12-16 at 8.31.49 PMOpen up your favorite sniffer… for me I like to use Wireshark.  We want to know what command is being sent from the page/swf.  When Wireshark is open, click on the Loopback interface.

Once you have your sniffer running on the loopback interface (which is basically your localhost) Hit that URL again:

http://www.wilddivine.com/content/grapher/grapher.html

Screen Shot 2014-12-16 at 8.35.41 PMOnce the page loads, stop the Wireshark/sniffer capture.  Click on each packet in the recording.  You will find one with some data with the line , this is clue that we’re going to be getting some command to “start” the process.

Somewhere after that packet is a packet like this one.Screen Shot 2014-12-16 at 7.59.21 PM  It has a command to start, with a variable tag.  It resembles XML.

 

 

 

It turns out we just need to send that command:

s.send(b"""<command></command>
 
 
 
 
 
 
 
 
 
 
 
 
 """)

At this point the final code should look like this:

import socket
 
s = socket.socket()
port = 8888
 
s.connect(('127.0.0.1', port))
s.send(b'\x00')
s.send(b"""<command></command>
 
 
 
 
 
 
 
 
 
 
 
 
 """)
s.send(b'\x00')
while True:
 print(s.recv(1024))

Part III: Turn it On!

Plug in your iOM (if it isn’t plugged in), and put it on.  Now run the script above… you should get some output…

b"&lt;m&gt;&lt;p sr='151' hr='1425' sf='1.510000' hf='14.250000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791971' tu='985033' tf='1418791971.985033' hc='0.795088' /&gt;&lt;p sr='151' hr='1726' sf='1.510000' hf='17.260000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='18366' tf='1418791972.018366' hc='0.795088' /&gt;&lt;p sr='151' hr='1810' sf='1.510000' hf='18.100000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='51699' tf='1418791972.051699' hc='0.795088' /&gt;&lt;/m&gt;\x00"
b"&lt;m&gt;&lt;p sr='151' hr='1653' sf='1.510000' hf='16.530000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='85032' tf='1418791972.085032' hc='0.795088' /&gt;&lt;p sr='151' hr='1391' sf='1.510000' hf='13.910000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='118365' tf='1418791972.118365' hc='0.795088' /&gt;&lt;p sr='151' hr='1079' sf='1.510000' hf='10.790000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='151698' tf='1418791972.151698' hc='0.795088' /&gt;&lt;/m&gt;\x00"
b"&lt;m&gt;&lt;p sr='151' hr='800' sf='1.510000' hf='8.000000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='185031' tf='1418791972.185031' hc='0.795088' /&gt;&lt;p sr='151' hr='624' sf='1.510000' hf='6.240000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='218364' tf='1418791972.218364' hc='0.795088' /&gt;&lt;p sr='151' hr='558' sf='1.510000' hf='5.580000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='251697' tf='1418791972.251697' hc='0.795088' /&gt;&lt;/m&gt;\x00"
b"&lt;m&gt;&lt;p sr='151' hr='566' sf='1.510000' hf='5.660000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='285030' tf='1418791972.285030' hc='0.795088' /&gt;&lt;p sr='151' hr='604' sf='1.510000' hf='6.040000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='318363' tf='1418791972.318363' hc='0.795088' /&gt;&lt;/m&gt;\x00"
b"&lt;m&gt;&lt;p sr='151' hr='659' sf='1.510000' hf='6.590000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='351696' tf='1418791972.351696' hc='0.795088' /&gt;&lt;p sr='151' hr='698' sf='1.510000' hf='6.980000' hb='97.661378' br='0.000000' bc='4.122181' ts='1418791972' tu='385029' tf='1418791972.385029' hc='0.795088' /&gt;&lt;/m&gt;\x00"

It’s pretty obvious that hb is the Heart Bate/Rate… br and bc is the breath related data from the sensors and sf is the skin conductance (GSR) sensor data.

Check it out… the heart rate and GSR data is coming in with a lot more detail.  This will help in graphing to greater resolution.

History

I started out trying to get this working in OSX… I started with the Python PyUsb library… which uses libusb.  It turns out though in OSX we can’t listen to a usb device, as the kernel has full control.  Suggestions are to use lib hid.  Unfortunately libhid is difficult to get working on OSX.  There’s a brew install for it, but Python never recognized it post install.

I turned from the complex solution of libhid to using just trying to listen to the port itself.  At first I heard nothing on the port.  But then noticed as I forced the Driver to activate for the hardware I would get a notice.  That was good news.  I was able to get the basic info that the device was recognized.  But no data.

I decided to try and open the public page while my code was running… and there was all the sensor data!  So a command was getting sent to the hardware to start it.  At this point, I used Wireshark to find the command.  I started it and hit the public page sending the start command.  Then I saw it in the packets… I took the command and sent the entire XML block.

Bingo!

References   [ + ]

1, 2. '127.0.0.1', port
Hacking Biofeedback Machines with Python
User Rating: 0 (0 votes)
Tags:
  • Matthew

    Thank you for sharing, this recipe worked for me!