diff --git a/arm/raspi/libwidevinecdm0_4.10.2252.0+3_arm64.deb b/arm/raspi/libwidevinecdm.so similarity index 56% rename from arm/raspi/libwidevinecdm0_4.10.2252.0+3_arm64.deb rename to arm/raspi/libwidevinecdm.so index 27c9c96e..1b3eb0cf 100644 Binary files a/arm/raspi/libwidevinecdm0_4.10.2252.0+3_arm64.deb and b/arm/raspi/libwidevinecdm.so differ diff --git a/arm/raspi/netflix.patch b/arm/raspi/netflix.patch new file mode 100644 index 00000000..4f8f3cbd --- /dev/null +++ b/arm/raspi/netflix.patch @@ -0,0 +1,20 @@ +--- a/src/content/common/user_agent.cc ++++ b/src/content/common/user_agent.cc +@@ -252,7 +252,7 @@ std::string GetOSVersion(IncludeAndroidB + "%s%s", android_version_str.c_str(), + android_info_str.c_str() + #else +- "" ++ "13597.84.0" + #endif + ); + return os_version; +@@ -287,7 +287,7 @@ std::string BuildOSCpuInfoFromOSVersionA + base::StringAppendF(&os_cpu, + #if BUILDFLAG(IS_MAC) + "%s Mac OS X %s", cpu_type.c_str(), os_version.c_str() +-#elif BUILDFLAG(IS_CHROMEOS) ++#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_POSIX) + "CrOS " + "%s %s", + cpu_type.c_str(), // e.g. i686 diff --git a/arm/raspi/widevine-other-locations.patch b/arm/raspi/widevine-other-locations.patch new file mode 100644 index 00000000..178a3a5a --- /dev/null +++ b/arm/raspi/widevine-other-locations.patch @@ -0,0 +1,44 @@ +--- a/src/chrome/common/chrome_paths.cc ++++ b/src/chrome/common/chrome_paths.cc +@@ -316,6 +316,10 @@ bool PathProvider(int key, base::FilePat + + #if BUILDFLAG(ENABLE_WIDEVINE) + case chrome::DIR_BUNDLED_WIDEVINE_CDM: ++ cur = base::FilePath(FILE_PATH_LITERAL("/opt")); ++ cur = cur.Append(kWidevineCdmBaseDirectory); ++ if (base::PathExists(cur)) ++ break; + if (!GetComponentDirectory(&cur)) + return false; + cur = cur.AppendASCII(kWidevineCdmBaseDirectory); +--- a/src/chrome/common/media/cdm_registration.cc ++++ b/src/chrome/common/media/cdm_registration.cc +@@ -56,9 +56,7 @@ namespace { + using Robustness = content::CdmInfo::Robustness; + + #if BUILDFLAG(ENABLE_WIDEVINE) +-#if (BUILDFLAG(BUNDLE_WIDEVINE_CDM) || \ +- BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && \ +- (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) ++#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + // Create a CdmInfo for a Widevine CDM, using |version|, |cdm_library_path|, and + // |capability|. + std::unique_ptr CreateWidevineCdmInfo( +@@ -100,7 +98,7 @@ std::unique_ptr Create + // BUILDFLAG(ENABLE_WIDEVINE_CDM_COMPONENT)) && (BUILDFLAG(IS_LINUX) || + // BUILDFLAG(IS_CHROMEOS)) + +-#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) && \ ++#if BUILDFLAG(ENABLE_WIDEVINE) && \ + (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) + // On Linux/ChromeOS we have to preload the CDM since it uses the zygote + // sandbox. On Windows and Mac, the bundled CDM is handled by the component +@@ -181,7 +179,7 @@ void AddSoftwareSecureWidevine(std::vect + // case both versions will be the same and point to the same directory, so + // it doesn't matter which one is loaded. + content::CdmInfo* bundled_widevine = nullptr; +-#if BUILDFLAG(BUNDLE_WIDEVINE_CDM) ++#if BUILDFLAG(ENABLE_WIDEVINE) + bundled_widevine = GetBundledWidevine(); + #endif + diff --git a/arm/raspi/widevine_fixup.py b/arm/raspi/widevine_fixup.py new file mode 100644 index 00000000..01e3869f --- /dev/null +++ b/arm/raspi/widevine_fixup.py @@ -0,0 +1,305 @@ +""" +MIT License + +Copyright (c) 2023 David Buchanan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This is widevine_fixup.py version 0.1. Find the latest version here: +https://gist.github.com/DavidBuchanan314/c6b97add51b97e4c3ee95dc890f9e3c8 +""" + +import sys + +if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} input.so output.so") + exit() + +""" +aarch64 widevine builds currently only support 4k page sizes. + +This script fixes that, by pre-padding the LOAD segments so that they meet +the alignment constraints required by the loader, and then fixing up the +relevant header offsets to keep the file valid. + +It also injects two functions that are not exported from arch's libgcc, into +the empty space at the end of the .text segment. This avoids any LD_PRELOAD +workarounds. (The injected functions are __aarch64_ldadd4_acq_rel +and __aarch64_swp4_acq_rel) + +This script is tested against aarch64 libwidevinecdm.so version 4.10.2557.0, with +BuildID b71fd3342a03fdb140e1f7a757960df219d63625 and sha256sum +42ddbedb40f079a1dd63147456c12eb1ee0e986a0cd1a8b26e57ad9d119adbb4 + +You can conveniently obtain it, and all dependencies, by installing +https://aur.archlinux.org/packages/widevine-aarch64 + +This process is fragile, and may not work as-is on future revisions of widevine. + +IMPORTANT NOTE: On systems with >4k page size (e.g. Apple Silicon devices), +Using the patched binary *significantly* weakens the security of your web browser, +in two ways. Firstly, it disables the RELRO security mitigation, and secondly it +creates a RWX mapping. + +I'm looking into ways around this, but it will require more advanced patching. + +P.S. If you want to watch Netflix, you will need to spoof a ChromeOS useragent +""" + +import ctypes + +class Elf64_Ehdr(ctypes.Structure): + _fields_ = [ + ('e_ident', ctypes.c_ubyte * 16), + ('e_type', ctypes.c_uint16), + ('e_machine', ctypes.c_uint16), + ('e_version', ctypes.c_uint32), + ('e_entry', ctypes.c_uint64), + ('e_phoff', ctypes.c_uint64), + ('e_shoff', ctypes.c_uint64), + ('e_flags', ctypes.c_uint32), + ('e_ehsize', ctypes.c_uint16), + ('e_phentsize', ctypes.c_uint16), + ('e_phnum', ctypes.c_uint16), + ('e_shentsize', ctypes.c_uint16), + ('e_shnum', ctypes.c_uint16), + ('e_shstrndx', ctypes.c_uint16), + ] + +class Elf64_Phdr(ctypes.Structure): + _fields_ = [ + ('p_type', ctypes.c_uint32), + ('p_flags', ctypes.c_uint32), + ('p_offset', ctypes.c_uint64), + ('p_vaddr', ctypes.c_uint64), + ('p_paddr', ctypes.c_uint64), + ('p_filesz', ctypes.c_uint64), + ('p_memsz', ctypes.c_uint64), + ('p_align', ctypes.c_uint64), + ] + +class P_FLAGS: + """ Flag values for the p_flags field of program headers + """ + PF_X=0x1 + PF_W=0x2 + PF_R=0x4 + PF_MASKOS=0x00FF0000 + PF_MASKPROC=0xFF000000 + +class PT: + PT_NULL=0 + PT_LOAD=1 + PT_DYNAMIC=2 + PT_INTERP=3 + PT_NOTE=4 + PT_SHLIB=5 + PT_PHDR=6 + PT_TLS=7 + PT_LOOS=0x60000000 + PT_HIOS=0x6fffffff + + PT_GNU_EH_FRAME=0x6474e550 + PT_GNU_STACK=0x6474e551 + PT_GNU_RELRO=0x6474e552 + PT_GNU_PROPERTY=0x6474e553 + +class Elf64_Shdr(ctypes.Structure): + _fields_ = [ + ('sh_name', ctypes.c_uint32), + ('sh_type', ctypes.c_uint32), + ('sh_flags', ctypes.c_uint64), + ('sh_addr', ctypes.c_uint64), + ('sh_offset', ctypes.c_uint64), + ('sh_size', ctypes.c_uint64), + ('sh_link', ctypes.c_uint32), + ('sh_info', ctypes.c_uint32), + ('sh_addralign', ctypes.c_uint64), + ('sh_entsize', ctypes.c_uint64), + ] + +class Elf64_Sym(ctypes.Structure): + _fields_ = [ + ('st_name', ctypes.c_uint32), + ('st_info', ctypes.c_ubyte), + ('st_other', ctypes.c_ubyte), + ('st_shndx', ctypes.c_uint16), + ('st_value', ctypes.c_uint64), + ('st_size', ctypes.c_uint64), + ] + +class Elf64_Dyn(ctypes.Structure): + _fields_ = [ + ('d_tag', ctypes.c_uint64), + ('d_val', ctypes.c_uint64), # union with d_ptr + ] + +class D_TAG: # XXX: this is very incomplete + DT_NULL=0 + DT_NEEDED=1 + DT_SONAME=14 + +class Elf64_Rela(ctypes.Structure): + _fields_ = [ + ('r_offset', ctypes.c_uint64), + #('r_info', ctypes.c_uint64), + ('r_type', ctypes.c_uint32), + ('r_symbol', ctypes.c_uint32), + ('r_addend', ctypes.c_int64), + ] + +import mmap +TARGET_PAGE_SIZE = 0x4000 +WEAKEN_SECURITY = mmap.PAGESIZE > 0x1000 +inject_addr = None + +if WEAKEN_SECURITY: + print("It looks like you're running Asahi, or some other device with >4k page size.") + print("We will need to weaken certain memory permissions accordingly.") + +""" +0000000000000b24 <__aarch64_ldadd4_acq_rel>: + b24: 2a0003e2 mov w2, w0 + b28: 885ffc20 ldaxr w0, [x1] + b2c: 0b020003 add w3, w0, w2 + b30: 8804fc23 stlxr w4, w3, [x1] + b34: 35ffffa4 cbnz w4, b28 <__aarch64_ldadd4_acq_rel+0x4> + b38: d65f03c0 ret + +0000000000000b3c <__aarch64_swp4_acq_rel>: + b3c: 2a0003e2 mov w2, w0 + b40: 885ffc20 ldaxr w0, [x1] + b44: 8803fc22 stlxr w3, w2, [x1] + b48: 35ffffc3 cbnz w3, b40 <__aarch64_swp4_acq_rel+0x4> + b4c: d65f03c0 ret +""" + +injected_code = bytes.fromhex("e203002a20fc5f880300020b23fc0488a4ffff35c0035fd6e203002a20fc5f8822fc0388c3ffff35c0035fd6") + + + +with open(sys.argv[1], "rb") as infile: + elf = bytearray(infile.read()) +elf_length = len(elf) +elf += bytearray(0x100000) # pre-expand the buffer by more than enough + +ehdr = Elf64_Ehdr.from_buffer(elf) + +phdrs = [ + Elf64_Phdr.from_buffer(memoryview(elf)[ehdr.e_phoff + i * ehdr.e_phentsize:]) + for i in range(ehdr.e_phnum) +] + +adjustments = [] + +def adjust_offset(x): + for index, delta in adjustments: + if x >= index: + x += delta + return x + +for phdr in phdrs: + phdr.p_offset = adjust_offset(phdr.p_offset) + if phdr.p_type == PT.PT_LOAD: + phdr.p_align = TARGET_PAGE_SIZE + delta_needed = (phdr.p_vaddr - phdr.p_offset) % phdr.p_align + if delta_needed: + print(f"inserting {hex(delta_needed)} bytes at offset {hex(phdr.p_offset)}") + if not inject_addr: + pad_bytes = injected_code + bytes(delta_needed - len(injected_code)) + inject_addr = phdr.p_offset + print(f"also injecting code at offset {hex(phdr.p_offset)}") + else: + pad_bytes = bytes(delta_needed) + elf[phdr.p_offset:] = pad_bytes + elf[phdr.p_offset:-delta_needed] + adjustments.append((phdr.p_offset, delta_needed)) + elf_length += delta_needed + phdr.p_offset += delta_needed + + if WEAKEN_SECURITY: + phdr.p_flags |= P_FLAGS.PF_X # XXX: this is a hack!!! (at the very least, we should apply it only to the mappings that need it) + + if WEAKEN_SECURITY and phdr.p_type == PT.PT_GNU_RELRO: + print("neutering relro") # XXX: relro is a security mechanism + phdr.p_type = PT.PT_NOTE + +# the section headers have moved +ehdr.e_shoff = adjust_offset(ehdr.e_shoff) + +shdrs = [ + Elf64_Shdr.from_buffer(memoryview(elf)[ehdr.e_shoff + i * ehdr.e_shentsize:]) + for i in range(ehdr.e_shnum) +] + +for shdr in shdrs: + shdr.sh_offset = adjust_offset(shdr.sh_offset) + +strtab = shdrs[ehdr.e_shstrndx] + +def resolve_string(elf, strtab, stridx, count=False): + if count: + str_start = strtab.sh_offset + for _ in range(stridx): + str_start = elf.index(b"\0", str_start) + 1 + else: + str_start = strtab.sh_offset + stridx + str_end = elf.index(b"\0", str_start) + return bytes(elf[str_start:str_end]) + +shdr_by_name = { + resolve_string(elf, strtab, shdr.sh_name): shdr + for shdr in shdrs +} + +# XXX: unfortunately this does not do anything useful! +# It doesn't hurt either, so I'm leaving it here just in case. +dynsym = shdr_by_name[b".dynsym"] +dynstr = shdr_by_name[b".dynstr"] +for i in range(0, dynsym.sh_size, dynsym.sh_entsize): + sym = Elf64_Sym.from_buffer(memoryview(elf)[dynsym.sh_offset + i:]) + name = resolve_string(elf, dynstr, sym.st_name) + if name in [b"__aarch64_ldadd4_acq_rel", b"__aarch64_swp4_acq_rel"]: + print(f"weak binding {name}") + sym.st_info = (sym.st_info & 0x0f) | (2 << 4) # STB_WEAK + +""" +dynamic = shdr_by_name[b".dynamic"] +for i in range(0, dynamic.sh_size, dynamic.sh_entsize): + dyn = Elf64_Dyn.from_buffer(memoryview(elf)[dynamic.sh_offset + i:]) + if dyn.d_tag == D_TAG.DT_SONAME: + print("hijacking SONAME tag to point to NEEDED libgcc_hide.so") + dyn.d_tag = D_TAG.DT_NEEDED + dyn.d_val = inject_addr - dynstr.sh_offset + dynstr.sh_size = (inject_addr - dynstr.sh_offset) + len(PATH_TO_INJECT) + 1 +""" + +rela_plt = shdr_by_name[b".rela.plt"] +for i in range(0, rela_plt.sh_size, rela_plt.sh_entsize): + rela = Elf64_Rela.from_buffer(memoryview(elf)[rela_plt.sh_offset + i:]) + sym = resolve_string(elf, dynstr, rela.r_symbol, count=True) + if sym in [b"__aarch64_ldadd4_acq_rel", b"__aarch64_swp4_acq_rel"]: + print(f"patching {sym} plt reloc to point into injected code") + rela.r_type = 1027 # R_AARCH64_RELATIVE + rela.r_addend = inject_addr + if sym == b"__aarch64_swp4_acq_rel": + rela.r_addend += 6*4 + +with open(sys.argv[2], "wb") as outfile: + outfile.write(memoryview(elf)[:elf_length]) +