/*! * \brief Find size of Linux kernel in boot image * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use mb_file_seek() to return to a known position. * * \param[in] bir MbBiReader to set error message * \param[in] file MbFile handle * \param[in] kernel_offset Offset of kernel in boot image * \param[out] kernel_size_out Pointer to store kernel size * * \return * * #MB_BI_OK if the kernel size is found * * #MB_BI_WARN if the kernel size cannot be found * * #MB_BI_FAILED if any file operation fails non-fatally * * #MB_BI_FATAL if any file operation fails fatally */ int find_linux_kernel_size(MbBiReader *bir, MbFile *file, uint32_t kernel_offset, uint32_t *kernel_size_out) { int ret; size_t n; uint32_t kernel_size; // If the boot image was patched with an early version of loki, the // original kernel size is not stored in the loki header properly (or in the // shellcode). The size is stored in the kernel image's header though, so // we'll use that. // http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html#d0e309 ret = mb_file_seek(file, kernel_offset + 0x2c, SEEK_SET, nullptr); if (ret != MB_FILE_OK) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to seek to kernel header: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } ret = mb_file_read_fully(file, &kernel_size, sizeof(kernel_size), &n); if (ret != MB_FILE_OK) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to read size from kernel header: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } else if (n != sizeof(kernel_size)) { mb_bi_reader_set_error(bir, mb_file_error(file), "Unexpected EOF when reading kernel header"); return MB_BI_WARN; } *kernel_size_out = mb_le32toh(kernel_size); return MB_BI_OK; }
/*! * \brief Find size of Linux kernel in boot image * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] kernel_offset Offset of kernel in boot image * \param[out] kernel_size_out Pointer to store kernel size * * \return * * Nothing if the kernel size is found * * FileError::UnexpectedEof if the kernel size cannot be found * * A specific error if any file operation fails */ oc::result<void> LokiFormatReader::find_linux_kernel_size(Reader &reader,File &file, uint32_t kernel_offset, uint32_t &kernel_size_out) { uint32_t kernel_size; // If the boot image was patched with an early version of loki, the // original kernel size is not stored in the loki header properly (or in the // shellcode). The size is stored in the kernel image's header though, so // we'll use that. // http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html#d0e309 auto seek_ret = file.seek(kernel_offset + 0x2c, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto ret = file_read_exact(file, &kernel_size, sizeof(kernel_size)); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } kernel_size_out = mb_le32toh(kernel_size); return oc::success(); }
/*! * \brief Find and read Loki ramdisk address * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use mb_file_seek() to return to a known position. * * \param[in] bir MbBiReader to set error message * \param[in] file MbFile handle * \param[in] hdr Android header * \param[in] loki_hdr Loki header * \param[out] ramdisk_addr_out Pointer to store ramdisk address * * \return * * #MB_BI_OK if the ramdisk address is found * * #MB_BI_WARN if the ramdisk address is not found * * #MB_BI_FAILED if any file operation fails non-fatally * * #MB_BI_FATAL if any file operation fails fatally */ int loki_find_ramdisk_address(MbBiReader *bir, MbFile *file, const AndroidHeader *hdr, const LokiHeader *loki_hdr, uint32_t *ramdisk_addr_out) { // If the boot image was patched with a newer version of loki, find the // ramdisk offset in the shell code uint32_t ramdisk_addr = 0; int ret; size_t n; if (loki_hdr->ramdisk_addr != 0) { uint64_t offset = 0; auto result_cb = [](MbFile *file, void *userdata, uint64_t offset) -> int { (void) file; uint64_t *offset_ptr = static_cast<uint64_t *>(userdata); *offset_ptr = offset; return MB_FILE_OK; }; ret = mb_file_search(file, -1, -1, 0, LOKI_SHELLCODE, LOKI_SHELLCODE_SIZE - 9, 1, result_cb, &offset); if (ret < 0) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to search for Loki shellcode: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } if (offset == 0) { mb_bi_reader_set_error(bir, MB_BI_ERROR_FILE_FORMAT, "Loki shellcode not found"); return MB_FILE_WARN; } offset += LOKI_SHELLCODE_SIZE - 5; ret = mb_file_seek(file, offset, SEEK_SET, nullptr); if (ret < 0) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to seek to ramdisk address offset: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } ret = mb_file_read_fully(file, &ramdisk_addr, sizeof(ramdisk_addr), &n); if (ret < 0) { mb_bi_reader_set_error(bir, mb_file_error(file), "Failed to read ramdisk address offset: %s", mb_file_error_string(file)); return ret == MB_FILE_FATAL ? MB_BI_FATAL : MB_BI_FAILED; } else if (n != sizeof(ramdisk_addr)) { mb_bi_reader_set_error(bir, MB_BI_ERROR_FILE_FORMAT, "Unexpected EOF when reading ramdisk address"); return MB_BI_WARN; } ramdisk_addr = mb_le32toh(ramdisk_addr); } else { // Otherwise, use the default for jflte (- 0x00008000 + 0x02000000) if (hdr->kernel_addr > UINT32_MAX - 0x01ff8000) { mb_bi_reader_set_error(bir, MB_BI_ERROR_FILE_FORMAT, "Invalid kernel address: %" PRIu32, hdr->kernel_addr); return MB_BI_WARN; } ramdisk_addr = hdr->kernel_addr + 0x01ff8000; } *ramdisk_addr_out = ramdisk_addr; return MB_BI_OK; }
/*! * \brief Patch Android boot image with Loki exploit in-place * * \param biw MbBiWriter instance for setting error message * \param file MbFile handle * \param aboot aboot image * \param aboot_size Size of aboot image * * \return * * #MB_BI_OK if the boot image is successfully patched * * #MB_BI_FAILED if a file operation fails non-fatally * * #MB_BI_FATAL if a file operation fails fatally */ int _loki_patch_file(MbBiWriter *biw, MbFile *file, const void *aboot, size_t aboot_size) { const unsigned char *aboot_ptr = reinterpret_cast<const unsigned char *>(aboot); unsigned char patch[] = LOKI_SHELLCODE; uint32_t target = 0; uint32_t aboot_base; int offset; int fake_size; size_t aboot_func_offset; uint64_t aboot_offset; LokiTarget *tgt = nullptr; AndroidHeader ahdr; LokiHeader lhdr; int ret; if (aboot_size < MIN_ABOOT_SIZE) { mb_bi_writer_set_error(biw, MB_BI_ERROR_INVALID_ARGUMENT, "aboot image size is too small"); return MB_BI_FAILED; } aboot_base = mb_le32toh(*reinterpret_cast<const uint32_t *>( aboot_ptr + 12)) - 0x28; // Find the signature checking function via pattern matching for (const unsigned char *ptr = aboot_ptr; ptr < aboot_ptr + aboot_size - ABOOT_SEARCH_LIMIT; ++ptr) { if (memcmp(ptr, PATTERN1, ABOOT_PATTERN_SIZE) == 0 || memcmp(ptr, PATTERN2, ABOOT_PATTERN_SIZE) == 0 || memcmp(ptr, PATTERN3, ABOOT_PATTERN_SIZE) == 0 || memcmp(ptr, PATTERN4, ABOOT_PATTERN_SIZE) == 0 || memcmp(ptr, PATTERN5, ABOOT_PATTERN_SIZE) == 0) { target = static_cast<uint32_t>(ptr - aboot_ptr + aboot_base); break; } } // Do a second pass for the second LG pattern. This is necessary because // apparently some LG models have both LG patterns, which throws off the // fingerprinting. if (target == 0) { for (const unsigned char *ptr = aboot_ptr; ptr < aboot_ptr + aboot_size - ABOOT_SEARCH_LIMIT; ++ptr) { if (memcmp(ptr, PATTERN6, ABOOT_PATTERN_SIZE) == 0) { target = static_cast<uint32_t>(ptr - aboot_ptr + aboot_base); break; } } } if (target == 0) { mb_bi_writer_set_error(biw, MB_BI_ERROR_FILE_FORMAT, "Failed to find aboot function to patch"); return MB_BI_FAILED; } for (size_t i = 0; i < (sizeof(targets) / sizeof(targets[0])); ++i) { if (targets[i].check_sigs == target) { tgt = &targets[i]; break; } } if (!tgt) { mb_bi_writer_set_error(biw, MB_BI_ERROR_FILE_FORMAT, "Unsupported aboot image"); return MB_BI_FAILED; } ret = _loki_read_android_header(biw, file, &ahdr); if (ret != MB_BI_OK) { return ret; } // Set up Loki header memset(&lhdr, 0, sizeof(lhdr)); memcpy(lhdr.magic, LOKI_MAGIC, LOKI_MAGIC_SIZE); lhdr.recovery = 0; strncpy(lhdr.build, tgt->build, sizeof(lhdr.build) - 1); // Store the original values in unused fields of the header lhdr.orig_kernel_size = ahdr.kernel_size; lhdr.orig_ramdisk_size = ahdr.ramdisk_size; lhdr.ramdisk_addr = ahdr.kernel_addr + ahdr.kernel_size + align_page_size<uint32_t>(ahdr.kernel_size, ahdr.page_size); if (!_patch_shellcode(tgt->hdr, ahdr.ramdisk_addr, patch)) { mb_bi_writer_set_error(biw, MB_BI_ERROR_FILE_FORMAT, "Failed to patch shellcode"); return MB_BI_FAILED; } // Ramdisk must be aligned to a page boundary ahdr.kernel_size = ahdr.kernel_size + align_page_size<uint32_t>(ahdr.kernel_size, ahdr.page_size) + ahdr.ramdisk_size; // Guarantee 16-byte alignment offset = tgt->check_sigs & 0xf; ahdr.ramdisk_addr = tgt->check_sigs - offset; if (tgt->lg) { fake_size = ahdr.page_size; ahdr.ramdisk_size = ahdr.page_size; } else { fake_size = 0x200; ahdr.ramdisk_size = 0; } aboot_func_offset = tgt->check_sigs - aboot_base - offset; // Write Android header ret = _loki_write_android_header(biw, file, &ahdr); if (ret != MB_BI_OK) { return ret; } // Write Loki header ret = _loki_write_loki_header(biw, file, &lhdr); if (ret != MB_BI_OK) { return ret; } aboot_offset = static_cast<uint64_t>(ahdr.page_size) + lhdr.orig_kernel_size + align_page_size<uint32_t>(lhdr.orig_kernel_size, ahdr.page_size) + lhdr.orig_ramdisk_size + align_page_size<uint32_t>(lhdr.orig_ramdisk_size, ahdr.page_size); // The function calls below are no longer recoverable should an error occur // Move DT image ret = _loki_move_dt_image(biw, file, aboot_offset, fake_size, ahdr.dt_size); if (ret != MB_BI_OK) { return MB_BI_FATAL; } // Write aboot ret = _loki_write_aboot(biw, file, aboot_ptr, aboot_size, aboot_offset, aboot_func_offset, fake_size); if (ret != MB_BI_OK) { return MB_BI_FATAL; } // Write shellcode ret = _loki_write_shellcode(biw, file, aboot_offset, offset, patch); if (ret != MB_BI_OK) { return MB_BI_FATAL; } return MB_BI_OK; }
/*! * \brief Find and read Loki ramdisk address * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] hdr Android header * \param[in] loki_hdr Loki header * \param[out] ramdisk_addr_out Pointer to store ramdisk address * * \return * * Nothing if the ramdisk address is found * * A LokiError if the ramdisk address is not found * * A specific error code if any file operation fails */ oc::result<void> LokiFormatReader::find_ramdisk_address(Reader &reader, File &file, const android::AndroidHeader &hdr, const LokiHeader &loki_hdr, uint32_t &ramdisk_addr_out) { // If the boot image was patched with a newer version of loki, find the // ramdisk offset in the shell code uint32_t ramdisk_addr = 0; if (loki_hdr.ramdisk_addr != 0) { uint64_t offset = 0; auto result_cb = [&](File &file_, uint64_t offset_) -> oc::result<FileSearchAction> { (void) file_; offset = offset_; return FileSearchAction::Continue; }; auto ret = file_search(file, {}, {}, 0, LOKI_SHELLCODE, LOKI_SHELLCODE_SIZE - 9, 1, result_cb); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } if (offset == 0) { return LokiError::ShellcodeNotFound; } offset += LOKI_SHELLCODE_SIZE - 5; auto seek_ret = file.seek(static_cast<int64_t>(offset), SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto read_ret = file_read_exact(file, &ramdisk_addr, sizeof(ramdisk_addr)); if (!read_ret) { if (file.is_fatal()) { reader.set_fatal(); } return read_ret.as_failure(); } ramdisk_addr = mb_le32toh(ramdisk_addr); } else { // Otherwise, use the default for jflte (- 0x00008000 + 0x02000000) if (hdr.kernel_addr > UINT32_MAX - 0x01ff8000) { //DEBUG("Invalid kernel address: %" PRIu32, hdr.kernel_addr); return LokiError::InvalidKernelAddress; } ramdisk_addr = hdr.kernel_addr + 0x01ff8000; } ramdisk_addr_out = ramdisk_addr; return oc::success(); }