본문 바로가기

리눅스 커널/네트워크

Kernel hooking packet capture

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/netfilter.h>
#include <linux/netfilter_arp.h>
#include <linux/skbuff.h>
#include <linux/ip.h>

struct nf_hook_ops hook;

unsigned int hook_func(void * priv, struct sk_buff * skb, const struct nf_hook_state * state)
{
        struct iphdr * ip = ip_hdr(skb);

        printk("caller: %s\n", current->comm);
        printk("ip protocol: %d\n", ip->protocol);
        printk("%pI4 -> %pI4\n", &ip->saddr, &ip->daddr);
        return NF_ACCEPT;
}

int start(void)
{
        hook.hook = hook_func;
        hook.hooknum = NF_INET_PRE_ROUTING;
        hook.pf = NFPROTO_IPV4;

        if (!nf_register_net_hook(&init_net, &hook))
                printk("netfilter register success\n");
        else
                printk("netfilter register fail\n");

        return 0;
}

void end(void)
{
        nf_unregister_net_hook(&init_net, &hook);
}

module_init(start);
module_exit(end);
MODULE_LICENSE("GPL");

module을 insmod로 적재 시 netfilter에 hooking 할 정보를 등록하게 된다.

이를 위해선 아래 구조체를 먼저 적절히 초기화 해야 한다. 

typedef unsigned int nf_hookfn(void *priv,
			       struct sk_buff *skb,
			       const struct nf_hook_state *state);
struct nf_hook_ops {
	/* User fills in from here down. */
	nf_hookfn		*hook; //hooking 시 호출 될 함수로 설정, 
	struct net_device	*dev;
	void			*priv;
	u_int8_t		pf; //protocol family, 
	unsigned int		hooknum;
	/* Hooks are ordered in ascending priority. */
	int			priority;
};

1. hook: hooking 시 호출 될 함수의 포인터를 설정한다.

2. dev: 따로 초기화 하지 않아도 되는 거 같다.

3. priv: 따로 초기화 하지 않아도 되는 거 같다.

4. pf: protocol famaily, NFPROTO로 시작하는 값을 넣어 주면 된다.

5. hooknum: enum nf_inet_hooks를 참조하면 된다.

6. priority: 같은 hook 지점에 여러 callback 함수들이 등록될 시 그 우선순위를 정한다.

              enum nf_ip_hook_priorities 를 따로 참조하면 되겠다.

//IPv4, IPv6를 대상으로 한다 
enum nf_inet_hooks {
	NF_INET_PRE_ROUTING, //모든 incoming packet들에 대한 hooking
	NF_INET_LOCAL_IN, //local host 내 incoming packet들에 대한 hooking
	NF_INET_FORWARD, //포워딩 되는 packet들에 대한 hooking
	NF_INET_LOCAL_OUT, //local host 내 outgoing packet들에 대한 hooking
	NF_INET_POST_ROUTING, //NF_INET_FORWARD hooking을 지나 포워딩 되는 패킷들에 대한 hooking
	NF_INET_NUMHOOKS
};

여기서는 주로 NF_INET_PRE_ROUTING 과 NF_INET_POST_ROUTING 정도만 기억하면 될 거 같다.

NF_INET_PRE_ROUTING 은 incoming packet에 대하여,

NF_INET_POST_ROUTING 은 outgoing packet에 대하여 hooking하도록 한다.

LOCAL이 들어 있는 것은 주로 127.0.0.1 로 보낼 때 / 받을 때 에 대한 후킹을 말한다.

enum {
	NFPROTO_UNSPEC =  0,
	NFPROTO_INET   =  1,
	NFPROTO_IPV4   =  2, //IPv4를 대상으로 함. icmp, tcp, udp... 전부 확인 해볼 수 있다.
	NFPROTO_ARP    =  3, //ARP를 대상으로 함.
	NFPROTO_NETDEV =  5,
	NFPROTO_BRIDGE =  7,
	NFPROTO_IPV6   = 10, //IPv6를 대상으로 함.
	NFPROTO_DECNET = 12,
	NFPROTO_NUMPROTO,
};

주석을 참조 하면 되겠다.

/* Responses from hook functions. */
#define NF_DROP 0 
#define NF_ACCEPT 1 
#define NF_STOLEN 2 //hooking한 함수에 의해 처리됨.
#define NF_QUEUE 3 //유저영역을 위해 패킷을 큐잉함.
#define NF_REPEAT 4 //hooking하는 함수가 다시 호출 되도록 함.

위 매크로는 hooking하는 함수가 반환해야만 하는 값 들을 보여주고 있다.

NF_DROP 은 말 그대로 해당 패킷을 drop, 폐기하는 것으로, 커널 레벨에서의 하나의 장점을 보여줄 수 있다.

NF_ACCEPT 역시 말 그대로 해당 패킷을 받아들인다는 의미로, 계속 커널 네트워크 스택을 거칠 수 있게  해준다.

 

/* ARP Hooks */
#define NF_ARP_IN	0
#define NF_ARP_OUT	1
#define NF_ARP_FORWARD	2
#define NF_ARP_NUMHOOKS	3

pf로 넘기는 값에는 arp에 대한 내용도 있다. 그 때에 hooknum은 nf_inet_hooks 와 약간 다른 점 이 있다.

IN은 상관 없지만 OUT같은 경우는 값이 차이가 나므로 위 매크로 값을 쓰면 되겠다.

 

끝으로 모듈을 rmmod로 해제 시에는 반드시 netfilter에 등록한 hooking을 해제해주어야 한다.

그렇지 않으면, 커널 패닉이 발생하게 된다.

이는 커널 코드 흐름 상 후킹 함수를 호출해야 하는데, 해당 함수가 위치한 영역이 모듈 제거로 인해 해제되었기 때문이다. 



'리눅스 커널 > 네트워크' 카테고리의 다른 글

DHCP  (0) 2021.10.24
Kernel handler packet capture  (0) 2021.10.24
Raw socket outgoing packet capture  (0) 2021.10.24
Raw socket incoming packet capture  (0) 2021.10.24
Ethernet & ARP Header  (0) 2021.10.24