Debian on the Samsung SM-T113

Christmas 2020 I was gifted a used SM-T113. Unfortunately, there was no Lineage-OS build, due to missing kernel sources (as far as I could tell). Nonetheless I wanted to
1. kick anything listening to "Ok Google" off this device
2. run Xorg + VNC to integrate it into my multi-monitor setup

Obviously, any serious work starts with rooting. It turned out that TWRP 3.x did not run on the SM-T113, but 2.8.7.0 worked quite fine. Thus I had SuperSU and a useful adb.

Fortunately, the Samsung fan club publishes the original firmware. Via simg2img I could locally mount the /system image and kill off anything suspicious looking. (In fact, it would have been easier to just to it on-device, but at that point I was still thinking about building everything off-device and only uploading a "completed" system once done.)

heimdall (the open-source clone of the Samsung-specific Odin flashing tool) would however not flash a re-assembled image, so I ultimately forwarded the system image via /sdcard and adb shell into TWRP and therein dd.

debootstrap --arch=armhf --foreign unstable ./debian-chroot

gave me a directory tree I could rsync (via SimpleSSHd, a "normal" android app) over to the device (after rw-remounting) into /system/debian.

However, the new chroot could not execute, because

root@goyavewifi:/ # /system/debian/bin/chroot
tmp-mksh: /system/debian/bin/chroot: No such file or directory

(even though it was of course there).

This is happening when the kernel tries to find the dynamic loader indicated in the file:

 # readelf -a chroot
...
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x015d84 0x00015d84 0x00015d84 0x00008 0x00008 R   0x4
  PHDR           0x000034 0x00000034 0x00000034 0x00120 0x00120 R   0x4
  INTERP         0x000154 0x00000154 0x00000154 0x00019 0x00019 R   0x1
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  LOAD           0x000000 0x00000000 0x00000000 0x15d90 0x15d90 R E 0x10000
  LOAD           0x0169f4 0x000269f4 0x000269f4 0x009b0 0x01ba0 RW  0x10000
  DYNAMIC        0x016f00 0x00026f00 0x00026f00 0x00100 0x00100 RW  0x4
  NOTE           0x000170 0x00000170 0x00000170 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x0169f4 0x000269f4 0x000269f4 0x0060c 0x0060c R   0x1

... and android has no /lib/ld-linux-armhf.so.3.

So I followed a bunch of symlinks and copied /system/debian/usr/lib/arm-linux-gnueabihf/ld-2.31.so to /system/ld-lx-armhf.so.3 (which is the same length as the original path). The program headers are at the front of the program, so vim is a fine binary editor for this task. After replacing the first occurence of /lib/ld-linux-armhf.so.3 with the new location, I got

/system/debian/sbin/chroot: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

... because the (now found) dynamic linker cannot find the android system libraries. Thus I needed

LD_LIBRARY_PATH=/system/debian/lib/arm-linux-gnueabihf/ /system/debian/sbin/chroot /system/debian /bin/sh

and it worked.

After looking around in the new system a bit, configuring shell prompts etc, I noticed I also had forgotten to get proc, sys and devpts mounted. Thus I created

root@goyavewifi:/ # cat /system/debian/start.sh
mount -o remount,rw /system
mount -t proc proc /system/debian/proc
mount -t sysfs sysfs /system/debian/sys
mount -t devpts devpts /system/debian/dev/pts
mount -o bind /data/debian/var/cache /system/debian/var/cache
mount -o bind /storage/emulated/legacy /system/debian/sdcard

root@goyavewifi:/ # cat /system/debian/enter.sh
export SHELL=/bin/zsh
export PATH=/bin:/usr/bin:/sbin:/usr/sbin
export TERM=linux
LD_LIBRARY_PATH=/system/debian/lib/arm-linux-gnueabihf/ /system/debian/sbin/chroot.android /system/debian "${@:-/bin/zsh}"

(with a renamed chroot.android after an apt-get update unhelpfully changed the chroot binary).

Now, it was time to explore hardware support. In particular, I wanted to get Xorg to run.

dd if=/dev/urandom of=/dev/fb0

showed no effect, as the android zygote process was still hogging the framebuffer. I added the correct incantations to start.sh and also created stop.sh:

localhost# cat start.sh
setprop ctl.stop media
setprop ctl.stop zygote
sleep 1
setprop ctl.stop bootanim
sleep 1
setprop ctl.stop media
setprop ctl.stop zygote
sleep 1
setprop ctl.stop bootanim

mount -o remount,rw /system
mount -t proc proc /system/debian/proc
mount -t sysfs sysfs /system/debian/sys
mount -t devpts devpts /system/debian/dev/pts
mount -o bind /data/debian/var/cache /system/debian/var/cache
mount -o bind /storage/emulated/legacy /system/debian/sdcard

localhost# cat stop.sh
setprop ctl.stop media
setprop ctl.stop zygote
setprop ctl.stop bootanim
setprop ctl.start bootanim
setprop ctl.start zygote
setprop ctl.start media

Now

dd if=/dev/urandom of=/dev/fb0

thankfully showed random pixels, so it should be possible to use the framebuffer for X.

First however, I needed to find a virtual terminal the X server could be "running on" (otherwise it would not start). Turns out the android-kernel did not give me tty1 or other consoles (whyever). I ended up "using" vt64, making the complete command line

/usr/lib/xorg/Xorg -sharevts -noreset -retro vt64

which does not really fidde with virtual terminals at all but tries to access to the framebuffer.

The X server would not start however, due to

(II) FBDEV(0): checking modes against framebuffer device...
(EE) FBDEV(0): FBIOPUT_VSCREENINFO: Invalid argument
(II) FBDEV(0):  mode "1024x600" test failed
(II) FBDEV(0):  mode "1024x600" not found
(II) FBDEV(0): checking modes against monitor...
(II) FBDEV(0): Virtual size is 1024x600 (pitch 1024)
(**) FBDEV(0):  Built-in mode "current": 36.9 MHz, 36.0 kHz, 60.0 Hz
(II) FBDEV(0): Modeline "current"x0.0   36.87  1024 1024 1024 1024  600 600 600 600 -hsync -vsync -csync (36.0 kHz b)
(==) FBDEV(0): DPI set to (96, 96)
(II) Loading /usr/lib/xorg/modules/libfb.so
(II) Module fb: vendor="X.Org Foundation"
        compiled for 1.20.10, module version = 1.0.0
(**) FBDEV(0): using shadow framebuffer
(II) Loading /usr/lib/xorg/modules/libshadow.so
(II) Module shadow: vendor="X.Org Foundation"
        compiled for 1.20.10, module version = 1.1.0
fbdev: PreInit done
fbdev: FBDevScreenInit 0
(EE) FBDEV(0): FBIOPUT_VSCREENINFO: Invalid argument
(EE) FBDEV(0): mode initialization failed
(EE) 
Fatal server error:
(EE) AddScreen/ScreenInit failed for driver 0
(EE) 
(EE) 

FBIOPUT_VSCREENINFO is defined in /usr/include/linux/fb.h and one of the framebuffer ioctls.

So I installed gdb onto the device and

(gdb) break ioctl

Some guessing (and a check of the armhf calling convention) gave $r2 as the register pointing to the fb_var_screeninfo struct, careful monitoring of $r1 showed a FBIOGET_SCREENINFO shortly before the failing call:

fb_var_screeninfo from GET:
0x2a1ce760:     1024    600     1024    1800
0x2a1ce770:     0       0       32      0
0x2a1ce780:     0       8       0       8
0x2a1ce790:     8       0       16      8
0x2a1ce7a0:     0       24      0       0
0x2a1ce7b0:     0       16      90      154
0x2a1ce7c0:     0       27126   0       0
0x2a1ce7d0:     0       0       0       0
0x2a1ce7e0:     0       0       0       0
0x2a1ce7f0:     0       0       0       0

fb_var_screeninfo from (failed) PUT:
0xbefff61c:     1024    600     1024    600
0xbefff62c:     0       0       32      0
0xbefff63c:     0       8       0       8
0xbefff64c:     8       0       16      8
0xbefff65c:     0       24      0       0
0xbefff66c:     0       2       90      154
0xbefff67c:     0       27125   0       0
0xbefff68c:     0       0       0       0
0xbefff69c:     0       0       0       0
0xbefff6ac:     0       0       0       0

So apparently, the driver requires the slightly nonsensical virtual y-resolution of 1800. Ok, fine.

(gdb) break ioctl
(gdb) condition 1 $r1 == 0x4601
(gdb) commands 1
> set *((uint32_t*)($r2 + 12)) = 1800
> cont
> end
(gdb) run

And TADA I have a running Xorg, and a black screen. :(

After having killed the X server, direct writing to /dev/fb0 also stopped producing pixels. So clearly, some configuration was put wrongly. Further (similar) debugging in gdb pointed at FBIOBLANK as the culprit.

(gdb) break ioctl
(gdb) condition 2 $r1 == 0x4611
(gdb) commands 1
> set $1 = 0x4601
> cont
> end

diverting the ioctl to a (known-failing) PUT call, finally gave me the Xorg retro stipple pattern. \o/

From here I quickly slapped up

localhost# cat /root/xorg-ioctls.c
// gcc -fPIC -shared -o xorg-ioctls.so xorg-ioctls.c -ldl

#define _GNU_SOURCE
#include <dlfcn.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stddef.h>
#include <linux/fb.h>

static int(*ioctl_orig)(int, unsigned long, ...) = NULL;

int ioctl(int fd, unsigned long request, void *a, void *b, void *c, void *d, void *e) {
        if(request == FBIOPUT_VSCREENINFO) {
                struct fb_var_screeninfo *info = a;
                info->yres_virtual = 1800;
        }
        if(request == FBIOBLANK) {
                return 0;
        }

        if(!ioctl_orig) {
                ioctl_orig = dlsym(RTLD_NEXT, "ioctl");
        }

        return ioctl_orig(fd, request, a, b, c, d, e);
}

and

localhost# cat /root/X.sh
#!/bin/sh

LD_PRELOAD=/root/xorg-ioctls.so /usr/lib/xorg/Xorg -sharevts -noreset -retro vt64

which resulted in a stable and non-gdbed Xorg server.

From there on everything was more or less working out of the box, except for the touchscreen being misunderstood as a relative device first. Finally with

localhost# cat /etc/X11/xorg.conf
Section "ServerLayout"
  Identifier "Layout0"
  Screen   "Screen0"
  InputDevice "Mouse0" "CorePointer"
  InputDevice "Keyboard0" "CoreKeyboard"
EndSection

Section "InputDevice"
  Identifier  "Keyboard0"
  Driver   "evdev"
  Option  "Device" "/dev/input/event0"
  Option  "Protocol" "usb"
EndSection

Section "InputDevice"
  Identifier "Mouse0"
  Driver  "evdev"
  Option  "Device" "/dev/input/event1"
  Option  "IgnoreRelativeAxes" "true"
  Option  "IgnoreAbsoluteAxes" "false"
  Option  "SwapAxes" "true"
  Option  "InvertY" "true"
  Option  "Mode" "Absolute"
EndSection

Section "Device"
  Identifier "Card0"
  Driver  "fbdev"
  Option  "fbdev" "/dev/fb0"
  Option  "debug" "true"
  VendorName "Unknown"
  BoardName "Unknown"
EndSection

Section "Screen"
  Identifier  "Screen0"
  Device  "Card0"
  Monitor "Monitor0"
  DefaultDepth    24
  SubSection      "Display"
    Depth               24
    Modes   "1024x600"
  EndSubSection
EndSection

Section "Monitor"
  Identifier "Monitor0"
  Mode "1024x600"
    DotClock 36.865
    HTimings 1024 1024 1024 1024
    VTimings 600 600 600 600
    Flags    "-HSync" "-VSync"
  EndMode
EndSection

Section "ServerFlags"
  Option "AutoAddDevices" "false"
EndSection

I got a running Xorg with a well-calibrated pointer device.

The installation of xtightvncviewer and integration into the multi-monitor setup ran as expected (and is not terribly interesting from an android-hacking standpoint).