Skip to content

Instantly share code, notes, and snippets.

@ArcaneNibble
Created February 8, 2022 11:52
Show Gist options
  • Save ArcaneNibble/8642c8698ff2f855d485cf34a122d379 to your computer and use it in GitHub Desktop.
Save ArcaneNibble/8642c8698ff2f855d485cf34a122d379 to your computer and use it in GitHub Desktop.
Set up reMarkable for easy hacking

Set up reMarkable for easy UI hacking

The reMarkable UI is primarily built using QML, but all of the files are embedded inside the binary as compiled Qt resources. For ease of hacking, it is possible to extract the files onto the filesystem and patch the binary to load the extracted files.

--> ONLY FOR FIRMWARE VERSION 2.11.0.442 on rM2 <--

Resource extraction

  1. Copy /usr/bin/xochitl to your computer
  2. Acquire qrc2zip
  3. Run the following
qrc2zip -o qrc1.zip -v xochitl 3 3961032 3962440 3959884
qrc2zip -o qrc2.zip -v xochitl 3 4255140 4255316 4254928
qrc2zip -o qrc3.zip -v xochitl 3 4525748 4539940 4531052
qrc2zip -o qrc4.zip -v xochitl 3 4928644 4928932 4928236
qrc2zip -o qrc5.zip -v xochitl 3 5264976 5265460 5264316
  1. Unzip all of the generated files.

Build hotpatch binary

  1. Acquire reMarkable SDK
  2. Compile hax.c
$OE_QMAKE_CC -Wall -ggdb3 -std=c11 -fpic -shared -o hax.so hax.c
  1. Copy hax.so to reMarkable

Putting it together

  1. Copy qml/ directory extracted from .zip files to /home/root/hacked-qml
  2. Cause hax.so to be loaded. If you are using Toltec and its wrapper, edit the file /opt/etc/xochitl.env.d/rm2fb-preload.env and replace the line export LD_PRELOAD=/opt/lib/librm2fb_client.so.1 with export LD_PRELOAD="/opt/lib/librm2fb_client.so.1 /home/root/hax.so"
// $OE_QMAKE_CC -Wall -ggdb3 -std=c11 -fpic -shared -o hax.so hax.c
// ONLY FOR FIRMWARE VERSION 2.11.0.442 on rM2
#define _GNU_SOURCE
#include <stdint.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define MSG(e) write(0, e, sizeof(e) - 1)
static const char haxed_qml_path[] = "/home/root/hacked-qml";
static const struct {
uint32_t refcnt;
uint32_t size;
uint32_t alloc;
uint32_t offset;
uint16_t data[];
} haxed_main_path_qstring = {
.refcnt = 0xffffffff,
.size = sizeof("/home/root/hacked-qml/device/main.qml") - 1,
.alloc = 0,
.offset = 0x10,
.data = {'/', 'h', 'o', 'm', 'e', '/', 'r', 'o', 'o', 't', '/', 'h', 'a', 'c', 'k', 'e', 'd', '-', 'q', 'm', 'l', '/', 'd', 'e', 'v', 'i', 'c', 'e', '/', 'm', 'a', 'i', 'n', '.', 'q', 'm', 'l'},
};
__attribute__((constructor)) void hax() {
MSG("hax!\n");
// Ensure xochitl
char buf[100];
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
if (len == -1 || len != sizeof("/usr/bin/xochitl") - 1) {
MSG("not xochitl (len)\n");
return;
}
if (memcmp(buf, "/usr/bin/xochitl", sizeof("/usr/bin/xochitl") - 1)) {
MSG("not xochitl (memcmp)\n");
return;
}
MSG("In xochitl\n");
if (mprotect((void *)0x00010000, 0x540fe0, PROT_READ | PROT_WRITE | PROT_EXEC)) {
MSG("mprotect RWX failed\n");
return;
}
// Patches here
uintptr_t myaddr = (uintptr_t)&haxed_qml_path;
uintptr_t val_to_patch_to = myaddr - 0x0022e4bc;
uint32_t old_val = *(uint32_t *)(0x0022e880);
if (old_val != 0x002c97cc) {
MSG("old val mismatch (1)\n");
return;
}
*(uint32_t *)(0x0022e880) = val_to_patch_to;
*(uint8_t *)0x00022e4b0 = sizeof(haxed_qml_path) - 1;
myaddr = (uintptr_t)&haxed_main_path_qstring;
val_to_patch_to = myaddr - 0x0003c188;
old_val = *(uint32_t *)(0x0003c380);
if (old_val != 0x0039a860) {
MSG("old val mismatch (2)\n");
return;
}
*(uint32_t *)(0x0003c380) = val_to_patch_to;
if (mprotect((void *)0x00010000, 0x540fe0, PROT_READ | PROT_EXEC)) {
MSG("mprotect RX failed\n");
return;
}
MSG("All xochitl patches applied!\n");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment