Skip to content

Instantly share code, notes, and snippets.

@DavidBuchanan314
Last active April 24, 2024 06:24
Show Gist options
  • Save DavidBuchanan314/c6b97add51b97e4c3ee95dc890f9e3c8 to your computer and use it in GitHub Desktop.
Save DavidBuchanan314/c6b97add51b97e4c3ee95dc890f9e3c8 to your computer and use it in GitHub Desktop.
Patch aarch64 widevine blobs from ChromeOS to work on non-ChromeOS linux, including platforms with 16K page size like Apple Silicon / Asahi Linux
"""
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])
@Niek
Copy link

Niek commented Mar 9, 2023

Awesome work! I'm getting a SIGTRAP on the resulting .so file though (in a Docker container):

faccessat(AT_FDCWD, "/proc/self/ns/user", F_OK) = 0
faccessat(AT_FDCWD, "/proc/self/ns/user", F_OK) = 0
faccessat(AT_FDCWD, "/proc/self/ns/pid", F_OK) = 0
faccessat(AT_FDCWD, "/proc/self/ns/user", F_OK) = 0
faccessat(AT_FDCWD, "/proc/self/ns/net", F_OK) = 0
getuid()                                = 999
getgid()                                = 0
faccessat(AT_FDCWD, "/proc/self/setgroups", F_OK) = 0
rt_sigprocmask(SIG_SETMASK, ~[RTMIN RT_1], [], 8) = 0
clone(child_stack=0xffffdda89df0, flags=CLONE_NEWUSER|CLONE_NEWPID|CLONE_NEWNET|SIGCHLD) = 2383
rt_sigprocmask(SIG_SETMASK, [], ~[KILL STOP RTMIN RT_1], 8) = 0
close(11)                               = 0
recvmsg(10, {msg_namelen=0}, 0)         = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=2382, si_uid=999, si_status=SIGSEGV, si_utime=0, si_stime=3} ---
recvmsg(10, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="", iov_len=13}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 0
--- SIGTRAP {si_signo=SIGTRAP, si_code=TRAP_BRKPT, si_pid=-465507284, si_uid=43690} ---
gettid()                                = 2369
prctl(PR_GET_DUMPABLE)                  = 1 (SUID_DUMP_USER)
prctl(PR_SET_PTRACER, 2376)             = 0
rt_sigprocmask(SIG_BLOCK, [CONT], [TRAP], 8) = 0
sendmsg(4, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="\1\0\0\0\1\0\0\0\370\334\216\225\377\377\0\0\10f(\0000\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=40}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 40
rt_sigtimedwait([CONT], {si_signo=SIGCONT, si_code=SI_TKILL, si_pid=2376, si_uid=999}, {tv_sec=5, tv_nsec=0}, 8) = 18 (SIGCONT)
rt_sigprocmask(SIG_SETMASK, [TRAP], NULL, 8) = 0
futex(0x3000286628, FUTEX_WAKE_PRIVATE, 2147483647) = 0
rt_sigaction(SIGTRAP, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, NULL, 8) = 0
getpid()                                = 2369
gettid()                                = 2369
rt_tgsigqueueinfo(2369, 2369, SIGTRAP, {si_signo=SIGTRAP, si_code=TRAP_BRKPT, si_pid=-465507284, si_uid=43690}) = 0
rt_sigreturn({mask=[]})                 = 0
--- SIGTRAP {si_signo=SIGTRAP, si_code=TRAP_BRKPT, si_pid=-465507284, si_uid=43690} ---
+++ killed by SIGTRAP +++

Any ideas why?

@pnc
Copy link

pnc commented Mar 9, 2023

@Niek Can you share the full command you ran to get that output, along with ensuring LD_DEBUG=all is set prior to the load?

@DavidBuchanan314
Copy link
Author

DavidBuchanan314 commented Mar 9, 2023

^ 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)

@Niek
Copy link

Niek commented Mar 11, 2023

@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

@DavidBuchanan314
Copy link
Author

DavidBuchanan314 commented Mar 11, 2023

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?

@Niek
Copy link

Niek commented Mar 13, 2023

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.

@DavidBuchanan314
Copy link
Author

DavidBuchanan314 commented Mar 14, 2023

  1. What happens when you try it outside of docker?
  2. What does the backtrace look like? (ideally with glibc symbols loaded)
  3. Are you using glibc-widevine?

@Niek
Copy link

Niek commented Mar 16, 2023

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!

@punidramesh
Copy link

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?

@DavidBuchanan314
Copy link
Author

I haven't actually tested Netflix on Firefox, so my first suggestion would be to try on Chromium (with the same spoofed UA)

@punidramesh
Copy link

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.

@DavidBuchanan314
Copy link
Author

Works on my machine 🤷‍♂️

@CharlesYiu
Copy link

CharlesYiu commented Aug 10, 2023

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

@DavidBuchanan314
Copy link
Author

@CharlesYiu where'd you get that so from?

@CharlesYiu
Copy link

CharlesYiu commented Aug 10, 2023

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)

@CharlesYiu
Copy link

CharlesYiu commented Aug 10, 2023

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

@DavidBuchanan314
Copy link
Author

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.

@CharlesYiu
Copy link

Oh okay, netflix not working may be another issue then.

@DavidBuchanan314
Copy link
Author

If netflix isn't working, that's likely an "incorrect" user-agent value - you want to be using one that looks like aarch64 chromeos

@CharlesYiu
Copy link

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.

@DavidBuchanan314
Copy link
Author

DavidBuchanan314 commented Aug 11, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment