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).