-
-
Save DavidBuchanan314/c6b97add51b97e4c3ee95dc890f9e3c8 to your computer and use it in GitHub Desktop.
""" | |
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]) |
@pnc @DavidBuchanan314 here is some more info on how I'm trying to get it working:
$ docker run --rm -it -v $(pwd):/test menci/archlinuxarm:latest bash
$ pacman -Sy --noconfirm nspr curl python3
$ LD_PRELOAD=/test/libwidevinecdm.so ls
ls: symbol lookup error: /test/libwidevinecdm.so: undefined symbol: __aarch64_ldadd4_acq_rel
$ # ^ to be expected, now let's patch the lib
$ curl -s "https://gist.githubusercontent.com/DavidBuchanan314/c6b97add51b97e4c3ee95dc890f9e3c8/raw/cac73ac13f8e41575c0c680d1ee38bf7c7533d39/widevine_fixup.py" -o widevine_fixup.py
$ python3 widevine_fixup.py /test/libwidevinecdm.so libwidevinecdm.so
inserting 0x1000 bytes at offset 0x904290
also injecting code at offset 0x904290
inserting 0x1000 bytes at offset 0x90c790
weak binding b'__aarch64_ldadd4_acq_rel'
weak binding b'__aarch64_swp4_acq_rel'
patching b'__aarch64_ldadd4_acq_rel' plt reloc to point into injected code
patching b'__aarch64_swp4_acq_rel' plt reloc to point into injected code
$ LD_PRELOAD=./libwidevinecdm.so ls
Segmentation fault
Here is the full strace:
$ strace -E LD_PRELOAD=./libwidevinecdm.so ls
execve("/usr/sbin/ls", ["ls"], 0xaaaae615d6c0 /* 9 vars */) = 0
brk(NULL) = 0xaaaac53b7000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff87a81000
openat(AT_FDCWD, "./libwidevinecdm.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\300\256R\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=9498448, ...}, AT_EMPTY_PATH) = 0
getcwd("/", 128) = 2
mmap(NULL, 22314536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff86504000
mmap(0xffff86504000, 22298152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86504000
munmap(0xffff87a48000, 15912) = 0
mmap(0xffff86e09000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x905000) = 0xffff86e09000
mmap(0xffff86e11000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x90d000) = 0xffff86e11000
mmap(0xffff86e13000, 12799528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff86e13000
close(3) = 0
faccessat(AT_FDCWD, "/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=19099, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 19099, PROT_READ, MAP_PRIVATE, 3, 0) = 0xffff87a7c000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0px\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=47072, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 176216, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff864d8000
mmap(0xffff864e0000, 110680, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff864e0000
munmap(0xffff864d8000, 32768) = 0
munmap(0xffff864fc000, 28760) = 0
mprotect(0xffff864ea000, 65536, PROT_NONE) = 0
mmap(0xffff864fa000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0xa000) = 0xffff864fa000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0p}\2\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=1673696, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 1842496, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff8631e000
mmap(0xffff86320000, 1776960, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86320000
munmap(0xffff8631e000, 8192) = 0
munmap(0xffff864d2000, 56640) = 0
mprotect(0xffff864b0000, 65536, PROT_NONE) = 0
mmap(0xffff864c0000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x190000) = 0xffff864c0000
mmap(0xffff864c6000, 48448, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff864c6000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=6128, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 135184, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff862fe000
mmap(0xffff86300000, 69648, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86300000
munmap(0xffff862fe000, 8192) = 0
munmap(0xffff86312000, 53264) = 0
mprotect(0xffff86301000, 61440, PROT_NONE) = 0
mmap(0xffff86310000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86310000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=6128, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 135184, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff862de000
mmap(0xffff862e0000, 69648, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff862e0000
munmap(0xffff862de000, 8192) = 0
munmap(0xffff862f2000, 53264) = 0
mprotect(0xffff862e1000, 61440, PROT_NONE) = 0
mmap(0xffff862f0000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff862f0000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libnspr4.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=274904, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 414992, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff8627a000
mmap(0xffff86280000, 349456, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff86280000
munmap(0xffff8627a000, 24576) = 0
munmap(0xffff862d6000, 38160) = 0
mprotect(0xffff862c0000, 65536, PROT_NONE) = 0
mmap(0xffff862d0000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x40000) = 0xffff862d0000
mmap(0xffff862d3000, 9488, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xffff862d3000
close(3) = 0
openat(AT_FDCWD, "/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0\267\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=592248, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff87a7a000
mmap(NULL, 721128, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff861cf000
mmap(0xffff861d0000, 655592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0xffff861d0000
munmap(0xffff861cf000, 4096) = 0
munmap(0xffff86271000, 57576) = 0
mprotect(0xffff86260000, 61440, PROT_NONE) = 0
mmap(0xffff8626f000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x8f000) = 0xffff8626f000
close(3) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xffff87a78000
set_tid_address(0xffff87a780f0) = 189
set_robust_list(0xffff87a78100, 24) = 0
rseq(0xffff87a787c0, 0x20, 0, 0xd428bc00) = 0
mprotect(0xffff864c0000, 16384, PROT_READ) = 0
mprotect(0xffff8626f000, 4096, PROT_READ) = 0
mprotect(0xffff862d0000, 8192, PROT_READ) = 0
mprotect(0xffff862f0000, 4096, PROT_READ) = 0
mprotect(0xffff86310000, 4096, PROT_READ) = 0
mprotect(0xffff864fa000, 4096, PROT_READ) = 0
mprotect(0xffff86e09000, 32768, PROT_READ) = 0
mprotect(0xaaaaacc53000, 8192, PROT_READ) = 0
mprotect(0xffff87a86000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0xffff87a7c000, 19099) = 0
prctl(PR_CAPBSET_READ, CAP_MAC_OVERRIDE) = 0
prctl(PR_CAPBSET_READ, 0x30 /* CAP_??? */) = -1 EINVAL (Invalid argument)
prctl(PR_CAPBSET_READ, CAP_CHECKPOINT_RESTORE) = 0
prctl(PR_CAPBSET_READ, 0x2c /* CAP_??? */) = -1 EINVAL (Invalid argument)
prctl(PR_CAPBSET_READ, 0x2a /* CAP_??? */) = -1 EINVAL (Invalid argument)
prctl(PR_CAPBSET_READ, 0x29 /* CAP_??? */) = -1 EINVAL (Invalid argument)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x6d8220} ---
+++ killed by SIGSEGV +++
Segmentation fault
And what happens if you try to LD_PRELOAD it somewhere outside of docker?
And, what does the backtrace look like if you run it under GDB? (protip: use this gdb command set env LD_PRELOAD=blah
)
Edit: are you using glibc-widevine
?
With GDB I get this:
$ gdb ls
(gdb) set env LD_PRELOAD=./libwidevinecdm.so
(gdb) run
Starting program: /usr/bin/ls
warning: Error disabling address space randomization: Operation not permitted
During startup program terminated with signal SIGSEGV, Segmentation fault.
After adding --privileged
to the container run flags, the warning goes away but I'm not able to get any more info.
- What happens when you try it outside of docker?
- What does the backtrace look like? (ideally with glibc symbols loaded)
- Are you using
glibc-widevine
?
Sorry for the confusion, I'm running this on a macOS host in Docker. So I can't really "try it outside of Docker". I have tried backtracing in gdb
(with debuginfod
and set debuginfod enabled on
) but I simply got this:
(gdb) bt
No stack.
I have now tried by installing glibc-widevine
and that worked fine! I thought the library patch made the glibc patches obsolete, but I might have misunderstood that. Hopefully the glibc patches are soon no longer needed, because that's a beast to (re)build 😄
Thanks for your support!
Greetings,
Fellow Asahi Linux user here. I had installed the widevine-aarch64 AUR package and I'm elated to report that the spotify web player is working fine! However, being the bearer of bad news, I had modified the useragent in Firefox to the one you had prescribed and yet Netflix refuses to stream content. Is there anyway we can figure out what is going wrong?
I haven't actually tested Netflix on Firefox, so my first suggestion would be to try on Chromium (with the same spoofed UA)
Yep, tried it on Chromium as well. I gives me a C7701-1003 error code and asks me to enable 'Sites can play protected content' on chrome://settings/content/protectedContent. Pretty sure its a widevine issue.
Works on my machine 🤷♂️
There seems to be a problem when running the script with the version 4.10.2257.0
of widevine.
$ python widevine_fixup.py libwidevinecdm.so new.so
It looks like you're running Asahi, or some other device with >4k page size.
We will need to weaken certain memory permissions accordingly.
weak binding b'__aarch64_ldadd4_acq_rel'
weak binding b'__aarch64_swp4_acq_rel'
patching b'__aarch64_ldadd4_acq_rel' plt reloc to point into injected code
Traceback (most recent call last):
File "/opt/WidevineCdm/firefox/gmp-widevinecdm/4.10.2257.0/widevine_fixup.py", line 299, in <module>
rela.r_addend = inject_addr
^^^^^^^^^^^^^
TypeError: 'NoneType' object cannot be interpreted as an integer
Even without the patch, Spotify and Disney+ still works; I guess Ratatouille would do
@CharlesYiu where'd you get that so from?
After installing widevine-aarch64
, I went to /opt/WidevineCdm
and tried to run the patch on the one in the chromium
and firefox
folders respectively. They all spitted out the same error except for the one in _platform_specific
, where it had this error instead:
charlesyiu@charlesyiu-mac:/opt/WidevineCdm/_platform_specific/linux_arm -$ sudo python3 patch.py libwidevinecdm.so libwidevinecdm-updated.so
It looks like you're running Asahi, or some other device with >4k page size.
We will need to weaken certain memory permissions accordingly.
Traceback (most recent call last):
File "/opt/WidevineCdm/_platform_specific/linux_arm/patch.py", line 190, in <module>
phdrs = [
^
File "/opt/WidevineCdm/_platform_specific/linux_arm/patch.py", line 191, in <listcomp>
Elf64_Phdr.from_buffer(memoryview(elf)[ehdr.e_phoff + i * ehdr.e_phentsize:])
ValueError: Buffer size too small (0 instead of at least 56 bytes)
I don't know if version 4.10.2257.0
is really the version of the widevine; I just saw the number in the path to firefox's widevine /opt/WidevineCdm/firefox/gmp-widevinecdm/4.10.2257.0/libwidevinecdm.so
I checked the manifest.json
in /opt/WidevineCdm
and it said 4.10.2557.0
btw
If you installed the widevine-aarch64
AUR package, then it already applied the patch for you, and there is no need to apply it again.
Oh okay, netflix not working may be another issue then.
If netflix isn't working, that's likely an "incorrect" user-agent value - you want to be using one that looks like aarch64 chromeos
I spoofed the useragent from your blog
Mozilla/5.0 (X11; CrOS aarch64 15236.80.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.125 Safari/537.36
It definitely looks like aarch64 chromeos but netflix still shows f7111-1331
when watching a movie.
My blog was published months ago, I'd recommend finding something more up-to-date, e.g. https://www.whatismybrowser.com/guides/the-latest-user-agent/chrome-os
^ this
Also, can you try outside of a docker container? It's possible that the DRM is detecting it and refusing to operate.
Also note that it will refuse to operate while being strace'd! (but the library should at least get loaded)