diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 1330f13..401c3c8 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -1,101 +1,128 @@ -name: Build patcher for multi platforms. +name: Build patcher for multiple platforms on: push: - branches: [ "main" ] + branches: [main, feature/multi-boot] pull_request: - branches: [ "main" ] + branches: [main] workflow_dispatch: jobs: build: + name: ${{ matrix.name }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - # Windows x86_64 - - os: windows-latest - arch: x86_64 - c_compiler: cl - cpp_compiler: cl - - # Windows ARM64 - - os: windows-11-arm - arch: aarch64 - c_compiler: cl - cpp_compiler: cl - - # Linux x86_64 - - os: ubuntu-latest - arch: x86_64 + - name: Windows x64 + os: windows-latest + arch: x64 + cmake_arch: x64 + build_shims: 'OFF' + executable_suffix: .exe + executable_directory: Release + - name: Windows ARM64 + os: windows-11-arm + arch: arm64 + cmake_arch: ARM64 + build_shims: 'OFF' + executable_suffix: .exe + executable_directory: Release + - name: Linux x64 + os: ubuntu-latest + arch: x64 c_compiler: clang - cpp_compiler: clang - - # Linux ARM64 - - os: ubuntu-24.04-arm - arch: aarch64 + build_shims: 'ON' + executable_suffix: '' + executable_directory: . + - name: Linux ARM64 + os: ubuntu-24.04-arm + arch: arm64 c_compiler: clang - cpp_compiler: clang - - # macOS x86_64 - - os: macos-latest - arch: x86_64 + build_shims: 'OFF' + executable_suffix: '' + executable_directory: . + - name: macOS x64 + os: macos-15-intel + arch: x64 c_compiler: clang - cpp_compiler: clang + build_shims: 'OFF' + executable_suffix: '' + executable_directory: . steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 + with: + submodules: recursive - - name: Set reusable strings - id: strings - shell: bash + - name: Install Linux dependencies + if: runner.os == 'Linux' run: | - echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + sudo apt-get update + sudo apt-get install --yes build-essential cmake git - - name: Configure Compiler - if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' }} - run: | - sudo apt install gcc-aarch64-linux-gnu - echo -e \\nset\(CMAKE_C_FLAGS "--target=${{ matrix.arch }}-linux-gnu"\) \\n >> ${{ github.workspace }}/CMakeLists.txt + - name: Install ARM64 Shim toolchain + if: matrix.build_shims == 'ON' + run: sudo apt-get install --yes binutils-aarch64-linux-gnu - - name: Configure CMake - run: > - cmake -B ${{ steps.strings.outputs.build-output-dir }} - -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + - name: Configure CMake on Windows + if: runner.os == 'Windows' + run: >- + cmake -S "${{ github.workspace }}" + -B "${{ github.workspace }}/build" + -A ${{ matrix.cmake_arch }} + -DCMAKE_BUILD_TYPE=Release + -DBUILD_SHIMS=${{ matrix.build_shims }} + + - name: Configure CMake on Unix + if: runner.os != 'Windows' + run: >- + cmake -S "${{ github.workspace }}" + -B "${{ github.workspace }}/build" -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=Release - -S ${{ github.workspace }} + -DBUILD_SHIMS=${{ matrix.build_shims }} - - name: Build Patcher and other tools - if: ${{ matrix.os != 'ubuntu-latest' || matrix.os != 'ubuntu-24.04-arm' }} - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release --target DualBootKernelPatcher DualBootPatchRemover HDRTool + - name: Build host tools + run: >- + cmake --build "${{ github.workspace }}/build" + --config Release + --target MultiBootKernelPatcher UnpackTool - - name: Build ASM Codes - if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' }} - run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release + - name: Build Shims + if: matrix.build_shims == 'ON' + run: >- + cmake --build "${{ github.workspace }}/build" + --config Release + --target Shims - - name: Upload Tools - uses: actions/upload-artifact@v4 + - name: Stage host tools + shell: bash + run: | + mkdir -p artifacts/tools + cp "build/${{ matrix.executable_directory }}/MultiBootKernelPatcher${{ matrix.executable_suffix }}" artifacts/tools/ + cp "build/${{ matrix.executable_directory }}/UnpackTool${{ matrix.executable_suffix }}" artifacts/tools/ + + - name: Upload host tools + uses: actions/upload-artifact@v7 with: name: Tools-${{ matrix.os }}-${{ matrix.arch }} - path: | - ${{ steps.strings.outputs.build-output-dir }}/**/DualBootKernelPatcher* - ${{ steps.strings.outputs.build-output-dir }}/**/DualBootPatchRemover* - ${{ steps.strings.outputs.build-output-dir }}/**/HDRTool* + path: artifacts/tools + if-no-files-found: error - - name: Upload ASM Bins + - name: Upload simplified Shims + if: matrix.build_shims == 'ON' uses: actions/upload-artifact@v4 - if: ${{ matrix.os == 'ubuntu-latest' }} with: - name: Shellcodes - path: | - ${{ steps.strings.outputs.build-output-dir }}/ShellCode/* + name: Shims + path: build/Shims/Shim.*.bin + if-no-files-found: error - - name: Upload Configs + - name: Upload configs + if: matrix.build_shims == 'ON' uses: actions/upload-artifact@v4 - if: ${{ matrix.os == 'ubuntu-latest' }} with: name: Configs - path: | - Config/* + path: Config + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 3526100..a81404b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .idea -ShellCode.Epsilon.S -cmake-build-debug \ No newline at end of file +cmake-build-debug +build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2a27619 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/inih"] + path = lib/inih + url = https://github.com/benhoyt/inih.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 05a82f1..0f77808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,84 @@ cmake_minimum_required(VERSION 3.18) -project(DualBootKernelPatcher C) +project(MultiBootKernelPatcher C) + +option(BUILD_SHIMS "Build the ARM64 Shim binaries" ON) + +if (BUILD_SHIMS) + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + # MinGW compiler download link: + # https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads + set(ARM64_TOOLCHAIN_PREFIX + "E:/aarch64-gcc/aarch64-none-elf/bin/" + CACHE STRING "ARM64 binutils path and executable prefix") + elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + set(ARM64_TOOLCHAIN_PREFIX "aarch64-linux-gnu-" + CACHE STRING "ARM64 binutils path and executable prefix") + else () + message(FATAL_ERROR + "Building ARM64 Shim binaries on ${CMAKE_HOST_SYSTEM_NAME} is not supported") + endif () + + set(ARM64_AS "${ARM64_TOOLCHAIN_PREFIX}as" + CACHE FILEPATH "ARM64 assembler") + set(ARM64_OBJCOPY "${ARM64_TOOLCHAIN_PREFIX}objcopy" + CACHE FILEPATH "ARM64 objcopy") +endif () set(CMAKE_C_STANDARD 11) +if (MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif () + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") - set(CMAKE_EXE_LINKER_FLAGS "-static") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") endif () +if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/lib/inih/ini.c) + message(FATAL_ERROR + "The inih submodule is missing. Run: git submodule update --init --recursive") +endif () + +add_library(inih_static STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/lib/inih/ini.c +) +if (MSVC) + set_property(TARGET inih_static PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif () +target_include_directories(inih_static PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/lib/inih +) +set(INIH_TARGET inih_static) + # Compile the patcher. -add_executable(DualBootKernelPatcher patcher.c utils.c) -add_executable(DualBootPatchRemover remover.c utils.c) -add_executable(HDRTool HDRTool.c utils.c) +add_executable(MultiBootKernelPatcher + src/patcher.c + src/shim.c + src/shim_format.c + src/shim_config.c + src/utils.c +) +target_include_directories(MultiBootKernelPatcher PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/inc +) +if (MSVC) + set_property(TARGET MultiBootKernelPatcher PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif () +target_link_libraries(MultiBootKernelPatcher PRIVATE ${INIH_TARGET}) +add_executable(UnpackTool + src/unpack.c + src/shim_format.c + src/utils.c +) +target_include_directories(UnpackTool PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/inc +) +if (MSVC) + set_property(TARGET UnpackTool PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif () -# Compile Shell Codes with aarch64 gcc. -add_subdirectory(ShellCode) \ No newline at end of file +if (BUILD_SHIMS) + add_subdirectory(Shims) +endif () \ No newline at end of file diff --git a/Config/DualBoot.AYAPS.cfg b/Config/DualBoot.AYAPS.cfg new file mode 100644 index 0000000..f7dd219 --- /dev/null +++ b/Config/DualBoot.AYAPS.cfg @@ -0,0 +1,24 @@ +[Pack] +Shim=../build/Shims/Shim.AYAPS.bin +Output=../build/PatchedKernel +Default=Image +Timeout=0 + +[Image] +Name=Android/Linux +Path=/path/to/Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-UEFI] +Name=UEFI +Path=/path/to/SM8550_EFI.fd +Type=FreeExec +BaseImage=false +CopyTo=0xC7CC0000 +CopySizeMax=0x00400000 +EntryOffset=0 + +[Manifest] +Type=Manifest \ No newline at end of file diff --git a/Config/DualBoot.Hotdog.cfg b/Config/DualBoot.Hotdog.cfg new file mode 100644 index 0000000..9b201d0 --- /dev/null +++ b/Config/DualBoot.Hotdog.cfg @@ -0,0 +1,24 @@ +[Pack] +Shim=../build/Shims/Shim.Hotdog.bin +Output=../build/PatchedKernel +Default=Image +Timeout=0 + +[Image] +Name=Android/Linux +Path=/path/to/Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-UEFI] +Name=UEFI +Path=/path/to/SM8150_EFI.fd +Type=FreeExec +BaseImage=false +CopyTo=0x9FC00000 +CopySizeMax=0x00300000 +EntryOffset=0 + +[Manifest] +Type=Manifest \ No newline at end of file diff --git a/Config/DualBoot.KernelWarpper.cfg b/Config/DualBoot.KernelWarpper.cfg new file mode 100644 index 0000000..ca282a4 --- /dev/null +++ b/Config/DualBoot.KernelWarpper.cfg @@ -0,0 +1,24 @@ +[Pack] +Shim=../build/Shims/Shim.KernelWrapper.bin +Output=../build/PatchedKernel +Default=Image +Timeout=0 + +[Image] +Name=Linux +Path=/path/to/Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-DTB] +Name=Mainline Device Tree +Path=/path/to/mainline.dtb +Type=DTB +BaseImage=false +CopyTo=0x9FC00000 +CopySizeMax=0x00300000 +Align=8 + +[Manifest] +Type=Manifest \ No newline at end of file diff --git a/Config/DualBoot.Miatoll.cfg b/Config/DualBoot.Miatoll.cfg new file mode 100644 index 0000000..4d844ee --- /dev/null +++ b/Config/DualBoot.Miatoll.cfg @@ -0,0 +1,24 @@ +[Pack] +Shim=../build/Shims/Shim.Miatoll.bin +Output=../build/PatchedKernel +Default=Image +Timeout=0 + +[Image] +Name=Android/Linux +Path=/path/to/Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-UEFI] +Name=UEFI +Path=/path/to/SM7125_EFI.fd +Type=FreeExec +BaseImage=false +CopyTo=0x9FC00000 +CopySizeMax=0x00300000 +EntryOffset=0 + +[Manifest] +Type=Manifest \ No newline at end of file diff --git a/Config/DualBoot.Pipa.cfg b/Config/DualBoot.Pipa.cfg new file mode 100644 index 0000000..5a3fcbd --- /dev/null +++ b/Config/DualBoot.Pipa.cfg @@ -0,0 +1,24 @@ +[Pack] +Shim=../build/Shims/Shim.Pipa.bin +Output=../build/PatchedKernel +Default=Image +Timeout=0 + +[Image] +Name=Android/Linux +Path=/path/to/Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-UEFI] +Name=UEFI +Path=/path/to/SM8250_EFI.fd +Type=FreeExec +BaseImage=false +CopyTo=0x9FC00000 +CopySizeMax=0x00300000 +EntryOffset=0 + +[Manifest] +Type=Manifest diff --git a/Config/DualBoot.Sm7125.cfg b/Config/DualBoot.Sm7125.cfg deleted file mode 100644 index e5aaf19..0000000 --- a/Config/DualBoot.Sm7125.cfg +++ /dev/null @@ -1,2 +0,0 @@ -StackBase=0x9FC00000 -StackSize=0x00300000 \ No newline at end of file diff --git a/Config/DualBoot.Sm8150.cfg b/Config/DualBoot.Sm8150.cfg deleted file mode 100644 index e5aaf19..0000000 --- a/Config/DualBoot.Sm8150.cfg +++ /dev/null @@ -1,2 +0,0 @@ -StackBase=0x9FC00000 -StackSize=0x00300000 \ No newline at end of file diff --git a/Config/DualBoot.Sm8250.cfg b/Config/DualBoot.Sm8250.cfg deleted file mode 100644 index d8f37a9..0000000 --- a/Config/DualBoot.Sm8250.cfg +++ /dev/null @@ -1,2 +0,0 @@ -StackBase=0x9FC00000 -StackSize=0x00300000 diff --git a/Config/DualBoot.Sm8250DT.cfg b/Config/DualBoot.Sm8250DT.cfg deleted file mode 100644 index e5aaf19..0000000 --- a/Config/DualBoot.Sm8250DT.cfg +++ /dev/null @@ -1,2 +0,0 @@ -StackBase=0x9FC00000 -StackSize=0x00300000 \ No newline at end of file diff --git a/Config/DualBoot.Sm8550.cfg b/Config/DualBoot.Sm8550.cfg deleted file mode 100644 index eeb7b95..0000000 --- a/Config/DualBoot.Sm8550.cfg +++ /dev/null @@ -1,2 +0,0 @@ -StackBase=0xC7CC0000 -StackSize=0x00400000 \ No newline at end of file diff --git a/Config/Shim.Sample.cfg b/Config/Shim.Sample.cfg new file mode 100644 index 0000000..8b87f39 --- /dev/null +++ b/Config/Shim.Sample.cfg @@ -0,0 +1,36 @@ +[Pack] +# Paths are resolved relative to this configuration file. +Shim=/path/to/shim.bin +Output=../build/ShimImage + +# Default references an Image section. Timeout=0 waits indefinitely. +Default=Image-A +Timeout=5000 + +[Image-A] +Name=Android Linux +Path=/path/to/base-Image +Type=Linux +BaseImage=true +Align=0x200000 + +[Image-B] +Name=Recovery Linux +Path=/path/to/recovery-Image +Type=Linux +BaseImage=false +Align=0x200000 + +# Images with CopyTo are copied to that address and booted from it. +# [Image-UEFI] +# Name=UEFI +# Path=../../firmware/UEFI.fd +# Type=FreeExec +# BaseImage=false +# CopyTo=0x9FC00000 +# CopySizeMax=0x00300000 +# EntryOffset=0 +# Align defaults to 4 for UEFI-style executable payloads. + +[Manifest] +Type=Manifest \ No newline at end of file diff --git a/HDRTool.c b/HDRTool.c deleted file mode 100644 index 2ea4728..0000000 --- a/HDRTool.c +++ /dev/null @@ -1,80 +0,0 @@ -/** @file -* Create a header or remove a header of a -* kernel for android boot v1. -* -* Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. -* MIT License -* -*/ - -#include "utils.h" - -int main( - int argc, - char *argv[] -) { - // Print hello world message - printf("Project Aloha Kernel Image HDR Patcher v1.2.0.0\n"); - printf("Copyright (c) 2021-2025 The DuoWoA authors\n\n"); - - // Check parameters and print help message - if (argc != 3) { - printf("Usage: \n"); - return -EINVAL; - } - - // Check if file exist - // Init Input file content - FileContent kernelInput = {.filePath = argv[1]}; - if (!get_file_size(&kernelInput)) { - printf("Error: Input kernel file not found or invalid.\n"); - return -EINVAL; - } - kernelInput.fileBuffer = malloc(kernelInput.fileSize + 0x14); - read_file_content(&kernelInput); - // add a 0x14 offset of the buffer - memmove(kernelInput.fileBuffer + 0x14, kernelInput.fileBuffer, kernelInput.fileSize); - - // Init Output file content - FileContent kernelOutput = {.filePath = argv[2]}; - kernelOutput.fileSize = kernelInput.fileSize; - kernelOutput.fileBuffer = kernelInput.fileBuffer + 0x14; - - // OK now check if kernel has a header - // if it is, then remove it - // if it is not, then create a header - // Header format: - // 0x00-0x0F: "UNCOMPRESSED_IMG" (16 bytes) - // 0x10-0x17: Kernel size (8 bytes, little-endian) - if (strncmp((char *) kernelInput.fileBuffer, "UNCOMPRESSED_IMG", 0x10) == 0) { - // Kernel has a header, remove it - printf("Kernel has UNCOMPRESSED_IMG header, removing...\n"); - kernelOutput.fileBuffer += 0x14; // Move past the header - kernelOutput.fileSize -= 0x14; // Reduce size by header size - } else { - // Kernel does not have a header, create one - printf("Kernel does not have UNCOMPRESSED_IMG header, creating...\n"); - // Reallocate buffer to add header - kernelOutput.fileBuffer -= 0x14; - // Set header value - memcpy(kernelOutput.fileBuffer, "UNCOMPRESSED_IMG", 0x10); - kernelOutput.fileBuffer[0x10] = kernelOutput.fileSize >> 0 & 0xFF; - kernelOutput.fileBuffer[0x11] = kernelOutput.fileSize >> 8 & 0xFF; - kernelOutput.fileBuffer[0x12] = kernelOutput.fileSize >> 16 & 0xFF; - kernelOutput.fileBuffer[0x13] = kernelOutput.fileSize >> 24 & 0xFF; - kernelOutput.fileSize += 0x14; - } - - // Save the output kernel - if (write_file_content(&kernelOutput)) { - printf("Error: Failed to write output kernel file.\n"); - free(kernelInput.fileBuffer); - return -EINVAL; - } - - // Free allocated memory - free(kernelInput.fileBuffer); - - // Print success message - printf("Kernel image processed successfully.\n"); -} diff --git a/README.md b/README.md index 0d7cc71..075a8ea 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ -# Dual Boot Kernel Patcher -> Based On [SurfaceDuoDualBootKernelImagePatcher +# Multi-Boot Kernel Patcher +> Inspired by [SurfaceDuoDualBootKernelImagePatcher ](https://github.com/WOA-Project/SurfaceDuoDualBootKernelImagePatcher) +Multi-Boot Kernel Patcher packages a base ARM64 Linux Image, a compatible +Shim runtime, and multiple bootable images from one INI configuration. + ## Build - Preparation + A computer with Windows or Linux @@ -9,13 +12,16 @@ + aarch64 GNU Assembler + Git + CMake + - Clone this repo ``` git clone https://github.com/Project-Aloha/DualBootKernelPatcher + cd DualBootKernelPatcher + git submodule update --init --recursive ``` - Setup CMake. ``` - cd DualBootKernelPacther + cd MultiBootKernelPatcher cmake -B output -S . ``` - Build! @@ -23,19 +29,25 @@ cmake --build output -j 12 ``` ## Usage - - Common usage. - ``` - DualBootKernelPatcher - ``` - - Example - ``` - DualBootKernelPatcher kernel SM8150_EFI.fd PacthedKernel DualBoot.Sm8150.cfg ShellCode.Hotdog.bin - ``` - - Notice - + Shell Code binaries can be find under `output/ShellCode/` - + - Copy and edit configuration file, fill paths in it. + - Pack an image from the configuration file. + ```sh + MultiBootKernelPatcher Config/Shim.Sample.cfg + ``` + - Unpack a patched image that contains a reversible Manifest. + ```sh + UnpackTool PatchedKernel unpacked + ``` + The output directory contains BaseImage, Shim, image payloads, and + `config.cfg`. Edit those files as needed, then repack `config.cfg` to + create `RepackedKernel` in the same directory. + - Notice + + Device Shim blobs are generated under `output/Shims/`. + + Images without the current reversible Manifest are not supported by + `UnpackTool`. + ## See More - - You can go to our [document website](https://aloha.firmware.icu/) to get more infomation about the Dual Boot Patcher. + - You can go to our [document website](https://aloha.firmware.icu/) to get more infomation about the Multi-Boot Kernel Patcher. ## License MIT License. diff --git a/ShellCode/CMakeLists.txt b/ShellCode/CMakeLists.txt deleted file mode 100644 index 325a847..0000000 --- a/ShellCode/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -cmake_minimum_required(VERSION 3.27) -project(ShellCode ASM) - -# Set up compilers -if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") - # Windows - # MinGW compiler download link: - # https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads - set(CROSS_COMPILE "E:/aarch64-gcc/aarch64-none-elf/bin/") -elseif (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") - # Linux - set(CROSS_COMPILE "aarch64-linux-gnu-") -else () - message("build on ${CMAKE_HOST_SYSTEM_NAME} is not supported") -endif () - -set(CMAKE_AS ${CROSS_COMPILE}as) -set(CMAKE_OBJCOPY ${CROSS_COMPILE}objcopy) - -set(SHELLCODE_ELF_DIR "${CMAKE_CURRENT_BINARY_DIR}/elf") -file(MAKE_DIRECTORY ${SHELLCODE_ELF_DIR}) - -# Compile ShellCode one by one. -file(GLOB ASM_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ShellCode.*.S") -foreach (CURRENT_ASM_SOURCE_SUFFIX ${ASM_SOURCES}) - string(REGEX REPLACE "(ShellCode.*)\\.S" "\\1" CURRENT_ASM_SOURCE ${CURRENT_ASM_SOURCE_SUFFIX}) - - add_custom_target( - ${CURRENT_ASM_SOURCE}_ELF ALL - ${CMAKE_AS} - "${CMAKE_CURRENT_SOURCE_DIR}/${CURRENT_ASM_SOURCE}.S" - -I ${CMAKE_CURRENT_SOURCE_DIR} - -o "${SHELLCODE_ELF_DIR}/${CURRENT_ASM_SOURCE}.elf" - ) - - add_custom_target( - ${CURRENT_ASM_SOURCE}_BIN ALL - ${CMAKE_OBJCOPY} -O binary - "${SHELLCODE_ELF_DIR}/${CURRENT_ASM_SOURCE}.elf" - "${CURRENT_ASM_SOURCE}.bin" - DEPENDS ${CURRENT_ASM_SOURCE}_ELF - ) -endforeach () diff --git a/ShellCode/CommonTail.S b/ShellCode/CommonTail.S deleted file mode 100644 index 48120b7..0000000 --- a/ShellCode/CommonTail.S +++ /dev/null @@ -1,38 +0,0 @@ -// DO NOT EDIT THIS FILE -// -// Instructions handles common behaviour after compare. -// 1. Provide _Linux label, if w5 != 0 jmp to linux kernel. -// 2. Provide _UEFI label, if x5 == 0 jmp to UEFI. -// 3. Provide _CopyLoop function, to copy memory from end of kernel to UEFI Stack Region. -// 4. Dead Loop if something wrong. - -_Linux: - b _LinuxStart // if w5 != 0, continue booting linux kernel. - -_UEFI: - adr x4, _KernelHead // Store kernel head address in x4. - ldr x5, _KernelSize // Store kernel size in x5. - add x4, x4, x5 // Add kernel base + kernel size, store value in x4. - ldr x5, _StackBase // Store FD Base in x5. - ldr x6, _StackSize // Store FD Size in x6. - bl _CopyLoop // Copy UEFI FD to stack region. - adr x0, _ShellCodeEnd // Store address of ShellCode end to x0. - adr x1, _ShellCodeEnd // Store address of ShellCode end to x1. - ldr x5, _StackBase // Store stack base address in x5. - br x5 // Jump to UEFI. - -_CopyLoop: - ldp x2, x3, [x4], #0x10 // Save value at [x4](pointer) to x2 and x3, then x4 add 16. - stp x2, x3, [x5], #0x10 // Save value in x2 and x3 to [x5](pointer), then x5 add 16 - subs x6, x6, #0x10 // x6 - 16, if , set CPSR register to 0. - b.ne _CopyLoop // Check CPSR, if CPSR != 0, jump back to _CopyLoop. - ret // Return when finish. - -_Dead: - b _Dead // We should never get here. - -.text -.align 4 - -_ShellCodeEnd: -/* Do not remove the last line */ diff --git a/ShellCode/DummyHead.S b/ShellCode/DummyHead.S deleted file mode 100644 index 4beebe6..0000000 --- a/ShellCode/DummyHead.S +++ /dev/null @@ -1,35 +0,0 @@ -// DO NOT EDIT THIS FILE -// 16 instruction in total. -// -// Codes here will not be inject into kernel actually, so it won't be run. -// This file is used to make things more clearly. -// Size of this file must be 0x40 after compiling to binary. - -_KernelHead: - b 0x40 // Code 1, Goto ShellCode - -_LinuxStart: - b 0x0 // Code2, Goto Linux Kernel - .ascii "SHLL" // Image load offset, use it as shell code magic here - .ascii "COD\0" // Image load offset, use it as shell code magic here - .long 0 // Effective Image size L - .long 0 // Effective Image size H - .long 0 // Flags L - .long 0 // Flags H - -_StackBase: - .long 0 // reserved2, Stack Base LWORD - .long 0 // reserved2, Stack Base HWORD - -_StackSize: - .long 0 // reserved3, Stack Size L - .long 0 // reserved3, Stack Size H - -_KernelSize: - .long 0 // reserved4, Kernel Size L - .long 0 // reserved4, Kernel Size H - - .ascii "ARM\x64" // magic_64 - .long 0 // reserve5 - -/* Do not remove the last line */ diff --git a/ShellCode/ShellCode.KernelWrapper.S b/ShellCode/ShellCode.KernelWrapper.S deleted file mode 100644 index 61e78ad..0000000 --- a/ShellCode/ShellCode.KernelWrapper.S +++ /dev/null @@ -1,73 +0,0 @@ -/** -* A Wrapper for Linux Kernel to by pass android dtb check -* -* Qualcomm application bootloader (ABL) will check msm-id/board-id -* or apply overlay before booting kernel. By replacing android dtb with linux dtb, -* abl will failed to check or apply dtbo and then refuse to boot. -* This wrapper is used to bypass the check. -* -* Scheme: -* Only replace kernel instead of replacing dtb. But inject the dtb in kernel -* -* Step: -* Inject this wrapper into kernel header and make a payload with linux kernel + linux dtb. -* Then, repack the android boot image with the payload, but not replace dtb. -* Linux will boot successfully then. -* -* Note: -* You need to add memory region for linux dtb in device tree manually -* otherwise linux will NOT boot. check the sample below, you can find -* it in android fdt at /sys/firmware/fdt. -* memory { -* ddr_device_type = <0x08>; -* device_type = "memory"; -* #address-cells = <2>; -* #size-cells = <2>; -* reg = <0x0 0x80000000 0x0 0x3b800000>, -* <0x1 0x80000000 0x1 0x00000000>, -* <0x0 0xc0000000 0x0 0xc0000000>; -* }; -* -* Usage: -* ./DualBootKernelPatcher OriginalKernel mainline_dtb output DualBoot.Sm8250DT.cfg ShellCode.KernelWrapper.bin -* Then repack android image with the output file. -* -* Inspired by @bigfootACA -* -*/ - -/* Dummy Header for shellcode */ -.include "DummyHead.S" - -_ShellCodeStart: - // Calculate UEFI FD(dtb addr here) start address and store in X4 - adr x4, _KernelHead // Store kernel head address in x4. - ldr x5, _KernelSize // Store kernel size in x5. - add x4, x4, x5 // Add kernel base + kernel size, store value in x4. - - // Copy dtb to safe place (StackRegion, you can configure it freely in DualBoot config file) - ldr x5, _StackBase // Store FD Base in x5. - ldr x6, _StackSize // Store FD Size in x6. - bl _CopyLoop // Copy DTB to stack region. - - // Set X0 to StackBase, which is the new DTB address - ldr x0, _StackBase // Store stack base address in x5. - b _LinuxStart // Boot linux kernel. - b _Dead // We should never get here. - -// Copy Sub program, X4 is src, X5 is dst, X6 is size -_CopyLoop: - ldp x7, x8, [x4], #0x10 // Save value at [x4](pointer) to x7 and x8, then x4 add 16. - stp x7, x8, [x5], #0x10 // Save value in x7 and x8 to [x5](pointer), then x5 add 16 - subs x6, x6, #0x10 // x6 - 16, if , set CPSR register to 0. - b.ne _CopyLoop // Check CPSR, if CPSR != 0, jump back to _CopyLoop. - ret // Return when finish. - -_Dead: - b _Dead // We should never get here. - -.text -.align 4 - -_ShellCodeEnd: -/* Do not remove the last line */ diff --git a/Shims/CMakeLists.txt b/Shims/CMakeLists.txt new file mode 100644 index 0000000..bdc41cf --- /dev/null +++ b/Shims/CMakeLists.txt @@ -0,0 +1,34 @@ +set(SHIM_ELF_DIR "${CMAKE_CURRENT_BINARY_DIR}/elf") +file(MAKE_DIRECTORY ${SHIM_ELF_DIR}) + +# Compile each Shim blob. +file(GLOB ASM_SOURCES CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "Shim.*.S") +set(SHIM_BIN_TARGETS) +foreach (CURRENT_ASM_SOURCE_SUFFIX ${ASM_SOURCES}) + string(REGEX REPLACE "\\.S$" "" CURRENT_ASM_SOURCE + "${CURRENT_ASM_SOURCE_SUFFIX}") + + add_custom_target( + ${CURRENT_ASM_SOURCE}_ELF ALL + ${ARM64_AS} + "${CMAKE_CURRENT_SOURCE_DIR}/${CURRENT_ASM_SOURCE}.S" + -I ${CMAKE_CURRENT_SOURCE_DIR} + -o "${SHIM_ELF_DIR}/${CURRENT_ASM_SOURCE}.elf" + DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${CURRENT_ASM_SOURCE}.S" + "${CMAKE_CURRENT_SOURCE_DIR}/DummyHead.S" + "${CMAKE_CURRENT_SOURCE_DIR}/CommonTail.S" + ) + + add_custom_target( + ${CURRENT_ASM_SOURCE}_BIN ALL + ${ARM64_OBJCOPY} -O binary + "${SHIM_ELF_DIR}/${CURRENT_ASM_SOURCE}.elf" + "${CURRENT_ASM_SOURCE}.bin" + DEPENDS ${CURRENT_ASM_SOURCE}_ELF + ) + list(APPEND SHIM_BIN_TARGETS ${CURRENT_ASM_SOURCE}_BIN) +endforeach () + +add_custom_target(Shims DEPENDS ${SHIM_BIN_TARGETS}) diff --git a/Shims/CommonTail.S b/Shims/CommonTail.S new file mode 100644 index 0000000..57eade3 --- /dev/null +++ b/Shims/CommonTail.S @@ -0,0 +1,125 @@ +// Shared implementation for two-entry assembly Shims. The entry BL leaves +// x30 at BaseImage + 4; x23 preserves it while the Shim selects an image. + +_Linux: + mov x0, x19 + mov x1, x20 + mov x2, x21 + mov x3, x22 + mov x30, x23 + ret + +_UEFI: + sub x24, x23, #4 + ldr x25, [x24, #0x20] + cbz x25, _Linux + ldr x8, [x24, #0x28] + cmp x8, #448 + b.lo _Linux + add x25, x24, x25 + + ldr x8, [x25] + ldr x9, _ManifestMagic + cmp x8, x9 + b.ne _Linux + ldr w8, [x25, #8] + cmp w8, #5 + b.ne _Linux + ldr w8, [x25, #12] + cmp w8, #64 + b.ne _Linux + ldr w8, [x25, #16] + cmp w8, #80 + b.ne _Linux + ldr w8, [x25, #20] + cmp w8, #2 + b.lo _Linux + + add x25, x25, #64 + add x25, x25, #80 + ldr w8, [x25, #48] + cbz w8, _Linux + cmp w8, #3 + b.hi _Linux + ldr x10, [x25, #40] + cmp x10, #4 + b.lo _Linux + ldr x11, [x25, #64] + sub x12, x10, #4 + cmp x11, x12 + b.hi _Linux + + ldr x8, [x25, #32] + add x8, x24, x8 + ldr w9, [x25, #52] + tbz w9, #1, _ExecuteEntry + ldr x9, [x25, #56] + cbz x9, _Linux + mov x24, x9 + mov x12, x10 + bl _CopyLoop + mov x0, x24 + mov x1, x12 + bl _SyncImageCache + mov x8, x24 + +_ExecuteEntry: + add x24, x8, x11 + adr x0, _ShimImageEnd + adr x1, _ShimImageEnd + mov x2, x21 + mov x3, x22 + br x24 + +_CopyLoop: + cbz x10, 2f +1: + ldrb w13, [x8], #1 + strb w13, [x9], #1 + subs x10, x10, #1 + b.ne 1b +2: + ret + +// x0 = destination, x1 = size +_SyncImageCache: + mrs x2, ctr_el0 + ubfx x3, x2, #16, #4 + mov x5, #4 + lsl x3, x5, x3 + sub x6, x3, #1 + bic x5, x0, x6 + add x6, x0, x1 +3: + dc cvau, x5 + add x5, x5, x3 + cmp x5, x6 + b.lo 3b + dsb ish + + and x3, x2, #0xf + mov x5, #4 + lsl x3, x5, x3 + sub x7, x3, #1 + bic x5, x0, x7 +4: + ic ivau, x5 + add x5, x5, x3 + cmp x5, x6 + b.lo 4b + dsb ish + isb + ret + +_Dead: + b _Dead + +.align 3 +_ManifestMagic: + .ascii "SHIMMF\0\0" + +.text +.align 4 + +_ShimImageEnd: +/* Do not remove the last line */ diff --git a/Shims/DummyHead.S b/Shims/DummyHead.S new file mode 100644 index 0000000..7e71e7a --- /dev/null +++ b/Shims/DummyHead.S @@ -0,0 +1,4 @@ +.text +.align 2 + +/* Do not remove the last line */ diff --git a/ShellCode/ShellCode.AYAPS.S b/Shims/Shim.AYAPS.S similarity index 78% rename from ShellCode/ShellCode.AYAPS.S rename to Shims/Shim.AYAPS.S index 8bce62d..530d2ae 100644 --- a/ShellCode/ShellCode.AYAPS.S +++ b/Shims/Shim.AYAPS.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Ayaneo Pocket S */ -_ShellCodeStart: +/* Simplified Shim for Ayaneo Pocket S */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0xd000 movk x4, #0x0f16, lsl #16 // Store 0xf16d000 to x4, which is io control of gpio109 on sm8550. str wzr, [x4], #4 // Set 0 in control reg, then x4 add 4 diff --git a/ShellCode/ShellCode.Andromeda.S b/Shims/Shim.Andromeda.S similarity index 85% rename from ShellCode/ShellCode.Andromeda.S rename to Shims/Shim.Andromeda.S index 0ec2a86..1431386 100644 --- a/ShellCode/ShellCode.Andromeda.S +++ b/Shims/Shim.Andromeda.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Xiaomi Mix3 5G */ -_ShellCodeStart: +/* Simplified Shim for Xiaomi Mix3 5G */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0x9000 movk x4, #0x392, lsl #16 // Store 0x3929000 to x4, which is io control of gpio 41. ldr w5, [x4] diff --git a/ShellCode/ShellCode.Cepheus.S b/Shims/Shim.Cepheus.S similarity index 85% rename from ShellCode/ShellCode.Cepheus.S rename to Shims/Shim.Cepheus.S index ef966ad..2bd8df5 100644 --- a/ShellCode/ShellCode.Cepheus.S +++ b/Shims/Shim.Cepheus.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Xiaomi 9 */ -_ShellCodeStart: +/* Simplified Shim for Xiaomi 9 */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0x1000 movk x4, #0x396, lsl #16 // Store 0x3961004 to x4, which is io status reg of gpio 97. ldr w5, [x4] diff --git a/ShellCode/ShellCode.Hotdog.S b/Shims/Shim.Hotdog.S similarity index 78% rename from ShellCode/ShellCode.Hotdog.S rename to Shims/Shim.Hotdog.S index df880d1..e466e81 100644 --- a/ShellCode/ShellCode.Hotdog.S +++ b/Shims/Shim.Hotdog.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Oneplus 7T Pro */ -_ShellCodeStart: +/* Simplified Shim for Oneplus 7T Pro */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0xd000 movk x4, #0x317, lsl #16 // Store 0x317d004 to x4, which is control reg of gpio 125. str wzr, [x4], #4 // Set 0 in control reg, then x4 add 4 diff --git a/Shims/Shim.KernelWrapper.S b/Shims/Shim.KernelWrapper.S new file mode 100644 index 0000000..006378e --- /dev/null +++ b/Shims/Shim.KernelWrapper.S @@ -0,0 +1,107 @@ +/* Assembly Shim for replacing the Linux DTB before entering BaseImage. */ +.include "DummyHead.S" + +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 + sub x24, x23, #4 + + ldr x25, [x24, #0x20] + cbz x25, _Linux + ldr x8, [x24, #0x28] + cmp x8, #448 + b.lo _Linux + add x25, x24, x25 + ldr x8, [x25] + ldr x9, _ManifestMagic + cmp x8, x9 + b.ne _Linux + ldr w8, [x25, #8] + cmp w8, #5 + b.ne _Linux + ldr w8, [x25, #12] + cmp w8, #64 + b.ne _Linux + ldr w8, [x25, #16] + cmp w8, #80 + b.ne _Linux + ldr w8, [x25, #20] + cmp w8, #2 + b.lo _Linux + + add x25, x25, #64 + add x25, x25, #80 + ldr w8, [x25, #48] + cmp w8, #5 + b.ne _Linux + ldr w8, [x25, #52] + tbz w8, #1, _Linux + + ldr x8, [x25, #32] + add x8, x24, x8 + ldr x9, [x25, #56] + cbz x9, _Linux + ldr x10, [x25, #40] + cbz x10, _Linux + mov x11, x9 + mov x12, x10 + bl _CopyLoop + + mov x0, x11 + mov x1, x12 + bl _SyncDataCache + + mov x0, x11 + mov x1, x20 + mov x2, x21 + mov x3, x22 + mov x30, x23 + ret + +_Linux: + mov x0, x19 + mov x1, x20 + mov x2, x21 + mov x3, x22 + mov x30, x23 + ret + +_CopyLoop: + cbz x10, 2f +1: + ldrb w13, [x8], #1 + strb w13, [x9], #1 + subs x10, x10, #1 + b.ne 1b +2: + ret + +// x0 = destination, x1 = size +_SyncDataCache: + mrs x2, ctr_el0 + ubfx x3, x2, #16, #4 + mov x4, #4 + lsl x3, x4, x3 + sub x5, x3, #1 + bic x4, x0, x5 + add x5, x0, x1 +3: + dc cvac, x4 + add x4, x4, x3 + cmp x4, x5 + b.lo 3b + dsb sy + ret + +.align 3 +_ManifestMagic: + .ascii "SHIMMF\0\0" + +.text +.align 4 + +_ShimImageEnd: +/* Do not remove the last line */ diff --git a/ShellCode/ShellCode.Miatoll.S b/Shims/Shim.Miatoll.S similarity index 88% rename from ShellCode/ShellCode.Miatoll.S rename to Shims/Shim.Miatoll.S index a72c640..501341d 100644 --- a/ShellCode/ShellCode.Miatoll.S +++ b/Shims/Shim.Miatoll.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Redmi Note 9S */ -_ShellCodeStart: +/* Simplified Shim for Redmi Note 9S */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0x6000 movk x4, #0xE60, lsl #16 // Store 0xE606000 to x4, which is address of 0xC0 spmi channel movz x5, #0x0081 diff --git a/ShellCode/ShellCode.Nabu.S b/Shims/Shim.Nabu.S similarity index 83% rename from ShellCode/ShellCode.Nabu.S rename to Shims/Shim.Nabu.S index 34032b7..d286958 100644 --- a/ShellCode/ShellCode.Nabu.S +++ b/Shims/Shim.Nabu.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Xiaomi Pad 5 Pro */ -_ShellCodeStart: +/* Simplified Shim for Xiaomi Pad 5 Pro */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0x9004 movk x4, #0x390, lsl #16 // Store 0x3909004 to x4, which is io status reg of gpio 9. movz x5, #0x3004 diff --git a/ShellCode/ShellCode.Pipa.S b/Shims/Shim.Pipa.S similarity index 76% rename from ShellCode/ShellCode.Pipa.S rename to Shims/Shim.Pipa.S index 9960131..630ea58 100644 --- a/ShellCode/ShellCode.Pipa.S +++ b/Shims/Shim.Pipa.S @@ -1,7 +1,12 @@ .include "DummyHead.S" -/* Shell Code for Xiaomi Pad 6 (Pipa) */ -_ShellCodeStart: +/* Simplified Shim for Xiaomi Pad 6 (Pipa) */ +_ShimStart: + mov x19, x0 + mov x20, x1 + mov x21, x2 + mov x22, x3 + mov x23, x30 movz x4, #0xe004 movk x4, #0xf96, lsl #16 // Store 0xf96E004 to x4, which is address of GPIO 110. ldr w6, [x4] // Get status value from address stored in x4 and store it to w6. diff --git a/inc/shim.h b/inc/shim.h new file mode 100644 index 0000000..6ac0e92 --- /dev/null +++ b/inc/shim.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include + +#define SHIM_MANIFEST_MAGIC "SHIMMF\0" +#define SHIM_MANIFEST_VERSION 5U +#define SHIM_MANIFEST_MAX_SIZE 0x2000U +#define SHIM_MAX_ENTRIES 50U +#define SHIM_MANIFEST_RECOVERY_MAGIC "SHIMREC\0" +#define SHIM_MANIFEST_RECOVERY_VERSION 1U +#define SHIM_LINUX_HEADER_SIZE 0x40U +#define SHIM_WRAPPER_SIZE 0x14U +#define SHIM_LINUX_ALIGNMENT 0x200000U +#define SHIM_BRANCH_ALIGNMENT 4U +#define SHIM_BRANCH_MIN_OFFSET (-0x08000000LL) +#define SHIM_BRANCH_MAX_OFFSET 0x08000000LL + +#define SHIM_ENTRY_TYPE_LINUX 1U +#define SHIM_ENTRY_TYPE_FREE_EXEC 2U +#define SHIM_ENTRY_TYPE_SHIM 3U +#define SHIM_ENTRY_TYPE_BLOB 4U +#define SHIM_ENTRY_TYPE_DTB 5U +#define SHIM_ENTRY_TYPE_MANIFEST 6U +#define SHIM_ENTRY_FLAG_BASE_IMAGE 1U +#define SHIM_ENTRY_FLAG_COPY 2U + +#define SHIM_PATH_SIZE 4096U + +typedef struct { + char section[32]; + char name[32]; + char path[SHIM_PATH_SIZE]; + uint32_t type; + int hasType; + int baseImage; + uint64_t copyAddress; + int hasCopyAddress; + uint64_t copySizeMax; + int hasCopySizeMax; + uint64_t alignment; + int hasAlignment; + uint64_t entryOffset; + int hasEntryOffset; +} ShimImageConfig; + +typedef struct { + int present; + uint32_t type; + int hasType; +} ShimManifestConfig; + +typedef struct { + char shim[SHIM_PATH_SIZE]; + char output[SHIM_PATH_SIZE]; + char defaultSection[32]; + ShimImageConfig images[SHIM_MAX_ENTRIES]; + size_t imageCount; + size_t baseImageIndex; + uint32_t defaultEntry; + uint32_t timeoutMs; + ShimManifestConfig manifest; +} ShimPackConfig; + +#ifdef _MSC_VER +#pragma pack(push, 1) +#define SHIM_PACKED +#else +#define SHIM_PACKED __attribute__((packed)) +#endif + +typedef struct SHIM_PACKED { + char magic[8]; + uint32_t version; + uint32_t headerSize; + uint32_t entrySize; + uint32_t entryCount; + uint32_t defaultEntry; + uint32_t timeoutMs; + uint64_t imageSize; + uint64_t reserved[3]; +} ShimManifestHeader; + +typedef struct SHIM_PACKED { + char name[32]; + uint64_t offset; + uint64_t size; + uint32_t type; + uint32_t flags; + uint64_t loadAddress; + uint64_t entryOffset; + uint64_t reserved; +} ShimManifestEntry; + +typedef struct SHIM_PACKED { + char magic[8]; + uint32_t version; + uint32_t headerSize; + uint32_t entrySize; + uint32_t entryCount; + uint64_t shimSize; + uint8_t baseHeader[SHIM_LINUX_HEADER_SIZE]; + uint8_t reserved[32]; +} ShimManifestRecoveryHeader; + +typedef struct SHIM_PACKED { + uint64_t alignment; + uint64_t copySizeMax; + uint32_t sourcePrefixSize; + uint32_t reserved; + uint8_t sourcePrefix[SHIM_WRAPPER_SIZE]; + uint8_t padding[4]; +} ShimManifestRecoveryEntry; + +typedef struct SHIM_PACKED { + ShimManifestHeader header; + ShimManifestEntry entries[SHIM_MAX_ENTRIES]; +} ShimManifest; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif +#undef SHIM_PACKED + +_Static_assert(sizeof(ShimManifestHeader) == 64, + "Invalid Shim manifest header size"); +_Static_assert(sizeof(ShimManifestEntry) == 80, + "Invalid Shim manifest entry size"); +_Static_assert(sizeof(ShimManifest) <= SHIM_MANIFEST_MAX_SIZE, + "Shim manifest exceeds its maximum size"); +_Static_assert(sizeof(ShimManifestRecoveryHeader) == 128, + "Invalid Shim manifest recovery header size"); +_Static_assert(sizeof(ShimManifestRecoveryEntry) == 48, + "Invalid Shim manifest recovery entry size"); + +int PackConfig(const char *configPath); +int PackShim(const ShimPackConfig *config); \ No newline at end of file diff --git a/inc/shim_format.h b/inc/shim_format.h new file mode 100644 index 0000000..324ff8b --- /dev/null +++ b/inc/shim_format.h @@ -0,0 +1,22 @@ +#pragma once + +#include "shim.h" +#include "utils.h" + +#include + +typedef struct { + size_t headerOffset; + size_t payloadSize; + uint64_t textOffset; + uint64_t imageSize; +} Arm64ImageInfo; + +uint32_t shim_read_le32(const uint8_t *buffer); +uint64_t shim_read_le64(const uint8_t *buffer); +void shim_write_le32(uint8_t *buffer, uint32_t value); +void shim_write_le64(uint8_t *buffer, uint64_t value); +bool shim_align_up(size_t value, size_t alignment, size_t *result); +bool shim_inspect_arm64_image(const FileContent *file, Arm64ImageInfo *info); +bool shim_is_executable_type(uint32_t type); +const char *shim_type_name(uint32_t type); diff --git a/inc/utils.h b/inc/utils.h new file mode 100644 index 0000000..820988c --- /dev/null +++ b/inc/utils.h @@ -0,0 +1,30 @@ +/** @file + * Multi-Boot Kernel Patcher Header File. + * + * Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. + * MIT License + * + */ + +#pragma once + +#include +#include +#include +#include +#include + +// +// Store some file information and file buffer. +// +typedef struct { + uint8_t *fileBuffer; + size_t fileSize; + const char *filePath; +} FileContent, *pFileContent; + +size_t get_file_size(FileContent *fileContent); + +uint8_t *read_file_content(FileContent *fileContent); + +int write_file_content(pFileContent fileContent); diff --git a/lib/inih b/lib/inih new file mode 160000 index 0000000..26254ee --- /dev/null +++ b/lib/inih @@ -0,0 +1 @@ +Subproject commit 26254ee9de7681f8825433415443e7116ff24b98 diff --git a/patcher.c b/patcher.c deleted file mode 100644 index d8fa704..0000000 --- a/patcher.c +++ /dev/null @@ -1,297 +0,0 @@ -/** @file - * DualBoot Kernel Patcher Source File. - * - * This Program will help you inject shell code into header of linux kernel. - * - * It only supports several formats of kernel: - * 1. Image file compile from source - * 2. Qualcomm patched kernel. - * 3. Image file kernel with efi stub. - * - * Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. - * MIT License - * - */ - -#include "utils.h" - -/** - * The main function will check and read given files, - * pass it to patch function, and write patched kernel to file. - * - * @param argc argc is numbers of argues given in cmdline. - * @param argv argv is a array that contains all given values. - * - * @retval -EINVAL Given files are not found or format not match. - * - */ -int main(int argc, char *argv[]) { - // Print hello message. - printf("Project Aloha DualBoot Kernel Image Patcher v1.2.0.0\n"); - printf("Copyright (c) 2021-2025 The DuoWoA authors\n\n"); - if (argc != 6) { - // Print usage if arg numbers not meet. - printf("Usage: " - " \n"); - return -EINVAL; - } - - // Get file paths. - FileContent originImage = {.filePath = argv[1]}; - FileContent uefiImage = {.filePath = argv[2]}; - FileContent outputImage = {.filePath = argv[3]}; - FileContent config = {.filePath = argv[4]}; - FileContent shellCode = {.filePath = argv[5]}; - - // Print processing msg. - printf("Patching %s with %s and saving to %s...\n\n", originImage.filePath, - uefiImage.filePath, - outputImage.filePath); - - // Read buffer from old kernel. - if (!get_file_size(&originImage)) - return -EINVAL; - originImage.fileBuffer = malloc(originImage.fileSize); - read_file_content(&originImage); - - // Read buffer from uefi image. - if (!get_file_size(&uefiImage)) - return -EINVAL; - uefiImage.fileBuffer = malloc(uefiImage.fileSize); - read_file_content(&uefiImage); - - // Parse config file. - Config stack = {0}; - if (parse_config(&config, &stack)) { - printf("Error: Parse config failed\n"); - return -EINVAL; - } - - // Get ShellCode buffer. - if (!get_file_size(&shellCode)) - return -EINVAL; - shellCode.fileBuffer = malloc(shellCode.fileSize); - read_file_content(&shellCode); - - // Patch Kernel. - void *buf = PatchKernel(&originImage, &uefiImage, &shellCode, &outputImage, &stack); - - // Output buffer to new kernel. - if (outputImage.fileBuffer != NULL && buf != NULL) { - write_file_content(&outputImage); - } else { - printf("Error Patching Kernel.\n"); - return -EINVAL; - } - - // Free buffers we allocated. - free(shellCode.fileBuffer); - free(originImage.fileBuffer); - free(uefiImage.fileBuffer); - free(outputImage.fileBuffer); - - // Print end message. - printf("Image successfully patched.\n"); - printf("Please check the patched kernel image at %s.\n", outputImage.filePath); - return 0; -} - - -/** - * Patch kernel based on given fileContents, - * - * @param[in] kernel origin kernel fileContent - * @param[in] uefi uefi fd fileContent - * @param[in] shellCode shell code binary - * @param[in,out] patchedKernel patched kernel fileContent - * @param[in] config config info read from config - * - * @return patched kernel buffer - * - */ -uint8_t *PatchKernel(pFileContent kernel, pFileContent uefi, pFileContent shellCode, - pFileContent patchedKernel, pConfig config) { - // Allocate output buffer - patchedKernel->fileSize = kernel->fileSize + uefi->fileSize; - patchedKernel->fileBuffer = malloc(patchedKernel->fileSize); - - // Copy two buffers into patchedBuffer. - memcpy(patchedKernel->fileBuffer, kernel->fileBuffer, kernel->fileSize); - memcpy(patchedKernel->fileBuffer + kernel->fileSize, uefi->fileBuffer, uefi->fileSize); - - // Check ShellCode binary magic. - if (shellCode->fileSize > 0x40 && strcmp((char *) (shellCode->fileBuffer + 8), "SHLLCOD") != 0) { - printf("Error: shell code binary format not recognize.\n"); - return NULL; - } - - // Check if kernel has UNCOMPRESSED_IMG Header. - char kernelHeader[0x11] = {0}; - uint8_t hasHeader = 0; - memcpy(kernelHeader, kernel->fileBuffer, 0x10); - if (strcmp(kernelHeader, "UNCOMPRESSED_IMG") == 0) { - printf("Kernel has UNCOMPRESSED_IMG header.\n"); - kernel->fileSize -= 0x14; // The magic is not part of kernel. - hasHeader |= 0b1; - } - - // Check if kernel size % 0x10 == 0, which will cause copy loop(in shellcode) issue. - if (kernel->fileSize % 0x10) { - printf("Align kernel size to 0x10.\n"); - - // Calculate align padding - uint8_t padding = 0x10 - (kernel->fileSize % 0x10); - - // New kernel size and output size - kernel->fileSize += padding; - patchedKernel->fileSize += padding; - - // Reallocate patched kernel. - void *ptr = realloc(patchedKernel->fileBuffer, patchedKernel->fileSize); - if (ptr == NULL) { - printf("Failed to reallocate patched kernel buffer."); - return NULL; - } else patchedKernel->fileBuffer = ptr; - - // Copy uefi image to 0x10 align place and fill padding part to 0. - if (hasHeader & 0b1) // Kernel has header, need to add 0x14 offset while copying. - { - memmove(patchedKernel->fileBuffer + kernel->fileSize + 0x14, - patchedKernel->fileBuffer + kernel->fileSize - padding + 0x14, uefi->fileSize); - memset(patchedKernel->fileBuffer + kernel->fileSize - padding + 0x14, 0, padding); - } else { - memmove(patchedKernel->fileBuffer + kernel->fileSize, - patchedKernel->fileBuffer + kernel->fileSize - padding, uefi->fileSize); - memset(patchedKernel->fileBuffer + kernel->fileSize - padding, 0, padding); - } - } - - // Kernel has uncompressed_img header, - if (hasHeader & 1) - // Move our pointer after UNCOMPRESSED_IMG header before further processing, - patchedKernel->fileBuffer += 0x14; - - /* Reserved for feature use */ - // Check if kernel was already patched. - // if config in kernel was same with the config provided here, - // set kernel size to previous kernel size. (which skipped copying previously uefi fd) - // if (*(uint64_t *) (kernel->fileBuffer + 0x20) == config->StackBase && - // *(uint64_t *) (kernel->fileBuffer + 0x28) == config->StackSize) { - // printf("Kernel was already patched previously!\n" - // "Re-patching with new UEFI...\n"); - // kernel->fileSize = *(uint64_t *) (kernel->fileBuffer + 0x30); - // patchedKernel->fileSize = kernel->fileSize + uefi->fileSize; // Update output kernel size - // printf("Old kernel size %llx\n", *(uint64_t *) (kernel->fileBuffer + 0x30)); - // hasHeader |= 0b10; - // } - - // Determine the loading offset of the kernel first, - // we are either going to find a b instruction on the - // first instruction or the second one. First is problematic, - // second is fine. - - // 0x14 is AArch64 b opcode - // If Code1 is jump instruction, the kernel is an original kernel or a patched kernel. - // However, if Code2 is also jump instruction, the kernel should be a patched kernel. - if (patchedKernel->fileBuffer[3] == 0x14 && patchedKernel->fileBuffer[7] != 0x14) { - // For kernel without EFI stub, we have to move the jump kernel instruction forward to Code2. - // We have a branch instruction first, we need to fix things a bit. - - // First start by getting the actual value of the branch addr offset. - uint8_t offsetInstructionBuffer[] = {patchedKernel->fileBuffer[0], patchedKernel->fileBuffer[1], - patchedKernel->fileBuffer[2], 0}; - uint32_t *offsetInstruction = (uint32_t *) offsetInstructionBuffer; - - // Now subtract 1 instruction because we'll move this to the second instruction of the kernel header. - (*offsetInstruction)--; - - // Convert back into an actual value that is usable as a b instruction - offsetInstructionBuffer[3] = 0x14; // Useless but just for our sanity :) - - // Now write the instruction back into the kernel (instr 2) - patchedKernel->fileBuffer[4] = offsetInstructionBuffer[0]; - patchedKernel->fileBuffer[5] = offsetInstructionBuffer[1]; - patchedKernel->fileBuffer[6] = offsetInstructionBuffer[2]; - patchedKernel->fileBuffer[7] = 0x14; - } else // Patched Kernel - if (patchedKernel->fileBuffer[3] == 0x14 && patchedKernel->fileBuffer[7] == 0x14) { - printf("Patched kernel detected, redo patch with new fd.\n"); - kernel->fileSize = *(uint64_t *) (kernel->fileBuffer + 0x30); - patchedKernel->fileSize = kernel->fileSize + uefi->fileSize; // Update output kernel size - } else // Kernel with EFI Stub - if (*(uint16_t *) (patchedKernel->fileBuffer) == 0x5A4D && patchedKernel->fileBuffer[7] == 0x14) { - // Check if kernel has EFI Stub, 4D5A, "MZ" - // Note: - // The magic is also an arm64 instruction, and the second instruction jump to kernel. - // We will only rewrite EFI header here. (This will break the kernel) - // For kernel with EFI stub, we only need to patch the efi header. - printf("Kernel has EFI header.\n"); - } else // Unknown stuff - if (patchedKernel->fileBuffer[7] != 0x14) { - // There is no branch instruction! - printf("Error: Invalid Kernel Image. Branch instruction not found within first two instruction slots.\n" - "Please Check the kernel you provided.\n"); - return NULL; - } - - // Alright, our kernel image has a compatible branch instruction, let's start. - - // First, add the jump to our code on instr 1 - // This directly jump right after the kernel header (there's enough headroom here) - patchedKernel->fileBuffer[0] = 0x10; - patchedKernel->fileBuffer[1] = 0; - patchedKernel->fileBuffer[2] = 0; - patchedKernel->fileBuffer[3] = 0x14; - - // Now we need to fill in the config base of our firmware - // Config Base: 0x00000000 9FC00000 (64 bit!) - patchedKernel->fileBuffer[0x20] = config->StackBase >> 0 & 0xFF; - patchedKernel->fileBuffer[0x21] = config->StackBase >> 8 & 0xFF; - patchedKernel->fileBuffer[0x22] = config->StackBase >> 16 & 0xFF; - patchedKernel->fileBuffer[0x23] = config->StackBase >> 24 & 0xFF; - patchedKernel->fileBuffer[0x24] = config->StackBase >> 32 & 0xFF; - patchedKernel->fileBuffer[0x25] = config->StackBase >> 40 & 0xFF; - patchedKernel->fileBuffer[0x26] = config->StackBase >> 48 & 0xFF; - patchedKernel->fileBuffer[0x27] = config->StackBase >> 56 & 0xFF; - - // Then we need to fill in the config size of our firmware - // Config Base: 0x00000000 00300000 (64 bit!) - patchedKernel->fileBuffer[0x28] = config->StackSize >> 0 & 0xFF; - patchedKernel->fileBuffer[0x29] = config->StackSize >> 8 & 0xFF; - patchedKernel->fileBuffer[0x2A] = config->StackSize >> 16 & 0xFF; - patchedKernel->fileBuffer[0x2B] = config->StackSize >> 24 & 0xFF; - patchedKernel->fileBuffer[0x2C] = config->StackSize >> 32 & 0xFF; - patchedKernel->fileBuffer[0x2D] = config->StackSize >> 40 & 0xFF; - patchedKernel->fileBuffer[0x2E] = config->StackSize >> 48 & 0xFF; - patchedKernel->fileBuffer[0x2F] = config->StackSize >> 56 & 0xFF; - - // Finally, we add in the total kernel image size because we need to jump over! - patchedKernel->fileBuffer[0x30] = kernel->fileSize >> 0 & 0xFF; - patchedKernel->fileBuffer[0x31] = kernel->fileSize >> 8 & 0xFF; - patchedKernel->fileBuffer[0x32] = kernel->fileSize >> 16 & 0xFF; - patchedKernel->fileBuffer[0x33] = kernel->fileSize >> 24 & 0xFF; - patchedKernel->fileBuffer[0x34] = kernel->fileSize >> 32 & 0xFF; - patchedKernel->fileBuffer[0x35] = kernel->fileSize >> 40 & 0xFF; - patchedKernel->fileBuffer[0x36] = kernel->fileSize >> 48 & 0xFF; - patchedKernel->fileBuffer[0x37] = kernel->fileSize >> 56 & 0xFF; - - // Now our header is fully patched, let's add a tiny bit - // of code as well to decide what to do. - // Ignore 0x40 Dummy Header in shellCode->fileBuffer, so we add 0x40 offset after it. - memcpy(patchedKernel->fileBuffer + 0x40, shellCode->fileBuffer + 0x40, shellCode->fileSize - 0x40); - - // Move back our pointer and recalculate kernel size in kernel - // header if the patched kernel buffer has UNCOMPRESSED_IMG header. - if (hasHeader & 0b1) { - patchedKernel->fileBuffer -= 0x14; - size_t newKernelSize = kernel->fileSize + uefi->fileSize; - patchedKernel->fileBuffer[0x10] = newKernelSize >> 0 & 0xFF; - patchedKernel->fileBuffer[0x11] = newKernelSize >> 8 & 0xFF; - patchedKernel->fileBuffer[0x12] = newKernelSize >> 16 & 0xFF; - patchedKernel->fileBuffer[0x13] = newKernelSize >> 24 & 0xFF; - } - - // And that's it, the user now can append executable code right after the kernel, - // and upon closing up the device said code will run at boot. Have fun! - return patchedKernel->fileBuffer; -} diff --git a/remover.c b/remover.c deleted file mode 100644 index 8e69e3a..0000000 --- a/remover.c +++ /dev/null @@ -1,129 +0,0 @@ -/** @file - * Patch Remover Source File. - * - * This Program will help you revert patched instructions in a patched kernel. - * - * It only supports several formats of kernel: - * 1. Image file compile from source - * 2. Qualcomm patched kernel. - * 3. Image file kernel with efi stub. - * - * Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. - * MIT License - * - */ - -#include "utils.h" - -/** - * Receive args and handle status. - * - * @param argc argc is numbers of argues given in cmdline. - * @param argv argv is a array that contains all given values. - * @return program status - */ -int main(int argc, char *argv[]) { - printf("Project Aloha DualBoot Patch Remover v1.2.0.0\n"); - printf("Copyright (c) 2021-2024 The DuoWoA authors\n\n"); - if (argc != 3) { - // Print usage if arg numbers not meet. - printf("args: %d\n", argc); - printf("Usage: \n"); - return -EINVAL; - } - - // Program status. - int status = 0; - - // Get file paths. - FileContent patchedImage = {.filePath = argv[1]}; - FileContent outputImage = {.filePath = argv[2]}; - - // Read buffer from patched kernel. - if (!get_file_size(&patchedImage)) { - status = -EINVAL; - goto free_and_exit; - } - patchedImage.fileBuffer = malloc(patchedImage.fileSize); - read_file_content(&patchedImage); - - // Remove! - Remove(&patchedImage, &outputImage); - - // Output buffer to new kernel. - if (outputImage.fileBuffer != NULL) { - write_file_content(&outputImage); - } else { - printf("Error Removing Patch.\n"); - status = -EINVAL; - goto free_and_exit; - } - - // Free buffers we allocated. - free_and_exit: - free(patchedImage.fileBuffer); - free(outputImage.fileBuffer); - - // Everything goes well - printf("Patch successfully removed.\n"); - printf("Please check the unpatched kernel image at %s.\n", outputImage.filePath); - return status; -} - -/** - * Revert patch in kernel and return output file buffer. - * - * @param patchedKernel Input Patched kernel. - * @param outputKernel Output Reverted kernel. - * @return Reverted kernel buffer, NULL if error processing. - */ -uint8_t *Remove(pFileContent patchedKernel, pFileContent outputKernel) { - // Get previous config from patched kernel. - size_t originKernelSize = *(uint64_t *) (patchedKernel->fileBuffer + 0x30); - - // Check if kernel has UNCOMPRESSED_IMG Header. - char kernelHeader[0x11] = {0}; - uint8_t hasHeader = 0; - memcpy(kernelHeader, patchedKernel->fileBuffer, 0x10); - if (strcmp(kernelHeader, "UNCOMPRESSED_IMG") == 0) { - printf("Kernel has UNCOMPRESSED_IMG header.\n"); - originKernelSize = *(uint64_t *) (patchedKernel->fileBuffer + 0x30 + 0x14) + 0x14; - hasHeader |= 0b1; - } - - // Allocate output buffer - outputKernel->fileSize = originKernelSize; - outputKernel->fileBuffer = malloc(outputKernel->fileSize); - - // Copy new buffer into outputBuffer. - memcpy(outputKernel->fileBuffer, patchedKernel->fileBuffer, outputKernel->fileSize); - - // After copying, jump over header - if (hasHeader & 0b1) - outputKernel->fileBuffer += 0x14; - - // Now check if it is a patched kernel - if (outputKernel->fileBuffer[3] == 0x14 && outputKernel->fileBuffer[7] == 0x14) { - printf("Patched kernel detected."); - // Recover Code1 jump instruction, jump to linux kernel directly. - *(uint32_t *) outputKernel->fileBuffer = - ((*(uint32_t *) (outputKernel->fileBuffer + 4) & ~(0xFF << 24)) + 1) | (0x14 << 24); - // Clean Code2 - *(uint32_t *) (outputKernel->fileBuffer + 4) = 0; - } else { - printf("Not an valid kernel."); - return NULL; - } - - // Move back our pointer and recalculate kernel size in kernel - // header if the patched kernel buffer has UNCOMPRESSED_IMG header. - if (hasHeader & 0b1) { - outputKernel->fileBuffer -= 0x14; - size_t newKernelSize = outputKernel->fileSize - 0x14; - outputKernel->fileBuffer[0x10] = newKernelSize >> 0 & 0xFF; - outputKernel->fileBuffer[0x11] = newKernelSize >> 8 & 0xFF; - outputKernel->fileBuffer[0x12] = newKernelSize >> 16 & 0xFF; - outputKernel->fileBuffer[0x13] = newKernelSize >> 24 & 0xFF; - } - return outputKernel->fileBuffer; -} \ No newline at end of file diff --git a/src/patcher.c b/src/patcher.c new file mode 100644 index 0000000..defdf9b --- /dev/null +++ b/src/patcher.c @@ -0,0 +1,21 @@ +/** @file + * Multi-Boot Kernel Patcher entry point. + * + * Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. + * MIT License + */ + +#include "shim.h" + +#include +#include + +int main(int argc, char *argv[]) { + printf("Project Aloha Multi-Boot Kernel Patcher v1.2.0.0\n"); + printf("Copyright (c) 2021-2025 The DuoWoA authors\n\n"); + if (argc != 2) { + printf("Usage: MultiBootKernelPatcher \n"); + return -EINVAL; + } + return PackConfig(argv[1]); +} diff --git a/src/shim.c b/src/shim.c new file mode 100644 index 0000000..486919d --- /dev/null +++ b/src/shim.c @@ -0,0 +1,362 @@ +#include "shim.h" +#include "shim_format.h" +#include "utils.h" + +#include + +static bool align_linux_offset(size_t minimum, + uint64_t primaryTextOffset, + uint64_t imageTextOffset, + size_t alignment, + size_t *result) { + const size_t mask = SHIM_LINUX_ALIGNMENT - 1; + size_t requiredResidue = + ((size_t)imageTextOffset - (size_t)primaryTextOffset) & mask; + size_t adjustment = (requiredResidue - (minimum & mask)) & mask; + if (minimum > SIZE_MAX - adjustment) + return false; + *result = minimum + adjustment; + return (*result & (alignment - 1)) == 0; +} + +static bool load_file(FileContent *file) { + if (!get_file_size(file)) + return false; + file->fileBuffer = malloc(file->fileSize); + return file->fileBuffer != NULL && read_file_content(file) != NULL; +} + +static bool resize_output(FileContent *output, size_t newSize) { + uint8_t *resized = realloc(output->fileBuffer, newSize); + if (resized == NULL) + return false; + if (newSize > output->fileSize) + memset(resized + output->fileSize, 0, newSize - output->fileSize); + output->fileBuffer = resized; + output->fileSize = newSize; + return true; +} + +static bool inspect_typed_blob(const FileContent *file, + const ShimImageConfig *image) { + if (image->type == SHIM_ENTRY_TYPE_DTB) { + static const uint8_t fdtMagic[] = {0xd0, 0x0d, 0xfe, 0xed}; + if (file->fileSize < sizeof(fdtMagic) || + memcmp(file->fileBuffer, fdtMagic, sizeof(fdtMagic)) != 0) { + printf("Error: [%s] is not a flattened device tree blob.\n", + image->section); + return false; + } + } else if (image->type == SHIM_ENTRY_TYPE_MANIFEST) { + if (file->fileSize < sizeof(ShimManifestHeader) || + memcmp(file->fileBuffer, SHIM_MANIFEST_MAGIC, 8) != 0 || + shim_read_le32(file->fileBuffer + 8) != SHIM_MANIFEST_VERSION) { + printf("Error: [%s] is not a v%u Shim manifest.\n", + image->section, SHIM_MANIFEST_VERSION); + return false; + } + } + return true; +} + +static bool patch_kernel_entry(uint8_t *kernelHeader, size_t shimOffset) { + if ((shimOffset & (SHIM_BRANCH_ALIGNMENT - 1)) != 0 || + shimOffset >= (size_t)SHIM_BRANCH_MAX_OFFSET) { + printf("Error: Shim is outside the ARM64 BL range.\n"); + return false; + } + + uint32_t firstInstruction = shim_read_le32(kernelHeader); + uint32_t secondInstruction = shim_read_le32(kernelHeader + 4); + bool firstIsBranch = (firstInstruction & 0xfc000000U) == 0x14000000U; + bool secondIsBranch = (secondInstruction & 0xfc000000U) == 0x14000000U; + + if (firstIsBranch && !secondIsBranch) { + uint32_t movedBranch = 0x14000000U | + ((firstInstruction - 1U) & 0x03ffffffU); + shim_write_le32(kernelHeader + 4, movedBranch); + } else if (!secondIsBranch) { + printf("Error: Base image has no supported entry branch.\n"); + return false; + } + + uint32_t branchImmediate = (uint32_t)(shimOffset >> 2); + shim_write_le32(kernelHeader, 0x94000000U | branchImmediate); + return true; +} + +int PackShim(const ShimPackConfig *config) { + const ShimImageConfig *baseImage = + &config->images[config->baseImageIndex]; + bool writeManifest = config->manifest.present; + int status = -EINVAL; + + FileContent primary = {.filePath = baseImage->path}; + FileContent shim = {.filePath = config->shim}; + FileContent output = {.filePath = config->output}; + ShimManifestRecoveryEntry recoveryEntries[SHIM_MAX_ENTRIES] = {0}; + + if (!load_file(&primary) || !load_file(&shim)) + goto cleanup; + if (shim.fileSize == 0) { + printf("Error: Shim must be a non-empty blob.\n"); + goto cleanup; + } + + Arm64ImageInfo primaryInfo = {0}; + if (!shim_inspect_arm64_image(&primary, &primaryInfo)) { + printf("Error: Base image is not an ARM64 Linux Image.\n"); + goto cleanup; + } + + size_t primarySlot = primaryInfo.payloadSize; + if (primaryInfo.imageSize > primarySlot) { + if (primaryInfo.imageSize > SIZE_MAX) { + printf("Error: Base image is too large.\n"); + goto cleanup; + } + primarySlot = (size_t)primaryInfo.imageSize; + } + if (!shim_align_up(primarySlot, SHIM_BRANCH_ALIGNMENT, &primarySlot) || + primarySlot >= (size_t)SHIM_BRANCH_MAX_OFFSET || + primaryInfo.headerOffset > SIZE_MAX - primarySlot || + primaryInfo.headerOffset + primarySlot > SIZE_MAX - shim.fileSize) { + printf("Error: Base image cannot reach the attached Shim with BL.\n"); + goto cleanup; + } + + output.fileSize = primaryInfo.headerOffset + primarySlot + shim.fileSize; + output.fileBuffer = calloc(1, output.fileSize); + if (output.fileBuffer == NULL) + goto cleanup; + memcpy(output.fileBuffer, primary.fileBuffer, primary.fileSize); + memcpy(output.fileBuffer + primaryInfo.headerOffset + primarySlot, + shim.fileBuffer, shim.fileSize); + + ShimManifestRecoveryHeader recovery = {0}; + if (writeManifest) { + memcpy(recovery.magic, SHIM_MANIFEST_RECOVERY_MAGIC, 8); + recovery.version = SHIM_MANIFEST_RECOVERY_VERSION; + recovery.headerSize = sizeof(ShimManifestRecoveryHeader); + recovery.entrySize = sizeof(ShimManifestRecoveryEntry); + recovery.entryCount = (uint32_t)config->imageCount; + recovery.shimSize = shim.fileSize; + memcpy(recovery.baseHeader, + primary.fileBuffer + primaryInfo.headerOffset, + sizeof(recovery.baseHeader)); + recoveryEntries[0].alignment = baseImage->alignment; + recoveryEntries[0].sourcePrefixSize = + (uint32_t)primaryInfo.headerOffset; + if (primaryInfo.headerOffset != 0) + memcpy(recoveryEntries[0].sourcePrefix, + primary.fileBuffer, primaryInfo.headerOffset); + } + + uint8_t *kernelHeader = output.fileBuffer + primaryInfo.headerOffset; + if (!patch_kernel_entry(kernelHeader, primarySlot)) + goto cleanup; + + ShimManifest manifest = {0}; + memcpy(manifest.header.magic, SHIM_MANIFEST_MAGIC, 8); + manifest.header.version = SHIM_MANIFEST_VERSION; + manifest.header.headerSize = sizeof(ShimManifestHeader); + manifest.header.entrySize = sizeof(ShimManifestEntry); + manifest.header.entryCount = (uint32_t)config->imageCount; + manifest.header.defaultEntry = config->defaultEntry; + manifest.header.timeoutMs = config->timeoutMs; + + ShimManifestEntry *baseEntry = &manifest.entries[0]; + memcpy(baseEntry->name, baseImage->name, strlen(baseImage->name) + 1); + baseEntry->type = SHIM_ENTRY_TYPE_LINUX; + baseEntry->entryOffset = baseImage->entryOffset; + baseEntry->offset = 0; + baseEntry->size = primaryInfo.payloadSize; + baseEntry->flags = SHIM_ENTRY_FLAG_BASE_IMAGE; + + size_t manifestIndex = 1; + for (size_t index = 0; index < config->imageCount; index++) { + if (index == config->baseImageIndex) + continue; + + const ShimImageConfig *image = &config->images[index]; + FileContent extra = {.filePath = image->path}; + if (!load_file(&extra)) + goto cleanup; + if (!inspect_typed_blob(&extra, image)) { + free(extra.fileBuffer); + goto cleanup; + } + if (image->hasCopyAddress && extra.fileSize > image->copySizeMax) { + printf("Error: [%s] image size 0x%zx exceeds CopySizeMax " + "0x%llx.\n", image->section, extra.fileSize, + (unsigned long long)image->copySizeMax); + free(extra.fileBuffer); + goto cleanup; + } + if (image->hasCopyAddress && + image->copyAddress > UINT64_MAX - extra.fileSize) { + printf("Error: Copy address range overflows: %s\n", + image->section); + free(extra.fileBuffer); + goto cleanup; + } + + size_t runtimeSize = output.fileSize - primaryInfo.headerOffset; + size_t imageOffset = 0; + size_t payloadOffset = 0; + size_t payloadSize = extra.fileSize; + size_t imageSlot = extra.fileSize; + Arm64ImageInfo extraInfo = {0}; + + if (image->type == SHIM_ENTRY_TYPE_LINUX) { + if (!shim_inspect_arm64_image(&extra, &extraInfo)) { + printf("Error: [%s] is not an ARM64 Linux Image.\n", + image->section); + free(extra.fileBuffer); + goto cleanup; + } + payloadOffset = extraInfo.headerOffset; + payloadSize = extraInfo.payloadSize; + imageSlot = extraInfo.payloadSize; + if (extraInfo.imageSize > imageSlot) { + if (extraInfo.imageSize > SIZE_MAX) { + free(extra.fileBuffer); + goto cleanup; + } + imageSlot = (size_t)extraInfo.imageSize; + } + if (!align_linux_offset(runtimeSize, primaryInfo.textOffset, + extraInfo.textOffset, + (size_t)image->alignment, &imageOffset) || + !shim_align_up(imageSlot, 0x1000, &imageSlot)) { + printf("Error: [%s] cannot satisfy Linux Image alignment.\n", + image->section); + free(extra.fileBuffer); + goto cleanup; + } + } else if (!shim_align_up(runtimeSize, (size_t)image->alignment, + &imageOffset)) { + printf("Error: [%s] image alignment overflows.\n", image->section); + free(extra.fileBuffer); + goto cleanup; + } + + if (writeManifest) { + recoveryEntries[manifestIndex].alignment = image->alignment; + recoveryEntries[manifestIndex].copySizeMax = + image->hasCopySizeMax ? image->copySizeMax : 0; + recoveryEntries[manifestIndex].sourcePrefixSize = + (uint32_t)payloadOffset; + if (payloadOffset != 0) + memcpy(recoveryEntries[manifestIndex].sourcePrefix, + extra.fileBuffer, payloadOffset); + } + + if (shim_is_executable_type(image->type) && + (image->entryOffset > payloadSize || + payloadSize - (size_t)image->entryOffset < 4 || + (image->entryOffset & (SHIM_BRANCH_ALIGNMENT - 1)) != 0)) { + printf("Error: [%s] EntryOffset is outside the executable image.\n", + image->section); + free(extra.fileBuffer); + goto cleanup; + } + if (imageOffset > SIZE_MAX - imageSlot || + primaryInfo.headerOffset > SIZE_MAX - imageOffset - imageSlot || + !resize_output(&output, + primaryInfo.headerOffset + imageOffset + imageSlot)) { + free(extra.fileBuffer); + goto cleanup; + } + memcpy(output.fileBuffer + primaryInfo.headerOffset + imageOffset, + extra.fileBuffer + payloadOffset, payloadSize); + + ShimManifestEntry *entry = &manifest.entries[manifestIndex++]; + memcpy(entry->name, image->name, strlen(image->name) + 1); + entry->type = image->type; + entry->entryOffset = image->entryOffset; + entry->offset = imageOffset; + entry->size = payloadSize; + if (image->hasCopyAddress) { + entry->flags |= SHIM_ENTRY_FLAG_COPY; + entry->loadAddress = image->copyAddress; + } + free(extra.fileBuffer); + } + + size_t recoveryOffset = 0; + size_t recoverySize = 0; + size_t manifestOffset = 0; + size_t manifestSize = 0; + if (writeManifest) { + size_t runtimeManifestSize = sizeof(ShimManifestHeader) + + manifest.header.entryCount * sizeof(ShimManifestEntry); + recoveryOffset = runtimeManifestSize; + recoverySize = sizeof(ShimManifestRecoveryHeader) + + config->imageCount * sizeof(ShimManifestRecoveryEntry); + if (runtimeManifestSize > SIZE_MAX - recoverySize) { + printf("Error: Shim manifest size overflows.\n"); + goto cleanup; + } + manifestSize = runtimeManifestSize + recoverySize; + size_t oldRuntimeSize = output.fileSize - primaryInfo.headerOffset; + if (!shim_align_up(oldRuntimeSize, sizeof(uint64_t), &manifestOffset)) { + printf("Error: Failed to align Shim manifest.\n"); + goto cleanup; + } + size_t manifestFileOffset = primaryInfo.headerOffset + manifestOffset; + if (manifestSize > SHIM_MANIFEST_MAX_SIZE || + manifestSize > SIZE_MAX - manifestFileOffset || + !resize_output(&output, manifestFileOffset + manifestSize)) { + printf("Error: Failed to append Shim manifest.\n"); + goto cleanup; + } + manifest.header.imageSize = manifestOffset + manifestSize; + manifest.header.reserved[0] = recoveryOffset; + manifest.header.reserved[1] = recoverySize; + memcpy(output.fileBuffer + manifestFileOffset, &manifest, + runtimeManifestSize); + memcpy(output.fileBuffer + manifestFileOffset + recoveryOffset, + &recovery, sizeof(recovery)); + memcpy(output.fileBuffer + manifestFileOffset + recoveryOffset + + sizeof(recovery), recoveryEntries, + config->imageCount * sizeof(ShimManifestRecoveryEntry)); + } + + size_t runtimeImageSize = output.fileSize - primaryInfo.headerOffset; + kernelHeader = output.fileBuffer + primaryInfo.headerOffset; + shim_write_le64(kernelHeader + 0x10, runtimeImageSize); + shim_write_le64(kernelHeader + 0x20, manifestOffset); + shim_write_le64(kernelHeader + 0x28, manifestSize); + shim_write_le64(kernelHeader + 0x30, primarySlot); + + if (primaryInfo.headerOffset != 0) { + if (runtimeImageSize > UINT32_MAX) { + printf("Error: UNCOMPRESSED_IMG payload exceeds 32-bit size.\n"); + goto cleanup; + } + shim_write_le32(output.fileBuffer + 0x10, (uint32_t)runtimeImageSize); + } + if (write_file_content(&output) != 0) { + printf("Error: Failed to write packed image.\n"); + goto cleanup; + } + + printf("Shim image packed successfully.\n"); + printf(" Base image slot: 0x%zx\n", primarySlot); + printf(" Shim blob: offset 0x%zx, size 0x%zx\n", + primarySlot, shim.fileSize); + if (writeManifest) + printf(" Manifest v%u: offset 0x%zx, size 0x%zx\n", + SHIM_MANIFEST_VERSION, manifestOffset, manifestSize); + else + printf(" Manifest: omitted\n"); + printf(" Output size: 0x%zx\n", runtimeImageSize); + status = 0; + +cleanup: + free(primary.fileBuffer); + free(shim.fileBuffer); + free(output.fileBuffer); + return status; +} diff --git a/src/shim_config.c b/src/shim_config.c new file mode 100644 index 0000000..4a4125c --- /dev/null +++ b/src/shim_config.c @@ -0,0 +1,365 @@ +#include "shim.h" + +#include + +#include +#include +#include +#include +#include + +typedef struct { + ShimPackConfig config; + char configDirectory[SHIM_PATH_SIZE]; + char error[256]; +} ConfigParser; + +static bool copy_text(char *destination, size_t size, const char *value) { + size_t length = strlen(value); + if (length >= size) + return false; + memcpy(destination, value, length + 1); + return true; +} + +static bool resolve_path(char *destination, + size_t size, + const char *configDirectory, + const char *value) { + if (value[0] == '/') + return copy_text(destination, size, value); + + int written = snprintf(destination, size, "%s/%s", configDirectory, value); + return written >= 0 && (size_t)written < size; +} + +static bool parse_u32(const char *value, uint32_t *result) { + char *end = NULL; + errno = 0; + unsigned long parsed = strtoul(value, &end, 0); + if (errno != 0 || value[0] == '\0' || *end != '\0' || parsed > UINT32_MAX) + return false; + *result = (uint32_t)parsed; + return true; +} + +static bool parse_u64(const char *value, uint64_t *result) { + char *end = NULL; + errno = 0; + if (value[0] == '-') + return false; + unsigned long long parsed = strtoull(value, &end, 0); + if (errno != 0 || value[0] == '\0' || *end != '\0') + return false; + *result = (uint64_t)parsed; + return true; +} + +static bool parse_bool(const char *value, int *result) { + if (strcmp(value, "true") == 0 || strcmp(value, "yes") == 0 || + strcmp(value, "1") == 0) { + *result = 1; + return true; + } + if (strcmp(value, "false") == 0 || strcmp(value, "no") == 0 || + strcmp(value, "0") == 0) { + *result = 0; + return true; + } + return false; +} + +static bool parse_image_type(const char *value, uint32_t *result) { + static const struct { + const char *name; + uint32_t type; + } types[] = { + {"Linux", SHIM_ENTRY_TYPE_LINUX}, + {"FreeExec", SHIM_ENTRY_TYPE_FREE_EXEC}, + {"Shim", SHIM_ENTRY_TYPE_SHIM}, + {"Blob", SHIM_ENTRY_TYPE_BLOB}, + {"DTB", SHIM_ENTRY_TYPE_DTB}, + {"Manifest", SHIM_ENTRY_TYPE_MANIFEST}, + }; + + for (size_t index = 0; index < sizeof(types) / sizeof(types[0]); index++) { + if (strcmp(value, types[index].name) == 0) { + *result = types[index].type; + return true; + } + } + return false; +} + +static bool set_once(char *destination, size_t size, const char *value) { + return destination[0] == '\0' && copy_text(destination, size, value); +} + +static bool set_path_once(char *destination, + size_t size, + const ConfigParser *parser, + const char *value) { + return destination[0] == '\0' && + resolve_path(destination, size, parser->configDirectory, value); +} + +static ShimImageConfig *find_or_add_image(ConfigParser *parser, + const char *section) { + for (size_t index = 0; index < parser->config.imageCount; index++) { + if (strcmp(parser->config.images[index].section, section) == 0) + return &parser->config.images[index]; + } + + if (parser->config.imageCount >= SHIM_MAX_ENTRIES || + strlen(section) >= sizeof(parser->config.images[0].section)) + return NULL; + + ShimImageConfig *image = + &parser->config.images[parser->config.imageCount++]; + memcpy(image->section, section, strlen(section) + 1); + image->baseImage = -1; + return image; +} + +static int config_handler(void *user, + const char *section, + const char *name, + const char *value) { + ConfigParser *parser = user; + bool valid = false; + + if (parser->config.manifest.present && + strcmp(section, "Manifest") != 0) { + snprintf(parser->error, sizeof(parser->error), + "[Manifest] must be the final section"); + return 0; + } + + if (strcmp(section, "Pack") == 0) { + if (strcmp(name, "Shim") == 0) + valid = set_path_once(parser->config.shim, + sizeof(parser->config.shim), parser, value); + else if (strcmp(name, "Output") == 0) + valid = set_path_once(parser->config.output, + sizeof(parser->config.output), parser, value); + else if (strcmp(name, "Default") == 0) + valid = set_once(parser->config.defaultSection, + sizeof(parser->config.defaultSection), value); + else if (strcmp(name, "Timeout") == 0) + valid = parse_u32(value, &parser->config.timeoutMs); + } else if (strcmp(section, "Manifest") == 0) { + parser->config.manifest.present = 1; + if (strcmp(name, "Type") == 0 && !parser->config.manifest.hasType) { + valid = parse_image_type(value, &parser->config.manifest.type); + parser->config.manifest.hasType = valid; + } + } else if (strcmp(section, "Image") == 0 || + (strncmp(section, "Image-", 6) == 0 && section[6] != '\0')) { + ShimImageConfig *image = find_or_add_image(parser, section); + if (image != NULL) { + if (strcmp(name, "Name") == 0) + valid = set_once(image->name, sizeof(image->name), value); + else if (strcmp(name, "Path") == 0) + valid = set_path_once(image->path, sizeof(image->path), + parser, value); + else if (strcmp(name, "Type") == 0 && !image->hasType) { + valid = parse_image_type(value, &image->type); + image->hasType = valid; + } + else if (strcmp(name, "BaseImage") == 0 && image->baseImage == -1) + valid = parse_bool(value, &image->baseImage); + else if (strcmp(name, "CopyTo") == 0 && + !image->hasCopyAddress) { + valid = parse_u64(value, &image->copyAddress); + image->hasCopyAddress = valid; + } else if (strcmp(name, "CopySizeMax") == 0 && + !image->hasCopySizeMax) { + valid = parse_u64(value, &image->copySizeMax); + image->hasCopySizeMax = valid; + } else if (strcmp(name, "Align") == 0 && + !image->hasAlignment) { + valid = parse_u64(value, &image->alignment); + image->hasAlignment = valid; + } else if (strcmp(name, "EntryOffset") == 0 && + !image->hasEntryOffset) { + valid = parse_u64(value, &image->entryOffset); + image->hasEntryOffset = valid; + } + } + } + + if (!valid) { + snprintf(parser->error, sizeof(parser->error), + "invalid or duplicate key [%s] %s", section, name); + return 0; + } + return 1; +} + +static bool get_config_directory(const char *configPath, + char *directory, + size_t size) { + const char *separator = strrchr(configPath, '/'); + if (separator == NULL) + return copy_text(directory, size, "."); + + size_t length = (size_t)(separator - configPath); + if (length == 0) + length = 1; + if (length >= size) + return false; + memcpy(directory, configPath, length); + directory[length] = '\0'; + return true; +} + +static bool validate_config(ConfigParser *parser, + size_t *baseImageIndex, + size_t *defaultIndex) { + ShimPackConfig *config = &parser->config; + if (config->shim[0] == '\0' || config->output[0] == '\0') { + printf("Error: [Pack] requires Shim and Output.\n"); + return false; + } + if (config->imageCount == 0) { + printf("Error: At least one [Image-*] section is required.\n"); + return false; + } + + size_t baseImageCount = 0; + bool defaultFound = false; + for (size_t index = 0; index < config->imageCount; index++) { + ShimImageConfig *image = &config->images[index]; + if (image->name[0] == '\0' || image->path[0] == '\0' || + !image->hasType || image->baseImage == -1) { + printf("Error: [%s] requires Name, Path, Type and BaseImage.\n", + image->section); + return false; + } + if (!image->hasAlignment) + image->alignment = 4; + if (image->alignment < SHIM_BRANCH_ALIGNMENT || + image->alignment > UINT32_MAX || + (image->alignment & (image->alignment - 1)) != 0) { + printf("Error: [%s] Align must be a power of two from 4 to " + "0xffffffff.\n", image->section); + return false; + } + if (image->baseImage) { + if (image->type != SHIM_ENTRY_TYPE_LINUX) { + printf("Error: Base image [%s] must set Type=Linux.\n", + image->section); + return false; + } + if (image->hasCopyAddress) { + printf("Error: Base image [%s] cannot set CopyTo.\n", + image->section); + return false; + } + if (image->entryOffset != 0) { + printf("Error: Base image [%s] cannot set EntryOffset.\n", + image->section); + return false; + } + *baseImageIndex = index; + baseImageCount++; + } + if (image->hasCopyAddress) { + if (image->copyAddress == 0) { + printf("Error: [%s] CopyTo address must be non-zero.\n", + image->section); + return false; + } + if ((image->copyAddress & 3) != 0) { + printf("Error: [%s] CopyTo address must be aligned to 4 " + "bytes.\n", image->section); + return false; + } + if (!image->hasCopySizeMax || image->copySizeMax == 0) { + printf("Error: [%s] with CopyTo requires non-zero " + "CopySizeMax.\n", image->section); + return false; + } + if (image->copyAddress > UINT64_MAX - image->copySizeMax) { + printf("Error: [%s] CopyTo region overflows.\n", + image->section); + return false; + } + } else if (image->hasCopySizeMax) { + printf("Error: [%s] CopySizeMax requires CopyTo.\n", + image->section); + return false; + } + bool executable = image->type == SHIM_ENTRY_TYPE_LINUX || + image->type == SHIM_ENTRY_TYPE_FREE_EXEC || + image->type == SHIM_ENTRY_TYPE_SHIM; + if (image->type == SHIM_ENTRY_TYPE_MANIFEST) { + printf("Error: [%s] must be configured as the [Manifest] " + "section.\n", image->section); + return false; + } + if (!executable && image->hasEntryOffset) { + printf("Error: [%s] Type does not allow EntryOffset.\n", + image->section); + return false; + } + if (config->defaultSection[0] != '\0' && + strcmp(image->section, config->defaultSection) == 0) { + *defaultIndex = index; + defaultFound = true; + } + } + + if (config->manifest.present && + (!config->manifest.hasType || + config->manifest.type != SHIM_ENTRY_TYPE_MANIFEST)) { + printf("Error: [Manifest] must set Type=Manifest.\n"); + return false; + } + + if (baseImageCount != 1) { + printf("Error: Exactly one [Image-*] must set BaseImage=true.\n"); + return false; + } + config->baseImageIndex = *baseImageIndex; + if (config->defaultSection[0] == '\0') { + *defaultIndex = *baseImageIndex; + defaultFound = true; + } + if (!defaultFound) { + printf("Error: Default must name an [Image-*] section.\n"); + return false; + } + config->defaultEntry = (uint32_t)( + *defaultIndex == *baseImageIndex + ? 0 + : 1 + *defaultIndex - (*defaultIndex > *baseImageIndex)); + return true; +} + +int PackConfig(const char *configPath) { + ConfigParser parser = {0}; + parser.config.timeoutMs = 5000; + if (!get_config_directory(configPath, parser.configDirectory, + sizeof(parser.configDirectory))) { + printf("Error: Config path is too long.\n"); + return -EINVAL; + } + + int parseStatus = ini_parse(configPath, config_handler, &parser); + if (parseStatus != 0) { + if (parseStatus == -1) + printf("Error: Cannot open config: %s\n", configPath); + else + printf("Error: Config line %d: %s\n", parseStatus, + parser.error[0] != '\0' ? parser.error : "parse failure"); + return -EINVAL; + } + + size_t baseImageIndex = 0; + size_t defaultIndex = SIZE_MAX; + if (!validate_config(&parser, &baseImageIndex, &defaultIndex)) + return -EINVAL; + + return PackShim(&parser.config); +} \ No newline at end of file diff --git a/src/shim_format.c b/src/shim_format.c new file mode 100644 index 0000000..f6ea56e --- /dev/null +++ b/src/shim_format.c @@ -0,0 +1,79 @@ +#include "shim_format.h" + +uint32_t shim_read_le32(const uint8_t *buffer) { + uint32_t value = 0; + for (int index = 3; index >= 0; index--) + value = (value << 8) | buffer[index]; + return value; +} + +uint64_t shim_read_le64(const uint8_t *buffer) { + uint64_t value = 0; + for (int index = 7; index >= 0; index--) + value = (value << 8) | buffer[index]; + return value; +} + +void shim_write_le32(uint8_t *buffer, uint32_t value) { + for (int index = 0; index < 4; index++) + buffer[index] = (uint8_t)(value >> (index * 8) & 0xff); +} + +void shim_write_le64(uint8_t *buffer, uint64_t value) { + for (int index = 0; index < 8; index++) + buffer[index] = (uint8_t)(value >> (index * 8) & 0xff); +} + +bool shim_align_up(size_t value, size_t alignment, size_t *result) { + if (alignment == 0 || (alignment & (alignment - 1)) != 0 || + value > SIZE_MAX - (alignment - 1)) + return false; + *result = (value + alignment - 1) & ~(alignment - 1); + return true; +} + +bool shim_inspect_arm64_image(const FileContent *file, Arm64ImageInfo *info) { + static const uint8_t arm64Magic[] = {'A', 'R', 'M', 0x64}; + + info->headerOffset = 0; + if (file->fileSize >= SHIM_WRAPPER_SIZE && + memcmp(file->fileBuffer, "UNCOMPRESSED_IMG", 0x10) == 0) + info->headerOffset = SHIM_WRAPPER_SIZE; + + if (file->fileSize < info->headerOffset + SHIM_LINUX_HEADER_SIZE || + memcmp(file->fileBuffer + info->headerOffset + 0x38, + arm64Magic, sizeof(arm64Magic)) != 0) + return false; + + info->payloadSize = file->fileSize - info->headerOffset; + info->textOffset = shim_read_le64( + file->fileBuffer + info->headerOffset + 0x08); + info->imageSize = shim_read_le64( + file->fileBuffer + info->headerOffset + 0x10); + return true; +} + +bool shim_is_executable_type(uint32_t type) { + return type == SHIM_ENTRY_TYPE_LINUX || + type == SHIM_ENTRY_TYPE_FREE_EXEC || + type == SHIM_ENTRY_TYPE_SHIM; +} + +const char *shim_type_name(uint32_t type) { + switch (type) { + case SHIM_ENTRY_TYPE_LINUX: + return "Linux"; + case SHIM_ENTRY_TYPE_FREE_EXEC: + return "FreeExec"; + case SHIM_ENTRY_TYPE_SHIM: + return "Shim"; + case SHIM_ENTRY_TYPE_BLOB: + return "Blob"; + case SHIM_ENTRY_TYPE_DTB: + return "DTB"; + case SHIM_ENTRY_TYPE_MANIFEST: + return "Manifest"; + default: + return NULL; + } +} \ No newline at end of file diff --git a/src/unpack.c b/src/unpack.c new file mode 100644 index 0000000..abf36af --- /dev/null +++ b/src/unpack.c @@ -0,0 +1,357 @@ +#include "shim_format.h" + +#include +#include + +#ifdef _WIN32 +#include +#define mkdir(path, mode) _mkdir(path) +#endif + +typedef struct { + uint8_t *buffer; + size_t size; + size_t headerOffset; + const ShimManifest *manifest; + const ShimManifestRecoveryHeader *recovery; + const ShimManifestRecoveryEntry *recoveryEntries; +} PackedImage; + +static bool join_path(char *result, size_t size, const char *directory, + const char *name) { + int written = snprintf(result, size, "%s/%s", directory, name); + return written >= 0 && (size_t)written < size; +} + +static bool make_directory(const char *path) { + if (mkdir(path, 0777) == 0) + return true; + return errno == EEXIST; +} + +static bool write_buffer(const char *path, const uint8_t *buffer, size_t size) { + FileContent file = {.filePath = path, .fileBuffer = (uint8_t *)buffer, + .fileSize = size}; + return write_file_content(&file) == 0; +} + +static bool range_inside(size_t offset, size_t size, size_t limit) { + return offset <= limit && size <= limit - offset; +} + +static bool ranges_overlap(size_t firstOffset, size_t firstSize, + size_t secondOffset, size_t secondSize) { + return firstOffset < secondOffset + secondSize && + secondOffset < firstOffset + firstSize; +} + +static bool valid_name(const char name[32]) { + const char *end = memchr(name, '\0', 32); + if (end == NULL || end == name) + return false; + for (const char *current = name; current < end; current++) { + unsigned char character = (unsigned char)*current; + if (character < 0x20 || character == 0x7f) + return false; + } + return true; +} + +static bool load_packed(const char *path, PackedImage *packed) { + FileContent file = {.filePath = path}; + if (!get_file_size(&file)) + return false; + packed->buffer = malloc(file.fileSize); + if (packed->buffer == NULL) + return false; + file.fileBuffer = packed->buffer; + if (read_file_content(&file) == NULL) { + free(packed->buffer); + packed->buffer = NULL; + return false; + } + packed->size = file.fileSize; + + FileContent view = {.fileBuffer = packed->buffer, .fileSize = packed->size}; + Arm64ImageInfo image = {0}; + if (!shim_inspect_arm64_image(&view, &image)) { + printf("Error: Input is not an ARM64 Linux Image.\n"); + return false; + } + packed->headerOffset = image.headerOffset; + if (packed->size - packed->headerOffset < SHIM_LINUX_HEADER_SIZE) { + printf("Error: Input image is truncated.\n"); + return false; + } + + const uint8_t *header = packed->buffer + packed->headerOffset; + uint64_t manifestOffset = shim_read_le64(header + 0x20); + uint64_t manifestSize = shim_read_le64(header + 0x28); + uint64_t runtimeSize = shim_read_le64(header + 0x10); + if (manifestOffset > SIZE_MAX || manifestSize > SIZE_MAX || + runtimeSize != packed->size - packed->headerOffset || + !range_inside((size_t)manifestOffset, (size_t)manifestSize, + (size_t)runtimeSize) || + manifestSize < sizeof(ShimManifestHeader)) { + printf("Error: Input image has invalid Manifest bounds.\n"); + return false; + } + + const uint8_t *manifestBuffer = header + (size_t)manifestOffset; + if (memcmp(manifestBuffer, SHIM_MANIFEST_MAGIC, 8) != 0 || + shim_read_le32(manifestBuffer + 8) != SHIM_MANIFEST_VERSION) { + printf("Error: Input image is not a supported reversible image.\n"); + return false; + } + const ShimManifest *manifest = (const ShimManifest *)manifestBuffer; + if (manifest->header.headerSize != sizeof(ShimManifestHeader) || + manifest->header.entrySize != sizeof(ShimManifestEntry) || + manifest->header.entryCount == 0 || + manifest->header.entryCount > SHIM_MAX_ENTRIES || + manifest->header.imageSize != runtimeSize || + manifestSize > SHIM_MANIFEST_MAX_SIZE || + manifest->header.defaultEntry >= manifest->header.entryCount || + manifest->header.reserved[0] > SIZE_MAX || + manifest->header.reserved[1] > SIZE_MAX) { + printf("Error: Input image has an invalid Manifest.\n"); + return false; + } + + size_t recoveryOffset = (size_t)manifest->header.reserved[0]; + size_t recoverySize = (size_t)manifest->header.reserved[1]; + size_t entryCount = manifest->header.entryCount; + size_t expectedRecoverySize = sizeof(ShimManifestRecoveryHeader) + + entryCount * sizeof(ShimManifestRecoveryEntry); + if (recoveryOffset == 0 || recoverySize != expectedRecoverySize || + !range_inside(recoveryOffset, recoverySize, (size_t)manifestSize) || + recoveryOffset != sizeof(ShimManifestHeader) + + entryCount * sizeof(ShimManifestEntry)) { + printf("Error: Input image has no supported Recovery data.\n"); + return false; + } + + const uint8_t *recoveryBuffer = manifestBuffer + recoveryOffset; + const ShimManifestRecoveryHeader *recovery = + (const ShimManifestRecoveryHeader *)recoveryBuffer; + if (memcmp(recovery->magic, SHIM_MANIFEST_RECOVERY_MAGIC, 8) != 0 || + recovery->version != SHIM_MANIFEST_RECOVERY_VERSION || + recovery->headerSize != sizeof(ShimManifestRecoveryHeader) || + recovery->entrySize != sizeof(ShimManifestRecoveryEntry) || + recovery->entryCount != entryCount || recovery->shimSize == 0 || + recovery->shimSize > SIZE_MAX) { + printf("Error: Input image has an invalid Recovery header.\n"); + return false; + } + + size_t primarySlot = (size_t)shim_read_le64(header + 0x30); + if (primarySlot == 0 || recovery->shimSize > SIZE_MAX - primarySlot || + !range_inside(primarySlot, (size_t)recovery->shimSize, + (size_t)manifestOffset)) { + printf("Error: Input image has invalid Shim bounds.\n"); + return false; + } + for (size_t index = 0; index < entryCount; index++) { + const ShimManifestEntry *entry = &manifest->entries[index]; + if (shim_type_name(entry->type) == NULL || !valid_name(entry->name) || + entry->offset > SIZE_MAX || entry->size > SIZE_MAX || + !range_inside((size_t)entry->offset, (size_t)entry->size, + (size_t)manifestOffset)) { + printf("Error: Input image has an invalid Manifest entry.\n"); + return false; + } + } + + packed->manifest = manifest; + packed->recovery = recovery; + packed->recoveryEntries = + (const ShimManifestRecoveryEntry *)(recoveryBuffer + + sizeof(ShimManifestRecoveryHeader)); + + if (manifest->entries[0].offset != 0 || + manifest->entries[0].type != SHIM_ENTRY_TYPE_LINUX || + manifest->entries[0].flags != SHIM_ENTRY_FLAG_BASE_IMAGE || + manifest->entries[0].size < SHIM_LINUX_HEADER_SIZE) + return false; + size_t shimEnd = primarySlot + (size_t)recovery->shimSize; + for (size_t index = 0; index < entryCount; index++) { + const ShimManifestEntry *entry = &manifest->entries[index]; + const ShimManifestRecoveryEntry *metadata = + &packed->recoveryEntries[index]; + uint32_t allowedFlags = index == 0 + ? SHIM_ENTRY_FLAG_BASE_IMAGE + : SHIM_ENTRY_FLAG_COPY; + if ((entry->flags & ~allowedFlags) != 0 || + metadata->alignment < SHIM_BRANCH_ALIGNMENT || + metadata->alignment > UINT32_MAX || + (metadata->alignment & (metadata->alignment - 1)) != 0 || + metadata->sourcePrefixSize > SHIM_WRAPPER_SIZE || + (metadata->sourcePrefixSize != 0 && + metadata->sourcePrefixSize != SHIM_WRAPPER_SIZE) || + (entry->flags & SHIM_ENTRY_FLAG_COPY) != 0 && + (entry->loadAddress == 0 || metadata->copySizeMax == 0 || + entry->size > metadata->copySizeMax)) { + printf("Error: Input image has invalid recovery metadata.\n"); + return false; + } + if (index == 0) { + if (entry->size > primarySlot) + return false; + continue; + } + if (ranges_overlap((size_t)entry->offset, (size_t)entry->size, + primarySlot, (size_t)recovery->shimSize)) { + printf("Error: Manifest entry overlaps the attached Shim.\n"); + return false; + } + for (size_t previous = 1; previous < index; previous++) { + const ShimManifestEntry *other = &manifest->entries[previous]; + if (ranges_overlap((size_t)entry->offset, (size_t)entry->size, + (size_t)other->offset, + (size_t)other->size)) { + printf("Error: Manifest entries overlap.\n"); + return false; + } + } + if ((size_t)entry->offset < shimEnd) + return false; + } + return true; +} + +static bool restore_files(const PackedImage *packed, const char *directory) { + char path[SHIM_PATH_SIZE * 2]; + const ShimManifest *manifest = packed->manifest; + const ShimManifestRecoveryHeader *recovery = packed->recovery; + size_t entryCount = manifest->header.entryCount; + size_t primarySlot = (size_t)shim_read_le64( + packed->buffer + packed->headerOffset + 0x30); + + FileContent base = {0}; + base.fileSize = (size_t)manifest->entries[0].size; + base.fileBuffer = malloc(base.fileSize); + if (base.fileBuffer == NULL) + return false; + memcpy(base.fileBuffer, packed->buffer + packed->headerOffset, + base.fileSize); + memcpy(base.fileBuffer, recovery->baseHeader, + sizeof(recovery->baseHeader)); + { + const ShimManifestRecoveryEntry *baseMetadata = + &packed->recoveryEntries[0]; + size_t prefixSize = baseMetadata->sourcePrefixSize; + if (prefixSize > SHIM_WRAPPER_SIZE || + !join_path(path, sizeof(path), directory, "BaseImage") || + prefixSize + base.fileSize > SIZE_MAX) { + free(base.fileBuffer); + return false; + } + size_t restoredSize = prefixSize + base.fileSize; + uint8_t *restored = malloc(restoredSize); + if (restored == NULL) { + free(base.fileBuffer); + return false; + } + memcpy(restored, baseMetadata->sourcePrefix, prefixSize); + memcpy(restored + prefixSize, base.fileBuffer, base.fileSize); + bool result = write_buffer(path, restored, restoredSize); + free(restored); + free(base.fileBuffer); + if (!result) + return false; + } + + if (!join_path(path, sizeof(path), directory, "Shim.bin") || + !write_buffer(path, packed->buffer + packed->headerOffset + primarySlot, + (size_t)recovery->shimSize)) + return false; + + for (size_t index = 1; index < entryCount; index++) { + const ShimManifestEntry *entry = &manifest->entries[index]; + const ShimManifestRecoveryEntry *metadata = + &packed->recoveryEntries[index]; + const char *typeName = shim_type_name(entry->type); + char name[64]; + snprintf(name, sizeof(name), "Image-%02zu-%s", index, typeName); + if (!join_path(path, sizeof(path), directory, name)) + return false; + size_t prefixSize = metadata->sourcePrefixSize; + if (prefixSize > SHIM_WRAPPER_SIZE || + prefixSize + (size_t)entry->size < prefixSize) + return false; + size_t outputSize = prefixSize + (size_t)entry->size; + uint8_t *output = malloc(outputSize); + if (output == NULL) + return false; + memcpy(output, metadata->sourcePrefix, prefixSize); + memcpy(output + prefixSize, + packed->buffer + packed->headerOffset + entry->offset, + (size_t)entry->size); + bool result = write_buffer(path, output, outputSize); + free(output); + if (!result) + return false; + } + return true; +} + +static bool write_config(const PackedImage *packed, const char *directory) { + char path[SHIM_PATH_SIZE * 2]; + if (!join_path(path, sizeof(path), directory, "config.cfg")) + return false; + FILE *file = fopen(path, "w"); + if (file == NULL) + return false; + + const ShimManifest *manifest = packed->manifest; + fprintf(file, "[Pack]\nShim=Shim.bin\nOutput=RepackedKernel\n"); + fprintf(file, "Default=Image-%02u\nTimeout=%u\n\n", + manifest->header.defaultEntry, manifest->header.timeoutMs); + for (size_t index = 0; index < manifest->header.entryCount; index++) { + const ShimManifestEntry *entry = &manifest->entries[index]; + const ShimManifestRecoveryEntry *metadata = + &packed->recoveryEntries[index]; + const char *typeName = shim_type_name(entry->type); + fprintf(file, "[Image-%02zu]\nName=%s\n", index, entry->name); + if (index == 0) + fprintf(file, "Path=BaseImage\n"); + else { + char name[64]; + snprintf(name, sizeof(name), "Image-%02zu-%s", index, typeName); + fprintf(file, "Path=%s\n", name); + } + fprintf(file, "Type=%s\nBaseImage=%s\nAlign=0x%llx\n", + typeName, index == 0 ? "true" : "false", + (unsigned long long)metadata->alignment); + if (entry->flags & SHIM_ENTRY_FLAG_COPY) + fprintf(file, "CopyTo=0x%llx\nCopySizeMax=0x%llx\n", + (unsigned long long)entry->loadAddress, + (unsigned long long)metadata->copySizeMax); + if (shim_is_executable_type(entry->type)) + fprintf(file, "EntryOffset=0x%llx\n", + (unsigned long long)entry->entryOffset); + fprintf(file, "\n"); + } + fprintf(file, "[Manifest]\nType=Manifest\n"); + return fclose(file) == 0; +} + +int main(int argc, char **argv) { + if (argc != 3) { + printf("Usage: UnpackTool \n"); + return EINVAL; + } + PackedImage packed = {0}; + if (!load_packed(argv[1], &packed)) { + free(packed.buffer); + return EINVAL; + } + if (!make_directory(argv[2]) || !restore_files(&packed, argv[2]) || + !write_config(&packed, argv[2])) { + printf("Error: Failed to write unpacked files.\n"); + free(packed.buffer); + return EINVAL; + } + printf("Kernel image unpacked successfully.\n"); + free(packed.buffer); + return 0; +} \ No newline at end of file diff --git a/utils.c b/src/utils.c similarity index 53% rename from utils.c rename to src/utils.c index 00b89a9..130fd0c 100644 --- a/utils.c +++ b/src/utils.c @@ -9,14 +9,20 @@ * */ size_t get_file_size(FileContent *fileContent) { - FILE *pFile = fopen(fileContent->filePath, "r"); + FILE *pFile = fopen(fileContent->filePath, "rb"); if (pFile == NULL) { printf("Error: %s not found\n", fileContent->filePath); return 0; } - fseek(pFile, 0, SEEK_END); - size_t len = ftell(pFile); + if (fseek(pFile, 0, SEEK_END) != 0) { + fclose(pFile); + return 0; + } + long position = ftell(pFile); fclose(pFile); + if (position <= 0) + return 0; + size_t len = (size_t)position; fileContent->fileSize = len; return len; } @@ -33,8 +39,11 @@ uint8_t *read_file_content(FileContent *fileContent) { FILE *pFile = fopen(fileContent->filePath, "rb"); if (pFile == NULL) return NULL; - fread(fileContent->fileBuffer, fileContent->fileSize, 1, pFile); - fclose(pFile); + size_t readSize = fread(fileContent->fileBuffer, 1, + fileContent->fileSize, pFile); + int closeStatus = fclose(pFile); + if (readSize != fileContent->fileSize || closeStatus != 0) + return NULL; return fileContent->fileBuffer; } @@ -49,38 +58,9 @@ int write_file_content(pFileContent fileContent) { FILE *pFile = fopen(fileContent->filePath, "wb"); if (pFile == NULL) return -EBADF; - fwrite(fileContent->fileBuffer, fileContent->fileSize, 1, pFile); - fclose(pFile); - return 0; -} - -/** - * Parse given config. - * - * @param fileContent - * @param config Config info read from config file - * @retval -EINVAL Give File not found. - * - */ -int parse_config(FileContent *fileContent, pConfig config) { - // Check file size - if (!get_file_size(fileContent)) - return -EINVAL; - - // Open config file - FILE *pConfigFile = fopen(fileContent->filePath, "r"); - char key[256]; - uint32_t value = 0; - - // Parse - while (fscanf(pConfigFile, "%[^=]=%x\n", key, &value) != EOF) { - if (strcmp(key, "StackBase") == 0) { - config->StackBase = value; - } else if (strcmp(key, "StackSize") == 0) { - config->StackSize = value; - } - } - - fclose(pConfigFile); - return 0; + size_t written = fwrite(fileContent->fileBuffer, 1, + fileContent->fileSize, pFile); + int closeStatus = fclose(pFile); + return written == fileContent->fileSize && closeStatus == 0 + ? 0 : -EBADF; } diff --git a/utils.h b/utils.h deleted file mode 100644 index 0748513..0000000 --- a/utils.h +++ /dev/null @@ -1,47 +0,0 @@ -/** @file - * DualBoot Kernel Patcher Header File. - * - * Copyright (c) 2021-2025 The DuoWoa authors. All rights reserved. - * MIT License - * - */ - -#ifndef LINUX_KERNEL_PATCHER_PATCHER_H -#define LINUX_KERNEL_PATCHER_PATCHER_H - -#include -#include -#include -#include -#include - -// -// Config info read from config file and set to kernel. -// -typedef struct { - uint64_t StackBase; - uint64_t StackSize; -} Config, *pConfig; - -// -// Store some file information and file buffer. -// -typedef struct { - uint8_t *fileBuffer; - size_t fileSize; - const char *filePath; -} FileContent, *pFileContent; - -size_t get_file_size(FileContent *fileContent); - -uint8_t *read_file_content(FileContent *fileContent); - -int write_file_content(pFileContent fileContent); - -int parse_config(FileContent *fileContent, pConfig config); - -uint8_t *PatchKernel(pFileContent kernel, pFileContent uefi, pFileContent shellCode, - pFileContent patchedKernel, pConfig config); - -uint8_t *Remove(pFileContent patchedKernel, pFileContent outputKernel); -#endif //LINUX_KERNEL_PATCHER_PATCHER_H