Netatalk

Netatalk là gì?

Netatalk là một open-source linux deamon, cung cấp POSIX-compliant *NIX/ * BSD hệ thống có khả năng chia sẻ các tập tin với các máy tính Apple Macintonsh.

Apple Filing Protocol là giao thức mạng độc quyền của Apple để phân phối file qua mạng. Đây là sự thay thế của Apple cho các giao thức Server Message Block (SMB) và Network File System (NFS).

Wikipedia

"Netatalk is a free, open-source implementation of the Apple Filing Protocol. It allows Unix-like operating system to serve as file server for Macintosh computers. Historically (until release 3.0) Netatalk implemented the AppleTalk protocol suite, allowing Unix-like operating systems to serve also as print and time servers for Apple Macintosh computers"

Hiểu một cách đơn giản, Netatalk là một ứng dụng giúp chúng ta chia sẻ file qua mạng giữa hệ thống Ubuntu và hệ thống MacOS bằng cách cung cấp cùng giao thức AFP

Setup the environment

For Ubuntu18.04 (Linux ubuntu 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux)

  • Download source of this version

    ​​​​wget [http://prdownloads.sourceforge.net/netatalk/netatalk-3.1.11.tar.gz](http://prdownloads.sourceforge.net/netatalk/netatalk-3.1.11.tar.gz)
    
  • Extract source

    ​​​​tar -xzf netatalk-3.1.11.tar.gz
    
  • Compile and install

    ​​​​./configure \
    ​​​​--with-init-style=debian-systemd \
    ​​​​--without-libevent \
    ​​​​--with-cracklib \
    ​​​​--enable-krbV-uam \
    ​​​​--with-pam-confdir=/etc/pam.d \
    ​​​​--with-dbus-daemon=/usr/bin/dbus-daemon \
    ​​​​--with-dbus-sysconf-dir=/etc/dbus-1/system.d
    ​​​​
    ​​​​make
    ​​​​
    ​​​​sudo make install
    
  • Start service

    ​​​​# enable and start netatalk service
    ​​​​sudo systemctl enable netatalk
    ​​​​sudo systemctl start netatalk
    
  • Check open port of service

    ​​​​# check service started
    ​​​​sudo systemctl status netatalk.service
    ​​​​
    ​​​​# check open port
    ​​​​sudo ss -nap | grep 548
    

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  • Change afp.conf follow challenge gives us

    config of this challenge

    ​​​​[Global]
    ​​​​afp port = 5566
    ​​​​disconnect time = 0
    ​​​​max connections = 1000
    ​​​​sleep time = 0
    

    setup

    ​​​​sudo nano /usr/local/etc/afp.conf
    ​​​​
    ​​​​# change infomation in file configuration of service
    
  • After start netatalk service, the execute file programming will be placed /usr/local/sbin

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Debug afpd

For debugger, I used to gdb-pwndbg

Start, I attach to process afpd with highest privilege sudo

# using commandline to run gdb (because I setup env = sudo)
sudo gdb attach 103712

And then

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Code analysis

After search google, I found that bug in dsi_opensess.c link

Source code from libatalk/dsi/dsi_opensess.c

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Tại sao lại có lỗi ở đây? Nếu chúng ta có thể control được commands thì khi đó ta có thể ghi đè được các trường sau attn_quantum —> heap overflow

Vậy hàm dsi_opensession() được gọi khi nào?

Phân tích qua package struct overview

Khi một package DSI được gửi đến server, nó sẽ được parser theo một struct sau:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Trường commands sẽ có một vài giá trị mà client có thể sử dụng để gọi đến những ứng dụng cấp từ server của netatalk như bảng dưới đây:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Vậy nên, nếu chúng ta request một dsi package với commands==4 thì khi đó trên server netatalk sẽ đi vào hàm dsi_opensession.c

Phân tích qua source code của netatalk

Chương trình sử dụng multi-proess handling khi client request, mỗi client khi request đến chương trình sẽ fork() một sub-process.

Với mỗi client khi request tới server, yêu cầu mở một session mới:

for (int i = 0; i < asev->used; i++) {
if (asev->fdset[i].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) {
switch (asev->data[i].fdtype) {

case LISTEN_FD:
    if ((child = dsi_start(&obj, (DSI *)(asev->data[i].private), server_children))) {
        if (!(asev_add_fd(asev, child->afpch_ipc_fd, IPC_FD, child))) {
            LOG(log_error, logtype_afpd, "out of asev slots");

            /*
                * Close IPC fd here and mark it as unused
                */
            close(child->afpch_ipc_fd);
            child->afpch_ipc_fd = -1;

            /*
                * Being unfriendly here, but we really
                * want to get rid of it. The 'child'
                * handle gets cleaned up in the SIGCLD
                * handler.
                */
            kill(child->afpch_pid, SIGKILL);
        }
    }
    break;

case IPC_FD:
    child = (afp_child_t *)(asev->data[i].private);
    LOG(log_debug, logtype_afpd, "main: IPC request from child[%u]", child->afpch_pid);

    if (ipc_server_read(server_children, child->afpch_ipc_fd) != 0) {
        if (!(asev_del_fd(asev, child->afpch_ipc_fd))) {
            LOG(log_error, logtype_afpd, "child[%u]: no IPC fd");
        }
        close(child->afpch_ipc_fd);
        child->afpch_ipc_fd = -1;
    }
    break;

default:
    LOG(log_debug, logtype_afpd, "main: IPC request for unknown type");
    break;

Chương trình sẽ gọi đến hàm dsi_start để mở một sub-process

static afp_child_t *dsi_start(AFPObj *obj, DSI *dsi, server_child_t *server_children)
{
    afp_child_t *child = NULL;

    if (dsi_getsession(dsi, server_children, obj->options.tickleval, &child) != 0) {
        LOG(log_error, logtype_afpd, "dsi_start: session error: %s", strerror(errno));
        return NULL;
    }

    /* we've forked. */
    if (child == NULL) {
        configfree(obj, dsi);
        afp_over_dsi(obj); /* start a session */
        exit (0);
    }

    return child;
}
switch (pid = dsi->proto_open(dsi)) { /* in libatalk/dsi/dsi_tcp.c */
  case -1:
    /* if we fail, just return. it might work later */
    LOG(log_error, logtype_dsi, "dsi_getsess: %s", strerror(errno));
    return -1;

  case 0: /* child. mostly handled below. */
    break;

Sử dụng data request từ client, server parse theo struct qua hàm sau:

/*!
 * 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");

  if (dsi->flags & DSI_DISCONNECTED)
      return 0;

  /* 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];

  if (dsi->header.dsi_command == 0)
      return 0;

  memcpy(&dsi->header.dsi_requestID, block + 2, sizeof(dsi->header.dsi_requestID));
  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);
  memcpy(&dsi->header.dsi_len, block + 8, sizeof(dsi->header.dsi_len));

  memcpy(&dsi->header.dsi_reserved, block + 12, sizeof(dsi->header.dsi_reserved));
  dsi->clientID = ntohs(dsi->header.dsi_requestID);
  
  /* 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");
      dsi->cmdlen = dsi->header.dsi_data.dsi_doff;
  }

  if (dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen)
    return 0;

  LOG(log_debug, logtype_dsi, "dsi_stream_receive: DSI cmdlen: %zd", dsi->cmdlen);

  return block[1];
}

Hàm dsi_stream_receive sẽ đọc data truyền vào một block sau đó parse vào dsi_header , clientID , cmdlencommands

Để đảm bảo data được copy vào dsi->commands không bị overflow, chương trình sẽ lấy giá trị nhỏ hơn của dsi->header.dsi_lendsi->server_quantum . Mà giá trị define bạn đầu của servquant như sau:

#define DSI_SERVQUANT_MIN   32000       /* minimum server quantum */
#define DSI_SERVQUANT_DEF   0x100000L   /* default server quantum (1 MB) */

Vậy nên dsi_len trong pakage mà ta request đến server phải có giá trị >32000

Struct của DSI block:

#define DSI_BLOCKSIZ 16
struct dsi_block {
    uint8_t dsi_flags;       /* packet type: request or reply */
    uint8_t dsi_command;     /* command */
    uint16_t dsi_requestID;  /* request ID */
    union {
        uint32_t dsi_code;   /* error code */
        uint32_t dsi_doff;   /* data offset */
    } dsi_data;
    uint32_t dsi_len;        /* total data length */
    uint32_t dsi_reserved;   /* reserved field */
};

Struct của DSI:

/* child and parent processes might interpret a couple of these
 * differently. */
typedef struct DSI {
    struct DSI *next;             /* multiple listening addresses */
    AFPObj   *AFPobj;
    int      statuslen;
    char     status[1400];
    char     *signature;
    struct dsi_block        header;
    struct sockaddr_storage server, client;
    struct itimerval        timer;
    int      tickle;            /* tickle count */
    int      in_write;          /* in the middle of writing multiple packets,
                                   signal handlers can't write to the socket */
    int      msg_request;       /* pending message to the client */
    int      down_request;      /* pending SIGUSR1 down in 5 mn */

    uint32_t attn_quantum, datasize, server_quantum;
    uint16_t serverID, clientID;
    uint8_t  *commands; /* DSI recieve buffer */
    uint8_t  data[DSI_DATASIZ];    /* DSI reply buffer */
    size_t   datalen, cmdlen;
    off_t    read_count, write_count;
    uint32_t flags;             /* DSI flags like DSI_SLEEPING, DSI_DISCONNECTED */
    int      socket;            /* AFP session socket */
    int      serversock;        /* listening socket */

    /* DSI readahead buffer used for buffered reads in dsi_peek */
    size_t   dsireadbuf;        /* size of the DSI readahead buffer used in dsi_peek() */
    char     *buffer;           /* buffer start */
    char     *start;            /* current buffer head */
    char     *eof;              /* end of currently used buffer */
    char     *end;

#ifdef USE_ZEROCONF
    char *bonjourname;      /* server name as UTF8 maxlen MAXINSTANCENAMELEN */
    int zeroconf_registered;
#endif

    /* protocol specific open/close, send/receive
     * send/receive fill in the header and use dsi->commands.
     * write/read just write/read data */
    pid_t  (*proto_open)(struct DSI *);
    void   (*proto_close)(struct DSI *);
} DSI;

Trong hàm dsi_opensession():

/* OpenSession. set up the connection */
void dsi_opensession(DSI *dsi)
{
  uint32_t i = 0; /* this serves double duty. it must be 4-bytes long */
  int offs;

  if (setnonblock(dsi->socket, 1) < 0) {
      LOG(log_error, logtype_dsi, "dsi_opensession: setnonblock: %s", strerror(errno));
      AFP_PANIC("setnonblock error");
  }

  /* parse options */
  while (i < dsi->cmdlen) {
    switch (dsi->commands[i++]) {
    case DSIOPT_ATTNQUANT:
      memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
      dsi->attn_quantum = ntohl(dsi->attn_quantum);

    case DSIOPT_SERVQUANT: /* just ignore these */
    default:
      i += dsi->commands[i] + 1; /* forward past length tag + length */
      break;
    }
  }

Để đến được dòng code vulnerability thì dsi->command[i++] phải là DSI_ATTNQUANT

Theo define:

/* DSI session options */
#define DSIOPT_SERVQUANT 0x00   /* server request quantum */
#define DSIOPT_ATTNQUANT 0x01   /* attention quantum */
#define DSIOPT_REPLCSIZE 0x02   /* AFP replaycache size supported by the server (that's us) */

Debugging

https://raw.githubusercontent.com/lanleft/CTF_Writeups/master/9_pwnable/Netatalk/imgs/Netatalk/Untitled%208.png

Bug & fixbug

pwndbg> print *dsi
value of type `DSI' requires 67432 bytes, which is more than max-value-size

pwndbg> set max-value-size 67433
pwndbg> print *dsi

After that

tickle = 0,
  in_write = 0,
  msg_request = 0,
  down_request = 0,
  attn_quantum = 2863311530,
  datasize = 2863311530,
  server_quantum = 2863311530,
  serverID = 43690,
  clientID = 43690,
  commands = 0xdeadbeefcafebabe <error: Cannot access memory at address 0xdeadbeefcafebabe>,
  data = '\000' <repeats 65535 times>,
  datalen = 0,
  cmdlen = 26,
  read_count = 42,
  write_count = 0,
  flags = 0,
  socket = 10,
  serversock = -1,
  dsireadbuf = 12,
  buffer = 0x7fedeb238010 "",
  start = 0x7fedeb238010 "",
  eof = 0x7fedeb238010 "",
  end = 0x7fedebe38010 "",
  bonjourname = 0x0,
  zeroconf_registered = 0,
  proto_open = 0x7fedf4ff2490 <dsi_tcp_open>,
  proto_close = 0x7fedf4ff20f0 <dsi_tcp_close>
}

Thật không may, chúng ta chỉ có OOB write với địa chỉ đã biết, và file afpd còn enable PIE

https://raw.githubusercontent.com/lanleft/CTF_Writeups/master/9_pwnable/Netatalk/imgs/Netatalk/Untitled%209.png

Stuck in step found address libc :((

fork() —> memory unchange —> disable ASRL

  • Relationship between fork() with runtimeASLR

    documentation

    Ngâm cứu-ing :<

References

Some informations about Netatalk

Netatalk/Netatalk

Data Stream Interface - Wikipedia

Explained attack by author 0xddaa

Hitcon ctf 2019 pwn 371 netatalk

Out of bound write

Exploiting an 18 Year Old Bug

Others

HITCON CTF 2019 Quals

0xddaa/ctf_challenges

struct - Interpret bytes as packed binary data - Python 3.9.5 documentation

Đặt break point tại line xx trong file c

Debugging with GDB - Set Breaks