编译安装
首先下载带有漏洞的源代码
https://sourceforge.net/projects/netatalk/files/netatalk/3.1.11/
安装一些依赖库(可能不全,到时根据报错安装其他的库)
sudo apt install libcrack2-dev
sudo apt install libgssapi-krb5-2
sudo apt install libgssapi3-heimdal
sudo apt install libgssapi-perl
sudo apt-get install libkrb5-dev
sudo apt-get install libtdb-dev
sudo apt-get install libevent-dev
然后编译安装
$ ./configure --with-init-style=debian-systemd --without-libevent --without-tdb --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 --with-tracker-pkgconfig-version=1.0
$ make
$ sudo make install
编译安装好后, 编辑一下配置文件
root@ubuntu:~# cat /usr/local/etc/afp.conf
[Global]
mimic model = Xserve #这个是指定让机器在你Mac系统中显示为什么的图标
log level = default:warn
log file = /var/log/afpd.log
hosts allow = 192.168.245.0/24 #允许访问的主机地址,根据需要自行修改
hostname = ubuntu #主机名,随你喜欢
uam list = uams_dhx.so uams_dhx2.so #默认认证方式 用户名密码登录 更多查看官方文档
[Homes]
basedir regex = /tmp #用户的Home目录
[NAS-FILES]
path = /tmp #数据目录
然后尝试启动服务
$ sudo systemctl enable avahi-daemon
$ sudo systemctl enable netatalk
$ sudo systemctl start avahi-daemon
$ sudo systemctl start netatalk
启动后 afpd
会监听在 548
端口,查看端口列表确认服务是否正常启动
为了调试的方便,关闭 alsr
echo 0 > /proc/sys/kernel/randomize_va_space
代码阅读笔记
为了便于理解漏洞和 poc
的构造,这里介绍下一些重点的代码逻辑。
程序使用多进程的方式处理客户端的请求,每来一个客户端就会 fork
一个子进程处理请求的数据。
利用客户端请求数据初始化结构体
首先会调用 dsi_stream_receive
把客户端的请求数据填充到 DSI
结构体中。
使用客户端的数据填充结构体的代码
/*!
* 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);
/* 确保不会溢出 dsi->commands */
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];
}
代码逻辑主要是填充 header
的一些字段,然后 拷贝 header
后面的数据到 dsi->commands
。
其中 header
的结构如下
#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 */
};
header
中比较重要的字段有:
dsi_command
表示需要执行的动作
dsi_len
表示 header
后面数据的大小, 这个值会和 dsi->server_quantum
进行比较,取两者之间较小的值作为 dsi->cmdlen
的值。
/* 确保不会溢出 dsi->commands */
dsi->cmdlen = MIN(ntohl(dsi->header.dsi_len), dsi->server_quantum);
这样做的目的是为了确保后面拷贝数据到 dsi->commands
时不会溢出。
dsi->commands
默认大小为 0x101000
pwndbg> p dsi->commands
$8 = (uint8_t *) 0x7ffff7ed4010 "