I recently picked up Ubiquiti’s latest UniFi Cloud Key that includes a nice LCD stats display.
While playing with it, I remembered that it used to be possible to control UniFi device LEDs via an SSH session:
echo 255 > /sys/class/leds/blue/brightness # LED on echo 0 > /sys/class/leds/blue/brightness # LED off
If it was possible to control the LED, I wondered, could the LCD display be controlled too? That turned into a fun reverse-engineering exercise I’m documenting here for others to build on.
What’s the display?
This Ubiquiti forum post contained another command to turn off the LCD display, which gives a first clue:
echo 0 > /sys/class/backlight/fb_sp8110/brightness
fb_sp8110? It seems specific to the LCD. When Googling it, I only found other Ubiquiti forum posts:
The other two results provide the next clue: logs containing error messages from
fb_sp8110 that reference an interestingly named fbtftupdatedisplay module.
fb_sp8110 spi3.0: fbtft_update_display: <some error>
fbtft_update_display and formed a clearer picture:
fbtftis a Linux framebuffer driver for SPI TFT screens
- TFT stands for “thin-film-transistor”, a specific variant of LCD screen
- SPI stands for ”serial peripheral interface”, a communication protocol
- SPI TFT displays are common in hobbyist projects (ex. Raspberry Pi)
How can we control it?
This is a good start - let’s do some more basic enumeration with this new information.
If you search the kernel logs for
tft, you can find specifications of the display:
root@cloudkey:/sys/class# dmesg | grep -i tft [ 0.819476] fbtft_of_value: width = 160 [ 0.819479] fbtft_of_value: height = 60 [ 0.819483] fbtft_of_value: buswidth = 8 [ 0.819486] fbtft_of_value: bpp = 16 [ 0.819490] fbtft_of_value: rotate = 180 [ 0.819494] fbtft_of_value: fps = 100
And if you search the kernel logs for graphics, you can find the block device that represents the display in the filesystem (
$ dmesg | grep graphics [ 0.945633] graphics fb0: fb_sp8110 frame buffer, 160x60, 18 KiB video memory, 16 KiB buffer memory, fps=100, spi3.0 at 19 MHz
Writing to this block device should cause something to happen on the display. As a quick test, we can write random bytes to the frame buffer:
cp /dev/urandom /dev/fb0
Ta-da! A display full of (our) static.
Similarly, you can clear / blank the display with:
cp /dev/null /dev/fb0
Persisting the Screen
In a few seconds, the static disappears as the original UI returns. It’s periodically redrawn by the UniFi software. If the aim is to replace the screen with our own information, we probably don’t want to fight for control of the framebuffer.
We can use lsof to find which process ID is drawing the UniFi UI and then disable it. The process begins again after a device reboot.
root@cloudkey:~# lsof /dev/fb0 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME ck-ui 685 root mem CHR 29,0 4441 /dev/fb0 ck-ui 685 root 4u CHR 29,0 0t0 4441 /dev/fb0 root@cloudkey:~# pgrep ck-ui 685 root@cloudkey:~# kill 685 root@cloudkey:~# watch pgrep ck-ui
Drawing Arbitrary Data
#!/usr/bin/env python2 import os import struct from PIL import Image im = Image.open('test.bmp') w, h = im.size with open('temp.fb', 'wb') as f: for j in range(0,h): for i in range(0,w): r,g,b =im.getpixel((i,j)) rgb=struct.pack('H',((r >> 3) << 11)|((j >> 2) << 5)|(b >> 3)) f.write(rgb); os.system('cat temp.fb > /dev/fb0')
From here, you could use matplotlib to generate custom infographics about your network(s) fed by the controller’s API, and so on.