[TECH] Handmade anti theft system
Posted on Tue 14 April 2020 in Tech, Hardware, Setup
Following my last post I decided to play a bit more with 4G networks and the oportunities of having it on a laptop.
During those Easter's holiday I finally took the time to prototype a small project I had in mind for a long time, I wanted to design an anti-theft system for my laptop. As most of the disks are using full disk encryption, it's not possible to integrate such a mechanism inside the system itself. The obvious solution was to bundle it before the system start: the initramfs.
The roadmap was the following: - create a custom initramfs - bundle a modem management software in it - script this tool to retrieve and send GPS/4G location to a central server - bonus: take a picture using the webcam and send it to the central server
Writing an initramfs
As always, gentoo wiki is a amazing start for this kind of task:
readonly ROOTDIR=/home/ex0ns/Projects/initramfs/
mkdir --parents $ROOTDIR/{bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}
cp --archive /dev/{null,console,tty,nvme0n1p4,random,urandom,cdc-wdm0} $ROOTDIR/dev/
This will create the basic structure for our initramfs, I'm using lvm and cryptsetup
hence urandom
and random
devices are required. cdc-wdm0 is my 4G modem, and
my laptop has nvme, this is why I don't have any sda device.
I then needed to bundle lvm and cryptsetup binaries into my initramfs, those can be staticaly compiled and thus easily copied into the initramfs. We will also add busybox in our initramfs in order to run the init script as well as having a few useful tools in case of failure.
cp --archive /sbin/cryptsetup $ROOTDIR/sbin/cryptsetup
cp --archive /sbin/lvm.static $ROOTDIR/sbin/lvm
cp --archive /bin/busybox $ROOTDIR/bin/busybox
ln -s $ROODIR/bin/busybox $ROOTDIR/bin/sh
Having those binary, it's now time to write a very simple (as simple as possible) init script to decrypt, mount and boot the system:
#!/bin/busybox sh
rescue_shell() {
printf '\e[1;31m' # bold red foreground
printf "\$1 Dropping you to a shell."
echo "$1"
printf "\e[00m\n" # normal colour foreground
# load the keymap
exec sh
}
mount -t proc none /proc || rescue_shell "Unable to mount proc"
mount -t sysfs none /sys || rescue_shell "Unable to mound sys"
cryptsetup --tries 5 luksOpen /dev/nvme0n1p4 root || rescue_shell "Cryptsetup failed"
lvm vgscan --mknodes || rescue_shell "vgscan failed #1"
lvm lvchange -a ly vg0/root || rescue_shell "lvchange failed"
lvm lvchange -a ly vg0/usr || rescue_shell "lvchange failed"
lvm vgscan --mknodes || rescue_shell "vgscan failed #2"
mount -o ro /dev/mapper/vg0-root /mnt/root
mount -o ro /dev/mapper/vg0-usr /mnt/root/usr
umount /proc
umount /sys
# Boot the real thing.
exec switch_root /mnt/root /sbin/init || rescue_shell "Unable to switch root"
I had the (would not recommend it) strange idea of having a separate /usr
so I had to
take care of that as well in my init script. The script is pretty classic, it asks for
the password to decrypt the disk, scan for specific LVM partitions (no udev support needed)
and finally mount them into the new root of the system /mnt/root
in our case.
We can already package and test our initramfs:
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs-debug.img
mount /boot
cp ../initramfs-debug.img /boot/
Add an entry in Grub/rEFI and reboot \o/
4G capabilities at boot
My first thought was to bundle mmcli into my initramfs as I have already written a management script for it. Unfortunately, mmcli is only a userland tool that uses dbus to communicate with the underlying device.
However, other tools exists in the libqmi
and libmbim
package to manage 4G devices: qmicli
and mbimcli. There is no static-libs
useflag for those package and will hence require some .so
to work, I wrote a small utility script to bundle dynamic binaries into the my initramfs:
#!/bin/bash
bundle_file() {
file="$(which $1)"
echo "Bundling $1 in initramfs"
for f in $file $(lddtree $"$file" | tail -n+2 | awk '/=>/ {print $NF}')
do
dir=$(dirname $f)
[ -d "./$dir" ] || mkdir -p "./$dir"
echo "Adding $f to initramfs"
cp "$f" "./$f"
done
echo ""
}
bundle_file "qmicli"
bundle_file "mbimcli"
This script parses the output of the ldtree
command to include the required library in the
initramfs (as well as the binary itself).
After a few failure, a lot of reboot and some research
here,
here and
here it seems that my card was requiering
some kind of proxy between qmi and mbim tools, that proxy can be find at /usr/libexec/mbim-proxy
,
I simply added this file to my bundle script:
bundle_file "/usr/libexec/mbim-proxy"
bundle_file "mbim-network"
I also added the mbim-network
binary that was missing from my setup.
4g script
This script is far from beeing 100% resilient and sometime the interface seems to be stuck or in a weird state at boot, but work well from the proof of concept I want to make:
#!/bin/sh
MMCLI="mbimcli --device=/dev/cdc-wdm0 --device-open-proxy --no-close "
get_network_info()
{
local config="$1"
local search="$2"
echo "$config" | sed -n -E "/$search/ s/$search: '(.*)'/\1/ p"
}
qmicli -d /dev/cdc-wdm0 --device-open-mbim --dms-set-fcc-authentication
$MMCLI --set-radio-state=on
$MMCLI --query-device-caps # debug purpose
mbim-network /dev/cdc-wdm0 start
$MMCLI --query-radio-state # debug purpose
sleep 10
config=$($MMCLI --query-ip-configuration)
if [ $? -eq 0 ]
then
ip="$(get_network_info "$config" 'IP \[0\]')"
gateway="$(get_network_info "$config" "Gateway")"
echo "Adding $ip via $gateway to wwan0"
ip addr add $ip dev wwan0
ip link set wwan0 up
ip route add default via $gateway
ping -c 1 1.1.1.1
ip addr # debug purpose
else
echo "something wrong happened"
fi
Nothing really new here, only some low level device management, some logging and some parsing to properly configure the network device (only the IP and the default route are needed here).
I added this to my init
script, packaged my initramfs and rebooted, and it worked:
the ping worked \o/ !
The goal is now to setup as very basic server that could collect informations sent by the client. As my current goal for this project is to write a very basic proof of concept, the server will be very rudimentary (and I mean it):
while true; do; nc -l -p 1234 > "raw_data_$(date).raw"; echo "File written"; done
We sill send to this server the output from two command:
qmicli -d /dev/cdc-wdm0 --nas-get-cell-location-info
qmicli -d /dev/cdc-wdm0 --nas-get-serving-system
Those two commands are giving a lot of useful informations. but I mainly need the following:
- 3GPP cell ID
- MCC: Country code, Switzerland's one is 228
- MNC: Operator code
I simply added those line to my initramfs script:
readonly IP=
readonly PORT=1234
qmicli -d /dev/cdc-wdm0 --nas-get-cell-location-info | nc $IP $PORT
qmicli -d /dev/cdc-wdm0 --nas-get-serving-system | nc $IP $PORT
No need to add nc
to our initramfs as busybox already contains rudimentary nc
binary.
After a reboot, a new file was available on my server, containing all tower and cell informations
I could get from my 4g modem.
Bonus: capturing webcam input
As I still had some time left and discover a really simple tool to capture images using the webcam, it only requires libgd, it's called fswebcam. I compiled it, bundled it into my initramfs and added the following line to my init script:
readonly PICTURE_PORT=
send_webcam_picture()
{
/usr/bin/fswebcam -d /dev/video0 -r 640x480 --jpeg 100 -F 5 test.jpg
cat test.jpg | nc $IP $PICTURE_PORT
}
And spawned a new netcat listener on a different port (cheap server, I told you):
while true; do; nc -l -p $PICTURE_PORT > "$(date)_picture.jpg"; echo "File written"; done
And then again, after a reboot, I received the picture on my server.
Next steps
Obviously writing a more advanced server would be the next step. The website OpenCellId has a list of cell with their MCC and MNC as well as the (more or less accurate) GPS coordinate of the antennas.
ex0ns