a3nm's blog

More fnacbook hacking

— updated

In this followup to my previous post about hacking the Kobo, I present a few other tips.

Reindexing the collection

It turns out that you can reindex the collection on the device without actually pluging it in a computer. This is very useful if you add or remove files to the device directly (using sftp, for instance), though you do not need it if you are just replacing existing files. The idea is that nickel uses the FIFO /tmp/nickel-hardware-status to get notified about events: if you write usb plug add or usb plug remove to this file, it will act as if the Kobo had been connected or disconnected to the computer.

Hence, you can create a file reindex.sh containing:

echo usb plug add >> /tmp/nickel-hardware-status \
  && sleep 10 \
  && echo usb plug remove

Make it executable and run it with nohup ./reindex.sh. nickel will present a dialog, quickly select "connect", and wait for the fake disconnection and reindexing to take place. Unfortunately, this will disconnect the Wi-Fi (hence the nohup business).

[This one was sent to me by Andreas Heider. Thanks a lot for suggesting this!] I also noticed that, when you download a file through the built-in web-browser, it will get indexed. It could be sensible to run a minimalistic web-server on the device to serve files already stored on the device to make the device index them. Maybe I'll investigate this later.

Automatic reverse ssh

You might want to connect to the device easily without having to find out its IP or worrying about NATs. The right tool for this problem is openvpn, but it has a lot of dependencies and isn't exactly light. A satisfactory (and probably simpler) alternative is to get the device to reverse ssh to a trusted server (with key-based authentication) when it connects to the Internet, so that you can always use this to get a shell.

I already mentioned how to get dropbear running on the device. To get reverse ssh working, it turns out that you have to add busybox ifconfig lo up to the /etc/init.d/rcS file because it isn't done by default. Then, generate a key using dropbearkey -f /root/.ssh/id_rsa -t rsa and copy the fingerprint returned by the command to your authorized_keys file on the trusted server. Obviously, if you worry that unauthorized people might have access to your device, you should use an account with very little permissions.

Now, once you have checked that dbclient -i /root/.ssh/id_rsa USER@SERVER works without asking for a password, you can add the following reverse ssh command at the end of the renew|bound) section of file /etc/udhcpc.d/default.script (note the unsafe "-y" to avoid unknown host key issues, and adapt if you don't like this):

dbclient -y -i /root/.ssh/id_rsa -R 4080: USER@SERVER < dev/ptmx &

Now, when obtaining or renewing a DHCP lease, the device should run the reverse ssh. I have found this to be pretty fragile when testing, for unknown reasons: workarounds you might want to try are using the IP address of SERVER directly (in case DNS resolution is not working properly yet) or moving the command to a separate script which sleeps for a few seconds before running the command.

Leaving the Wi-Fi active

I was complaining in my previous post about the Wi-Fi getting disabled after a few minutes. According to the (very insightful) guide Hacking the Kobo Touch for Dummies, the responsible for this is nickel, and, indeed, just running killall nickel will fix the problem. Obviously, without nickel running, there is little you can do with the device except from using ssh. When you're done, the /etc/init.d/rcS file suggests that you can get nickel back by issuing something like:

if [ $PLATFORM == ntx508 ]; then


export QWS_MOUSE_PROTO="tslib_nocal:/dev/input/event1"
export QWS_KEYBOARD=imx508kbd:/dev/input/event0
export QWS_DISPLAY=Transformed:imx508:Rot90
export NICKEL_HOME=/mnt/onboard/.kobo
export LD_LIBRARY_PATH=/usr/local/Kobo
export LANG=en_US.UTF-8
export UBOOT_MMC=/etc/u-boot/$PLATFORM/u-boot.mmc
export UBOOT_RECOVERY=/etc/u-boot/$PLATFORM/u-boot.recovery

/usr/local/Kobo/nickel -qws

Trouble is, this does not seem to work perfectly (the new nickel instance is a bit broken, especially with regards to Wi-Fi). I would need to investigate more. Of course, to fix things for real, you can always just reboot the device.

Displaying stuff on the Kobo from the shell

People have already figured out how to display stuff on the Kobo's screen from the shell. It's pretty minimalistic (only images in a weird format). To spare a click, here is how you do it:

ffmpeg -i $INPUT.png -vf transpose=2 -f rawvideo
    -pix_fmt rgb565 -s 600x800 -y $OUTPUT.raw
cat $OUTPUT.raw | ssh DEVICE /usr/local/Kobo/pickel showpic

Of course, this means that you can replace the images in /etc/images and /usr/local/Kobo/slideshow by other images. Disappointingly, though, the interesting images (the "powered off" and "sleeping" logos) don't seem to be there and seem to be displayed in another way. The most interesting thing you can customize is the boot animation (/etc/images/on-*.raw.gz).

Retrieving touchscreen events from the device

The (very insightful) guide Hacking the Kobo Touch for Dummies also explains how to recover the touchscreen input events from the device:

evtest /dev/input/event1

There is a lot you can do by forwarding these events to your computer and interpreting them creatively (a custom remote control, for instance). As always, buffering issues are a problem when trying to retrieve the events synchronously. Here is a possible way to retrieve the useful info, where DEVICE is where you can reach the device:

cat <(echo 'evtest /dev/input/event1;') - |
  socat EXEC:'ssh DEVICE',pty,ctty,echo=0 STDIO |
  sed -u 's/$/\n/' |
  stdbuf -i0 -o0 -e0 cut -d ' ' -f 9,11,13

In case you're wondering, the sed command is because the newline at the end of the last event is outputted when the next one happens, which isn't really convenient if you want to pipe the data to something.

Here is a small Python program to run on your computer which accepts this input and will draw what you do on your touchscreen:

import pygame
import pygame.draw
import sys


def line(x1, y1, x2, y2, r=255, g=255, b=255):
  global window
  pygame.draw.aaline(window, (r, g, b), (x1, y1), (x2, y2))

window = pygame.display.set_mode((600, 800))

pre = None

while True:
  l = sys.stdin.readline()
  if not l:
  l = l.rstrip().split(' ')[0:3]
  if len(l) != 3 or '' in l:
  print l
  if pre:
    line(600-int(pre[1]), int(pre[0]), 600-int(l[1]), int(l[0]))
  if l[2] != '0':
    pre = l
    pre = None

Obviously, you should try to minimize the network latency if you want this to be pleasant.

comments welcome at a3nm<REMOVETHIS>@a3nm.net