---[[ 前言 ]]--------------------------------------------
本文主要介紹幾個在UNIX系統平台上開發網絡安全工具時最常用的library。此外還提供一些如何使用這些開發庫進行網絡安全工具開發的設計架構和流程。希望能和對網絡安全工具開發有興趣的朋友共同交流,互相促進。
衆所周知,基于socket的網絡程式設計已成為當今不可替代的程式設計方法。這種程式設計思想将網絡通訊當作“檔案”描述字進行處理,對這個“網絡檔案”(即 socket,套接字/套接口)的操作從程式設計者的角度來講與普通的檔案操作(如讀、寫、打開、關閉等)大同小異,進而極大地簡化了網絡程式開發過程。
在衆多的網絡安全程式、工具和軟體中都是基于socket設計和開發的。由于在安全程式中通常需要對網絡通訊的細節(如連接配接雙方位址/端口、服務類型、傳輸控制等)進行檢查、處理或控制,象資料包截獲、資料標頭分析、資料包重寫、甚至截斷連接配接等,都幾乎在每個網絡安全程式中必須實作。為了簡化網絡安全程式的編寫過程,提高網絡安全程式的性能和健壯性,同時使代碼更易重用與移植,最好的方法就是将最常用和最繁複的過程函數,如監聽套接口的打開/關閉、資料包截獲、資料包構造/發送/接收等,封裝起來,以API library的方式提供給開發人員使用。
---[[ C開發庫簡介 ]]-------------------------------------
在Unix系統平台上的網絡安全工具開發中,目前最為流行的C API library有libnet、libpcap、libnids和libicmp等。它們分别從不同層次和角度提供了不同的功能函數。使網絡開發人員能夠忽略網絡底層細節的實作,進而專注于程式本身具體功能的設計與開發。其中,
* libnet提供的接口函數主要實作和封裝了資料包的構造和發送過程。
* libpcap提供的接口函數主要實作和封裝了與資料包截獲有關的過程。
* libnids提供的接口函數主要實作了開發網絡入侵監測系統所必須的一些結構架構。
* libicmp等相對較為簡單,它封裝的是ICMP資料包的主要處理過程(構造、發送、接收等)。
利用這些C函數庫的接口,網絡安全工具開發人員可以很友善地編寫出具有結構化強、健壯性好、可移植性高等特點的程式,如scanner、sniffer、firewall、IDS等。
---[[ libnet ]]------------------------------------------
libnet庫的最新版本為1.0.0,它一共約7600行C源代碼,33個源程式檔案,12個C頭檔案,50餘個自定義函數,提供的接口函數包含15種資料包生成器和兩種資料包發送器(IP層和資料鍊路層)。目前隻支援IPv4,不支援IPv6。已經過測試的系統平台包括:
* OpenBSD 2.6snap, 2.5, 2.4, 2.3, 2.2 (i386)
* FreeBSD 4.0-STABLE, 3.3-STABLE, 3.2-RELEASE, 3.1-CURRENT, 3.0, 2.2 (i386)
* NetBSD 1.3.2 (i386)
* BSD/OS 3.x (i386)
* BSDi 3.0 (i386)
* Linux 2.2.x, 2.0.3x, 2.1.124 (i386, alpha) (libc: 2.4.x, glibc: 2.0.x)
* Solaris 7 (SPARC, gcc 2.7.2[13], 2.8.2), 2.6 (SPARC, gcc 2.8.2),
2.5.x (SPARC, gcc 2.7.2[13])
* IRIX 6.2
* MacOS 5.3rhapsody (powerpc)
libnet提供的接口函數按其作用可分為四類:
* 記憶體管理(配置設定和釋放)函數
* 位址解析函數
* 資料包構造函數
* 資料包發送函數
以下分别列出這些接口函數及其功能(其參數含義簡單易懂,不再解釋):
記憶體管理函數
單資料包記憶體初始化:
int libnet_init_packet(u_short packet_size, u_char **buf);
單資料包記憶體釋放:
void libnet_destroy_packet(u_char **buf);
多資料包記憶體初始化:
int libnet_init_packet_arena(struct libnet_arena **arena,
u_short packet_num, u_short packet_size);
通路多資料包記憶體中的下一個資料包:
u_char *libnet_next_packet_from_arena(struct libnet_arena **arena,
u_short packet_size);
多資料包記憶體釋放:
void libnet_destroy_packet_arena(struct libnet_arena **arena);
位址解析函數
解析主機名:
u_char *libnet_host_lookup(u_long ip, u_short use_name);
解析主機名(可重入函數):
void libnet_host_lookup_r(u_long ip, u_short use_name, u_char *buf);
域名解析:
u_long libnet_name_resolve(u_char *ip, u_short use_name);
擷取接口裝置IP位址:
u_long libnet_get_ipaddr(struct libnet_link_int *l,
const u_char *device, const u_char *ebuf);
擷取接口裝置硬體位址:
struct ether_addr *libnet_get_hwaddr(struct libnet_link_int *l,
const u_char *device,
const u_char *ebuf);
資料包構造函數
ARP協定資料包:
int libnet_build_arp(u_short hrdw, u_short prot, u_short h_len,
u_short p_len, u_short op, u_char *s_ha,
u_char *s_pa, u_char *t_ha, u_char *t_pa,
const u_char *payload, int payload_len,
u_char *packet_buf);
DNS協定資料包:
int libnet_build_dns(u_short id, u_short flags, u_short num_q,
u_short num_answ_rr, u_short num_auth_rr,
u_short num_add_rr, const u_char * payload,
int payload_len, u_char *packet_buf);
以太網協定資料包:
int libnet_build_ethernet(u_char *daddr, u_char *saddr, u_short id,
ICMP協定資料包(ICMP_ECHO / ICMP_ECHOREPLY):
int libnet_build_icmp_echo(u_char type, u_char code, u_short id,
u_short seq, const u_char *payload,
ICMP協定資料包(ICMP_MASKREQ / ICMP_MASKREPLY):
int libnet_build_icmp_mask(u_char type, u_char code, u_short id,
u_short seq, u_long mask,
ICMP協定資料包(ICMP_UNREACH):
int libnet_build_icmp_unreach(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
ICMP協定資料包(ICMP_TIMEXCEED):
int libnet_build_icmp_timeexceed(u_char type, u_char code,
ICMP協定資料包(ICMP_REDIRECT):
int libnet_build_icmp_redirect(u_char type, u_char code, u_long gateway,
ICMP協定資料包(ICMP_TSTAMP / ICMP_TSTAMPREPLY):
int libnet_build_icmp_timestamp(u_char type, u_char code, u_short id,
u_short seq, n_time otime, n_time rtime,
n_time ttime, const u_char *payload,
IGMP協定資料包:
int libnet_build_igmp(u_char type, u_char code, u_long ip,
IP協定資料包:
int libnet_build_ip(u_short len, u_char tos, u_short ip_id, u_short frag,
u_char ttl, u_char protocol, u_long saddr,
u_long daddr, const u_char *payload, int payload_len,
OSPF路由協定資料包:
int libnet_build_ospf(u_short len, u_char type, u_long router_id,
u_long area_id, u_short auth_type,
const char *payload, int payload_s, u_char *buf);
OSPF路由協定資料包(Hello):
int libnet_build_ospf_hello(u_long netmask, u_short interval,
u_char options, u_char priority,
u_int dead_interval, u_long des_router,
u_long backup, u_long neighbor,
const char *payload, int payload_s,
u_char *buf);
OSPF路由協定資料包(DataBase Description (DBD)):
int libnet_build_ospf_dbd(u_short len, u_char options, u_char type,
u_int sequence_num, const char *payload,
int payload_s, u_char *buf);
OSPF路由協定資料包(Link State Request (LSR)):
int libnet_build_ospf_lsr(u_int type, u_int ls_id, u_long adv_router,
OSPF路由協定資料包(Link State Update (LSU)):
int libnet_build_ospf_lsu(u_int num, const char *payload,
OSPF路由協定資料包(Link State Acknowledgement (LSA)):
int libnet_build_ospf_lsa(u_short age, u_char options, u_char type,
u_int ls_id, u_long adv_router,
u_int sequence_num, u_short len,
OSPF路由協定資料包(OSPF Link Sate NetworkLink State Router):
int libnet_build_ospf_lsa_net(u_long netmask, u_int router_id,
OSPF路由協定資料包(Link State Router):
int libnet_build_ospf_lsa_rtr(u_short flags, u_short num, u_int id,
u_int data, u_char type, u_char tos,
u_short metric, const char *payload,
OSPF路由協定資料包(Link State Summary):
int libnet_build_ospf_lsa_sum(u_long netmask, u_int metric, u_int tos,
OSPF路由協定資料包(Link State AS External):
int libnet_build_ospf_lsa_as(u_long netmask, u_int metric,
u_long fwd_addr, u_int tag,
RIP路由協定資料包:
int libnet_build_rip(u_char cmd, u_char ver, u_short domain,
u_short addr_fam, u_short route_tag, u_long ip,
u_long mask, u_long next_hop, u_long metric,
TCP協定資料包:
int libnet_build_tcp(u_short th_sport, u_short th_dport, u_long th_seq,
u_long th_ack, u_char th_flags, u_short th_win,
u_short th_urg, const u_char *payload,
UDP協定資料包:
int libnet_build_udp(u_short sport, u_short dport, const u_char *payload,
IP協定資料包選項:
int libnet_insert_ipo(struct ipoption *opt, u_char opt_len,
TCP協定資料包選項:
int libnet_insert_tcpo(struct tcpoption *opt, u_char opt_len,
資料包發送函數
打開raw socket:
int libnet_open_raw_sock(int protocol);
關閉raw socket:
int libnet_close_raw_sock(int socket);
選擇接口裝置:
int libnet_select_device(struct sockaddr_in *sin,
u_char **device, u_char *ebuf);
打開鍊路層接口裝置:
struct libnet_link_int *libnet_open_link_interface(char *device,
char *ebuf);
關閉鍊路層接口裝置:
int libnet_close_link_interface(struct libnet_link_int *l);
發送IP資料包:
int libnet_write_ip(int socket, u_char *packet, int packet_size);
發送鍊路層資料包:
int libnet_write_link_layer(struct libnet_link_int *l,
const u_char *device, u_char *packet,
int packet_size);
檢驗和計算:
int libnet_do_checksum(u_char *packet, int protocol, int packet_size);
相關的支援函數
随機數種子生成器:
int libnet_seed_prand();
擷取随機數:
u_long libnet_get_prand(int modulus);
16進制資料輸出:
void libnet_hex_dump(u_char * buf, int len, int swap, FILE *stream);
端口清單鍊初始化:
int libnet_plist_chain_new(struct libnet_plist_chain **plist,
char *token_list);
擷取端口清單鍊的下一項(端口範圍):
int libnet_plist_chain_next_pair(struct libnet_plist_chain *plist,
u_short *bport, u_short *eport);
端口清單鍊輸出顯示:
int libnet_plist_chain_dump(struct libnet_plist_chain *plist);
擷取端口清單鍊:
u_char *libnet_plist_chain_dump_string(struct libnet_plist_chain *plist);
端口清單鍊記憶體釋放:
void libnet_plist_chain_free(struct libnet_plist_chain *plist);
資料常量
==================================================================================
資料標頭大小定義:
常量名 數值(位元組數)
LIBNET_ARP_H 28
LIBNET_DNS_H 12
LIBNET_ETH_H 14
LIBNET_ICMP_H 4
LIBNET_ICMP_ECHO_H 8
LIBNET_ICMP_MASK_H 12
LIBNET_ICMP_UNREACH_H 8
LIBNET_ICMP_TIMXCEED_H 8
LIBNET_ICMP_REDIRECT_H 8
LIBNET_ICMP_TS_H 20
LIBNET_IGMP_H 8
LIBNET_IP_H 20
LIBNET_RIP_H 24
LIBNET_TCP_H 20
LIBNET_UDP_H 8
資料包記憶體常量:
常量名 含義
LIBNET_PACKET TCP/UDP資料標頭 + IP資料標頭使用的記憶體
LIBNET_OPTS IP或TCP選項使用的記憶體
LIBNET_MAX_PACKET IP_MAXPACKET (65535位元組)使用的記憶體
随機數發生器常量(libnet_get_prand()函數使用):
常量名 數值
LIBNET_PRAND_MAX 65535
LIBNET_PR2 0 - 2
LIBNET_PR8 0 - 255
LIBNET_PR16 0 - 32767
LIBNET_PRu16 0 - 65535
LIBNET_PR32 0 - 2147483647
LIBNET_PRu32 0 - 4294967295
錯誤消息常量(libnet_error()函數使用):
常量名 含義
LIBNET_ERR_WARNING 警告類型消息
LIBNET_ERR_CRITICAL 緊急類型消息
LIBNET_ERR_FATAL 緻命錯誤消息
libnet_host_lookup()、libnet_host_lookup_r()和libnet_name_resolve()函數使用的常量:
LIBNET_DONT_RESOLVE 不将IP位址解析為FQDN名
LIBNET_RESOLVE 嘗試将IP位址解析為FQDN名
宏定義
宏名 功能
LIBNET_GET_ARENA_SIZE(arena) 傳回多資料包記憶體緩沖區大小(位元組數)
LIBNET_GET_ARENA_REMAINING_BYTES(arena) 傳回多資料包記憶體緩沖區剩餘空間大小(位元組數)
LIBNET_PRINT_ETH_ADDR(e) 輸出顯示ether_addr結構中的以太網位址
---[[ libnet應用執行個體 ]]----------------------------------
利用libnet函數庫開發應用程式的基本步驟非常簡單:
1、資料包記憶體初始化;
2、網絡接口初始化;
3、構造所需資料包;
4、計算資料包檢驗和;
5、發送資料包;
6、關閉網絡接口;
7、釋放資料包記憶體。
以下是四個使用了libnet接口函數編寫的資料包發送程式。在編譯前必須確定libnet庫已成功安裝。
============================ cut here ============================
/* Example 1 [raw socket api - TCP packet] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x \
`libnet-config --libs` */
#include <libnet.h>
void usage(char *);
int main(int argc, char **argv)
{
int network, /* our network interface */
packet_size, /* packet size */
c; /* misc */
u_long src_ip, dst_ip; /* ip addresses */
u_short src_prt, dst_prt; /* ports */
u_char *cp, *packet; /* misc / packet */
printf("libnet example code:\tmodule 1\n\n");
printf("packet injection interface:\traw socket\n");
printf("packet type:\t\t\tTCP [no payload]\n");
src_ip = 0;
dst_ip = 0;
src_prt = 0;
dst_prt = 0;
while((c = getopt(argc, argv, "d:s:")) != EOF)
switch (c)
/*
* We expect the input to be of the form `ip.ip.ip.ip.port`. We
* point cp to the last dot of the IP address/port string and
* then seperate them with a NULL byte. The optarg now points to
* just the IP address, and cp points to the port.
*/
case 'd':
if (!(cp = strrchr(optarg, '.')))
usage(argv[0]);
}
*cp++ = 0;
dst_prt = (u_short)atoi(cp);
if (!(dst_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
libnet_error(LIBNET_ERR_FATAL,
"Bad destination IP address: %s\n", optarg);
break;
case 's':
src_prt = (u_short)atoi(cp);
if (!(src_ip = libnet_name_resolve(optarg, LIBNET_RESOLVE)))
"Bad source IP address: %s\n", optarg);
if (!src_ip || !src_prt || !dst_ip || !dst_prt)
exit(EXIT_FAILURE);
}
* We're just going to build a TCP packet with no payload using the
* raw sockets API, so we only need memory for a TCP header and an IP
* header.
packet_size = LIBNET_IP_H + LIBNET_TCP_H;
* Step 1: Memory initialization (interchangable with step 2).
libnet_init_packet(packet_size, &packet);
if (packet == NULL)
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet failed\n");
* Step 2: Network initialization (interchangable with step 1).
network = libnet_open_raw_sock(IPPROTO_RAW);
if (network == -1)
libnet_error(LIBNET_ERR_FATAL, "Can't open network.\n");
* Step 3: Packet construction (IP header).
libnet_build_ip(LIBNET_TCP_H, /* size of the packet sans IP header */
IPTOS_LOWDELAY, /* IP tos */
242, /* IP ID */
0, /* frag stuff */
48, /* TTL */
IPPROTO_TCP, /* transport protocol */
src_ip, /* source IP */
dst_ip, /* destination IP */
NULL, /* payload (none) */
0, /* payload length */
packet); /* packet header memory */
* Step 3: Packet construction (TCP header).
libnet_build_tcp(src_prt, /* source TCP port */
dst_prt, /* destination TCP port */
0xa1d95, /* sequence number */
0x53, /* acknowledgement number */
TH_SYN, /* control flags */
1024, /* window size */
0, /* urgent pointer */
packet + LIBNET_IP_H); /* packet header memory */
* Step 4: Packet checksums (TCP header only).
if (libnet_do_checksum(packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)
libnet_error(LIBNET_ERR_FATAL, "libnet_do_checksum failed\n");
* Step 5: Packet injection.
c = libnet_write_ip(network, packet, packet_size);
if (c < packet_size)
libnet_error(LN_ERR_WARNING,
"libnet_write_ip only wrote %d bytes\n", c);
else
printf("construction and injection completed, wrote all %d bytes\n", c);
* Shut down the interface.
if (libnet_close_raw_sock(network) == -1)
"libnet_close_raw_sock couldn't close the interface");
* Free packet memory.
libnet_destroy_packet(&packet);
return (c == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
void usage(char *name)
fprintf(stderr, "usage: %s -s s_ip.s_port -d d_ip.d_port\n", name);
/* Example 2 [link layer api - ICMP_MASK] */
/* gcc -Wall `libnet-config --defines` libnet-example-x.c -o libnet-example-x `libnet-config --libs` */
u_char enet_src[6] = {0x0d, 0x0e, 0x0a, 0x0d, 0x00, 0x00};
u_char enet_dst[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
int
main(int argc, char *argv[])
int packet_size, /* size of our packet */
u_long src_ip, dst_ip; /* source ip, dest ip */
u_char *packet; /* pointer to our packet buffer */
char err_buf[LIBNET_ERRBUF_SIZE]; /* error buffer */
u_char *device; /* pointer to the device to use */
struct libnet_link_int *network; /* pointer to link interface struct */
printf("libnet example code:\tmodule 2\n\n");
printf("packet injection interface:\tlink layer\n");
printf("packet type:\t\t\tICMP net mask [no payload]\n");
device = NULL;
dst_ip = 0;
while ((c = getopt(argc, argv, "i:d:s:")) != EOF)
"Bad destination IP address: %s\n", optarg);
case 'i':
device = optarg;
default:
if (!src_ip || !dst_ip)
* Step 1: Network Initialization (interchangable with step 2).
if (device == NULL)
struct sockaddr_in sin;
* Try to locate a device.
if (libnet_select_device(&sin, &device, err_buf) == -1)
"libnet_select_device failed: %s\n", err_buf);
printf("device:\t\t\t\t%s\n", device);
if ((network = libnet_open_link_interface(device, err_buf)) == NULL)
"libnet_open_link_interface: %s\n", err_buf);
* We're going to build an ICMP packet with no payload using the
* link-layer API, so this time we need memory for a ethernet header
* as well as memory for the ICMP and IP headers.
packet_size = LIBNET_IP_H + LIBNET_ETH_H + LIBNET_ICMP_MASK_H;
* Step 2: Memory Initialization (interchangable with step 1).
if (libnet_init_packet(packet_size, &packet) == -1)
* Step 3: Packet construction (ethernet header).
libnet_build_ethernet(enet_dst,
enet_src,
ETHERTYPE_IP,
NULL,
0,
packet);
* Step 3: Packet construction (ICMP header).
libnet_build_icmp_mask(ICMP_MASKREPLY, /* type */
0, /* code */
242, /* id */
0, /* seq */
0xffffffff, /* mask */
NULL, /* payload */
0, /* payload_s */
packet + LIBNET_ETH_H + LIBNET_IP_H);
libnet_build_ip(ICMP_MASK_H,
0, /* IP tos */
0, /* Frag */
64, /* TTL */
IPPROTO_ICMP, /* Transport protocol */
src_ip, /* Source IP */
dst_ip, /* Destination IP */
NULL, /* Pointer to payload (none) */
packet + LIBNET_ETH_H); /* Packet header memory */
* Step 4: Packet checksums (ICMP header *AND* IP header).
if (libnet_do_checksum(packet + ETH_H, IPPROTO_ICMP, LIBNET_ICMP_MASK_H) == -1)
if (libnet_do_checksum(packet + ETH_H, IPPROTO_IP, LIBNET_IP_H) == -1)
c = libnet_write_link_layer(network, device, packet, packet_size);
"libnet_write_link_layer only wrote %d bytes\n", c);
if (libnet_close_link_interface(network) == -1)
"libnet_close_link_interface couldn't close the interface");
fprintf(stderr, "usage: %s [-i interface] -s s_ip -d d_ip\n", name);
/* Example 3 [raw socket api - ICMP_ECHO using an arena] */
int network, n, c, number_of_packets, packet_size;
struct libnet_arena arena, *arena_p;
u_char *packets[10];
u_long src_ip, dst_ip;
printf("libnet example code:\tmodule 3\n\n");
printf("packet type:\t\t\tICMP_ECHO [no payload] using an arena\n");
* We're just going to build an ICMP packet with no payload using the
* raw sockets API, so we only need memory for a ICMP header and an IP
packet_size = LIBNET_IP_H + LIBNET_ICMP_ECHO_H;
* Let's just build say, 10 packets.
number_of_packets = 10;
arena_p = &arena;
if (libnet_init_packet_arena(&arena_p, number_of_packets, packet_size) == -1)
libnet_error(LIBNET_ERR_FATAL, "libnet_init_packet_arena failed\n");
printf("Allocated an arena of %ld bytes..\n",
LIBNET_GET_ARENA_SIZE(arena));
libnet_error(LIBNET_ERR_FATAL, "Can't open the network.\n");
for (n = 0; n < number_of_packets; n++)
printf("%ld bytes remaining in arena\n",
LIBNET_GET_ARENA_REMAINING_BYTES(arena));
packets[n] = libnet_next_packet_from_arena(&arena_p, packet_size);
if (!packets[n])
libnet_error(LIBNET_ERR_WARNING, "Arena is empty\n");
continue;
libnet_build_ip(ICMP_ECHO_H, /* Size of the payload */
IPTOS_LOWDELAY | IPTOS_THROUGHPUT, /* IP tos */
IPPROTO_ICMP, /* transport protocol */
NULL, /* pointer to payload */
packets[n]); /* packet header memory */
libnet_build_icmp_echo(ICMP_ECHO, /* type */
5, /* seq */
packets[n] + LIBNET_IP_H); /* packet header memory */
if (libnet_do_checksum(packets[n], IPPROTO_ICMP, LIBNET_ICMP_ECHO_H) == -1)
c = libnet_write_ip(network, packets[n], packet_size);
printf("construction and injection of packet %d of %d completed, wrote all %d bytes\n",
n + 1, number_of_packets, c);
libnet_destroy_packet_arena(&arena_p);
fprintf(stderr, "usage: %s -s source_ip -d destination_ip\n ", name);
/* Example 4 [link-layer api - UDP packet using port list chaining] */
#define MAX_PAYLOAD_SIZE 1024
int main(int argc, char *argv[])
payload_size, /* size of our packet */
u_short bport, eport; /* beginning and end ports */
u_short cport; /* current port */
u_char payload[MAX_PAYLOAD_SIZE]; /* packet payload */
struct libnet_link_int *network; /* pointer to link interface struct */
struct libnet_plist_chain plist; /* plist chain */
struct libnet_plist_chain *plist_p; /* plist chain pointer */
printf("libnet example code:\tmodule 4\n\n");
printf("packet type:\t\t\tUDP [with payload] using port list chaining\n");
plist_p = NULL;
while ((c = getopt(argc, argv, "i:d:s:p:")) != EOF)
case 'p':
plist_p = &plist;
if (libnet_plist_chain_new(&plist_p, optarg) == -1)
"Could not build port list\n");
if (!src_ip || !dst_ip || !plist_p)
c = argc - optind;
if (c != 1)
memset(payload, 0, sizeof(payload));
strncpy(payload, argv[optind], strlen(argv[optind]));
* Get the payload from the user. Hrm. This might fail on a Sparc
* if byte alignment is off...
payload_size = strlen(payload);
* We're going to build a UDP packet with a payload using the
* as well as memory for the ICMP and IP headers and our payload.
packet_size = LIBNET_IP_H + LIBNET_ETH_H + LIBNET_UDP_H + payload_size;
libnet_build_ip(LIBNET_UDP_H + payload_size,
IPPROTO_UDP, /* Transport protocol */
while (libnet_plist_chain_next_pair(plist_p, &bport, &eport))
{
while (!(bport > eport) && bport != 0)
cport = bport++;
* Step 3: Packet construction (UDP header).
libnet_build_udp(242, /* source port */
cport, /* dest. port */
payload, /* payload */
payload_size, /* payload length */
if (libnet_do_checksum(packet + ETH_H, IPPROTO_UDP, LIBNET_UDP_H + payload_size) == -1)
printf("construction and injection completed, wrote all %d bytes, port %d\n",
c, cport);
fprintf(stderr, "usage: %s [-i interface] -s s_ip -d d_ip -p port list payload\n", name);
---[[ libpcap ]]------------------------------------------
libpcap的英文意思是 Packet Capturelibrary,即資料包捕獲函數庫。該庫提供的C函數接口可用于需要捕獲經過網絡接口(隻要經過該接口,目标位址不一定為本機)資料包的系統開發上。由Berkeley大學Lawrence Berkeley National Laboratory研究院的Van Jacobson、CraigLeres和Steven McCanne編寫,目前的最新版本為0.4。該函數庫支援Linux、Solaris和*BSD系統平台。
主要接口函數說明如下:
pcap_t *pcap_open_live(char *device, int snaplen,
int promisc, int to_ms, char *ebuf)
獲得用于捕獲網絡資料包的資料包捕獲描述字。device參數為指定打開
的網絡裝置名。snaplen參數定義捕獲資料的最大位元組數。promisc指定
是否将網絡接口置于混雜模式。to_ms參數指定逾時時間(毫秒)。
ebuf參數則僅在pcap_open_live()函數出錯傳回NULL時用于傳遞錯誤消
息。
pcap_t *pcap_open_offline(char *fname, char *ebuf)
打開以前儲存捕獲資料包的檔案,用于讀取。fname參數指定打開的文
件名。該檔案中的資料格式與tcpdump和tcpslice相容。"-"為标準輸
入。ebuf參數則僅在pcap_open_offline()函數出錯傳回NULL時用于傳
遞錯誤消息。
pcap_dumper_t *pcap_dump_open(pcap_t *p, char *fname)
打開用于儲存捕獲資料包的檔案,用于寫入。fname參數為"-"時表示
标準輸出。出錯時傳回NULL。p參數為調用pcap_open_offline()或
pcap_open_live()函數後傳回的pcap結構指針。fname參數指定打開
的檔案名。如果傳回NULL,則可調用pcap_geterr()函數擷取錯誤消
char *pcap_lookupdev(char *errbuf)
用于傳回可被pcap_open_live()或pcap_lookupnet()函數調用的網絡
裝置名指針。如果函數出錯,則傳回NULL,同時errbuf中存放相關的
錯誤消息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
bpf_u_int32 *maskp, char *errbuf)
獲得指定網絡裝置的網絡号和掩碼。netp參數和maskp參數都是
bpf_u_int32指針。如果函數出錯,則傳回-1,同時errbuf中存放相
關的錯誤消息。
int pcap_dispatch(pcap_t *p, int cnt,
pcap_handler callback, u_char *user)
捕獲并處理資料包。cnt參數指定函數傳回前所處理資料包的最大值。
cnt=-1表示在一個緩沖區中處理所有的資料包。cnt=0表示處理所有
資料包,直到産生以下錯誤之一:讀取到EOF;逾時讀取。callback
參數指定一個帶有三個參數的回調函數,這三個參數為:一個從
pcap_dispatch()函數傳遞過來的u_char指針,一個pcap_pkthdr結構
的指針,和一個資料包大小的u_char指針。如果成功則傳回讀取到的
位元組數。讀取到EOF時則傳回零值。出錯時則傳回-1,此時可調用
pcap_perror()或pcap_geterr()函數擷取錯誤消息。
int pcap_loop(pcap_t *p, int cnt,
功能基本與pcap_dispatch()函數相同,隻不過此函數在cnt個資料包
被處理或出現錯誤時才傳回,但讀取逾時不會傳回。而如果為
pcap_open_live()函數指定了一個非零值的逾時設定,然後調用
pcap_dispatch()函數,則當逾時發生時pcap_dispatch()函數會傳回。
cnt參數為負值時pcap_loop()函數将始終循環運作,除非出現錯誤。
void pcap_dump(u_char *user, struct pcap_pkthdr *h,
u_char *sp)
向調用pcap_dump_open()函數打開的檔案輸出一個資料包。該函數可
作為pcap_dispatch()函數的回調函數。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
char *str, int optimize, bpf_u_int32 netmask)
将str參數指定的字元串編譯到過濾程式中。fp是一個bpf_program結
構的指針,在pcap_compile()函數中被指派。optimize參數控制結果
代碼的優化。netmask參數指定本地網絡的網絡掩碼。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
指定一個過濾程式。fp參數是bpf_program結構指針,通常取自
pcap_compile()函數調用。出錯時傳回-1;成功時傳回0。
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
傳回指向下一個資料包的u_char指針。
int pcap_datalink(pcap_t *p)
傳回資料鍊路層類型,例如DLT_EN10MB。
int pcap_snapshot(pcap_t *p)
傳回pcap_open_live被調用後的snapshot參數值。
int pcap_is_swapped(pcap_t *p)
傳回目前系統主機位元組與被打開檔案的位元組順序是否不同。
int pcap_major_version(pcap_t *p)
傳回寫入被打開檔案所使用的pcap函數的主版本号。
int pcap_minor_version(pcap_t *p)
傳回寫入被打開檔案所使用的pcap函數的輔版本号。
int pcap_stats(pcap_t *p, struct pcap_stat *ps)
向pcap_stat結構指派。成功時傳回0。這些數值包括了從開始
捕獲資料以來至今共捕獲到的資料包統計。如果出錯或不支援
資料包統計,則傳回-1,且可調用pcap_perror()或
pcap_geterr()函數來擷取錯誤消息。
FILE *pcap_file(pcap_t *p)
傳回被打開檔案的檔案名。
int pcap_fileno(pcap_t *p)
傳回被打開檔案的檔案描述字号碼。
void pcap_perror(pcap_t *p, char *prefix)
在标準輸出裝置上顯示最後一個pcap庫錯誤消息。以prefix參
數指定的字元串為消息頭。
char *pcap_geterr(pcap_t *p)
傳回最後一個pcap庫錯誤消息。
char *pcap_strerror(int error)
如果strerror()函數不可用,則可調用pcap_strerror函數替代。
void pcap_close(pcap_t *p)
關閉p參數相應的檔案,并釋放資源。
void pcap_dump_close(pcap_dumper_t *p)
關閉相應的被打開檔案。
<<< 待續 >>>
<<< 續前 >>>
---[[ libnids ]]------------------------------------------
一、簡介
libnids的英文意思是 Network Intrusion Detect System library,即網絡入侵監測系統函數庫。它是在前面介紹的兩種C函數接口庫libnet和libpcap的基礎上開發的,封裝了開發NIDS所需的許多通用型函數。linids提供的接口函數監視流經本地的所有網絡通信,檢查資料包等。除此之外,還具有重組TCP資料段、處理IP分片包和監測TCP端口掃描的功能。利用libnids接口函數庫,NIDS開發者不需要再編寫底層的網絡處理代碼,隻需專注于NIDS本身功能的實作即可。
libnids支援Linux、Solaris和*BSD系統平台,目前最新版本為1.13。
二、IP分片資料包
為了使libnids能接收所有的IP資料包(包括分片包、畸形包等),程式員需要定義如下的回調函數:
void ip_frag_func(struct ip * a_packet)
在調用nids_init()函數初始化後,使用nids的函數進行注冊:
nids_register_ip_frag(ip_frag_func);
這樣回調函數ip_frag_func會在适當的時候由libnids調用,參數a_packet指針将指向接收到的資料報。
類似地,如果僅接收目标主機會接受的資料包(如非碎片包、重組包或頭部校驗正确的資料包等),需要定義如下回調函數:
void ip_func(struct ip * a_packet)
然後注冊:
nids_register_ip(ip_func);
三、TCP資料流重組
要接收TCP流在交換的資料,必須定義如下回調函數:
void tcp_callback(struct tcp_stream * ns, void ** param)
tcp_stream結構提供了一個TCP連接配接的所有資訊。例如,它包含了用戶端與伺服器端的half_stream結構。下文會對該結構的字段進行解釋。
tcp_stream結構有一個名為nids_state的字段。此字段的數值将決定tcp_callback的操作。
(a) ns->nids_state==NIDS_JUST_EST時,ns表示一個剛剛建立的連接配接。
tcp_callback可以據此決定是否對該連接配接的後續資料進行檢查。如
需要檢查,tcp_callback回調函數将通知libnids它希望接收哪些
資料(如到用戶端的資料、到伺服器端的資料、到用戶端的緊急數
據或到伺服器端的緊急資料等),然後傳回。
(b) ns->nids_state==NIDS_DATA時,表示ns連接配接接收到新的資料。
half_stream結構中的緩沖區用于存放這些資料。
(c) nids_state字段為其它數值(NIDS_CLOSE、NIDS_RESET、
NIDS_TIMEOUT)時,表示該連接配接已經關閉了。tcp_callback函數應
釋放相關資源。
四、一個簡單的執行個體
下面的源代碼是一個非常簡單的程式,它将libnids捕獲的所有TCP連接配接交換的資料輸出顯示到标準輸出裝置上。
-----------------------BEGINING OF CODE--------------------------------
#include "nids.h"
#include <string.h>
#include <stdio.h>
extern char * inet_ntoa(unsigned long);
// tuple4結構包含了TCP連接配接兩端的IP位址和端口,以下函數将它們轉換為字元串
// 格式,如10.0.0.1,1024, 10.0.0.2,23
char *
adres (struct tuple4 addr)
static char buf[256];
strcpy (buf, inet_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, inet_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i", addr.dest);
return buf;
void
tcp_callback (struct tcp_stream *a_tcp, void ** this_time_not_needed)
char buf[1024];
strcpy (buf, adres (a_tcp->addr)); // we put conn params into buf
if (a_tcp->nids_state == NIDS_JUST_EST)
{
// a_tcp所定義的連接配接已經建立。此處可視程式需要添加額外
// 的判斷處理。如if (a_tcp->addr.dest != 23) return;表
// 示不處理目标端口為23的資料包。
// 本例需要處理(顯示)所有資料包,故:
a_tcp->client.collect++; // 需要處理用戶端接收的資料
a_tcp->server.collect++; // 和伺服器端接收的資料
a_tcp->server.collect_urg++; // 需要處理伺服器端接收的緊急資料
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
a_tcp->client.collect_urg++; // 需要處理用戶端接收的緊急資料
// (打開編譯選項才有效)
#endif
fprintf (stderr, "%s established\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
// TCP連接配接正常關閉
fprintf (stderr, "%s closing\n", buf);
if (a_tcp->nids_state == NIDS_RESET)
// TCP連接配接因RST資料包而關閉
fprintf (stderr, "%s reset\n", buf);
if (a_tcp->nids_state == NIDS_DATA)
// 接收到新資料,下面判斷決定是否顯示
struct half_stream *hlf;
if (a_tcp->server.count_new_urg)
{
// 伺服器端接收的緊急資料
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
if (a_tcp->client.count_new_urg)
// 用戶端接收的緊急資料
if (a_tcp->client.count_new)
{
// 用戶端接收的資料
hlf = &a_tcp->client; // 準備顯示用戶端接收的資料
strcat (buf, "(<-)"); // 訓示資料流方向
}
else
hlf = &a_tcp->server; // 準備顯示伺服器端接收的資料
strcat (buf, "(->)"); // 訓示資料流方向
fprintf(stderr,"%s",buf); // 首先輸出顯示連接配接雙方的IP位址、端口
// 和資料流方向
write(2,hlf->data,hlf->count_new); // 輸出顯示接收到的新資料
return ;
main ()
// 此處可自定義libnids的全局變量,如:
// nids_params.n_hosts=256;
if (!nids_init () )
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
// NOT REACHED
return 0;
---------------------------END OF CODE------------------------------------
五、libnids的資料結構及接口函數
libnids庫的所有資料結構及接口函數都在"nids.h"頭檔案中聲明。
struct tuple4 // TCP連接配接參數
{
unsigned short source,dest; // 用戶端和伺服器端的端口号
unsigned long saddr,daddr; // 用戶端和伺服器端的IP位址
};
struct half_stream // TCP連接配接一端的資料結構
char state; // 套接字狀态(如TCP_ESTABLISHED)
char collect; // 如果大于0,則儲存其資料到緩沖區中,否則忽略
char collect_urg; // 如果大于0,則儲存緊急資料,否則忽略
char * data; // 正常資料的緩沖區
unsigned char urgdata; // 緊急資料緩沖區
int count; // 自從連接配接建立以來儲存到"data"緩沖區的資料位元組
// 數總和
int offset; // 儲存到"data"緩沖區的首位元組資料偏移量
int count_new; // 最近一次接收到的資料位元組數;如果為0,則無數
// 到達
char count_new_urg; // 如果非0,表示有新的緊急資料到達
... // libnids庫使用的輔助字段
struct tcp_stream
struct tuple4 addr; // TCP連接配接參數(saddr, daddr, sport, dport)
char nids_state; // TCP連接配接的邏輯狀态
struct half_stream client,server; // 描述用戶端與伺服器端的資料結構
... // libnids庫使用的輔助字段
在上面的執行個體程式中,回調函數tcp_callback輸出顯示hlf->data緩沖區中的資料到标準輸出裝置上。這些資料在tcp_callback函數傳回後,由libnids自動釋放這些資料所占用的記憶體空間。同時,hlf->offset字段将增加被丢棄資料的位元組數,而新接收到的資料則存放到"data"緩沖區的起始處。
如果在其它應用中不進行如上例的操作(例如,資料處理過程至少需要N個位元組的輸入資料,而libnids隻接收到的資料位元組數count_new<N),則需要在tcp_callback函數傳回前調用如下函數:
void nids_discard(struct tcp_stream * a_tcp, int num_bytes)
此時,當回調函數tcp_callback傳回後linids将"data"緩沖區的前num_bytes位元組資料,同時計算調整offset字段的數值,并将剩餘資料移動到緩沖區的起始處。
如果始終不調用nids_discard()函數(如上面執行個體),hlf->data緩沖區中将包含hlf->count_new位元組資料。通常情況下,在hlf->data緩沖區中的資料位元組數等于hlf->count - hlf->offset。
有了nids_discard()函數,程式員就不必拷貝接收到的資料到另外的緩沖區中,hlf->data緩沖區将總是盡可能儲存足夠的資料。然後,有時會有保留資料包特定資料的需要。例如,我們希望能監測到針對wu-ftpd伺服器的"CWD"溢出攻擊,就需要跟蹤檢查ftp用戶端發送的"CWD"指令。此時就需要tcp_callback回調函數具有第二個參數了。此參數是某TCP連接配接私有資料的指針。處理過程如下:
void
tcp_callback_2 (struct tcp_stream * a_tcp, struct conn_param **ptr)
if (a_tcp->nids_state==NIDS_JUST_EST)
struct conn_param * a_conn;
if the connection is uninteresting, return;
a_conn=malloc of some data structure
init of a_conn
*ptr=a_conn // this value will be passed to tcp_callback_2 in future
// calls
increase some of "collect" fields
}
if (a_tcp->nids_state==NIDS_DATA)
struct conn_param *current_conn_param=*ptr;
using current_conn_param and the newly received data from the net
we search for attack signatures, possibly modyfying
current_conn_param
return ;
...
nids_register_tcp和nids_register_ip*函數可被任意次調用。在同一個TCP連接配接中使用兩種不同的回調函數是允許的。
libnids庫定義了一個全局變量結構nids_params,其聲明如下:
struct nids_prm
int n_tcp_streams; // 存放tcp_stream結構的hash表大小。
// 預設值:1024
int n_hosts; // 存放IP分片資訊的hash表大小
// 預設值:256
char * device; // libnids監聽的接口裝置名
// 預設值 == NULL,即由pcap_lookupdev函數确定
int sk_buff_size; // (Linux核心)sk_buff結構大小
// 預設值:168
int dev_addon; // sk_buff為網絡接口保留的位元組數
// 如果dev_addon==-1,則由nids_init函數确定
// 預設值:-1
void (*syslog)(); // 日志函數指針
int syslog_level; // 如果nids_params.syslog==nids_syslog,則此字段值
// 将确定日志等級loglevel
// 預設值:LOG_ALERT
int scan_num_hosts;// 存放端口掃描資訊的hash表大小。
// 如果為0,則關閉端口掃描監測功能。
int scan_num_ports;// 來自同一IP位址所掃描的TCP端口數上限
// 預設值:10
int scan_delay; // 在兩次端口掃描中的間隔時間上限(毫秒)
// 預設值:3000
void (*no_mem)(); // 記憶體不足時被調用,此時應終止目前程序
int (*ip_filter)(struct ip*); // 當接收到一個IP資料包時調用。如傳回值
// 非零,則處理該資料包,否則忽略。
// 預設為(nids_ip_filter)且總傳回1
char *pcap_filter; // 傳遞給pcap過濾器的字元串。
// 預設值:NULL
} nids_params;
nids_params的syslog字段預設時指向nids_syslog函數,聲明如下:
void nids_syslog (int type, int errnum, struct ip *iph, void *data);
nids_params.syslog函數用于記錄異常情況,如端口掃描企圖,無效TCP頭标志等。該字段應指向自定義的日志處理函數。nids_syslog()僅作為一個例子。nids_syslog()函數向系統守護服務syslogd發送日志消息。
使用nids_run有一個缺陷:應用程式将完全由資料包驅動(運作)。有時需要在沒有資料包到達時也能處理一些任務,則作為nids_run()函數的替代,程式員可使用如下函數:
int nids_next()
此函數将調用pcap_next()函數(而不是pcap_loop()函數)。(詳細資料請參閱《網絡安全工具開發函數庫介紹之二 ——libpcap》。) nids_next()函數成功時傳回1,出錯時傳回0,且nids_errbuf緩沖區存放相應錯誤消息。
典型地,當使用nids_next()函數時,應用程式調用I/O複用函數select()阻塞,監聽套接字fd在“讀”描述字集合fd_set中設定。該套接字可通過如下函數獲得:
int nids_getfd()
成功時傳回一個檔案描述字,出錯時傳回-1,且nids_errbuf緩沖區存放相應錯誤消息。
---[[ libnids應用執行個體 ]]----------------------------------
1、nids_next()函數的應用
/*
This is an example how one can use nids_getfd() and nids_next() functions.
You can replace printall.c's function main with this file.
*/
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int
// here we can alter libnids params, for instance:
int fd;
int time = 0;
fd_set rset;
struct timeval tv;
if (!nids_init ())
fd = nids_getfd ();
for (;;)
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO (&rset);
FD_SET (fd, &rset);
// add any other fd we need to take care of
if (select (fd + 1, &rset, 0, 0, &tv))
if (FD_ISSET(fd,&rset) // need to test it if there are other
// fd in rset
if (!nids_next ()) break;
fprintf (stderr, "%i ", time++);
2、Simple sniffer
See the file COPYING for license details.
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <arpa/inet.h>
#include <fcntl.h>
#define LOG_MAX 100
#define SZLACZEK "\n--------------------------------------------------\n"
#define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x))
strcpy (buf, int_ntoa (addr.saddr));
strcat (buf, int_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i : ", addr.dest);
int logfd;
do_log (char *adres_txt, char *data, int ile)
write (logfd, adres_txt, strlen (adres_txt));
write (logfd, data, ile);
write (logfd, SZLACZEK, strlen (SZLACZEK));
sniff_callback (struct tcp_stream *a_tcp, void **this_time_not_needed)
int dest;
dest = a_tcp->addr.dest;
if (dest == 21 || dest == 23 || dest == 110 || dest == 143 || dest == 513)
a_tcp->server.collect++;
if (a_tcp->nids_state != NIDS_DATA)
// seems the stream is closing, log as much as possible
do_log (adres (a_tcp->addr), a_tcp->server.data,
a_tcp->server.count - a_tcp->server.offset);
if (a_tcp->server.count - a_tcp->server.offset < LOG_MAX)
// we haven't got enough data yet; keep all of it
nids_discard (a_tcp, 0);
// enough data
do_log (adres (a_tcp->addr), a_tcp->server.data, LOG_MAX);
// Now procedure sniff_callback doesn't want to see this stream anymore.
// So, we decrease all the "collect" fields we have previously increased.
// If there were other callbacks following a_tcp stream, they would still
// receive data
a_tcp->server.collect--;
logfd = open ("./logfile", O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (logfd < 0)
perror ("opening ./logfile:");
exit (1);
fprintf (stderr, "%s\n", nids_errbuf);
nids_register_tcp (sniff_callback);
3、Wu-FTPd overflow attack detector
See the file COPYING for license details.
This code attempts to detect attack against imapd (AUTHENTICATE hole) and
wuftpd (creation of deep directory). This code is to ilustrate use of libnids;
in order to improve readability, some simplifications were made, which enables
an attacker to bypass this code (note, the below routines should be improved,
not libnids)
#include <stdlib.h>
#include <syslog.h>
if we find a pattern AUTHENTICATE {an_int} in data stream sent to an imap
server, where an_int >1024, it means an buffer overflow attempt. We kill the
connection.
#define PATTERN "AUTHENTICATE {"
#define PATLEN strlen(PATTERN)
detect_imap (struct tcp_stream *a_tcp)
char numbuf[30];
int i, j, datalen, numberlen;
struct half_stream *hlf;
if (a_tcp->addr.dest == 143)
a_tcp->server.collect++;
return;
return;
hlf = &a_tcp->server;
datalen = hlf->count - hlf->offset;
if (datalen < PATLEN)
// we have too small amount of data to work on. Keep all data in buffer.
for (i = 0; i <= datalen - PATLEN; i++)
if (!memcmp (PATTERN, hlf->data + i, PATLEN)) //searching for a pattern
break;
if (i > datalen - PATLEN)
// retain PATLEN bytes in buffer
nids_discard (a_tcp, datalen - PATLEN);
for (j = i + PATLEN; j < datalen; j++) // searching for a closing '}'
if (*(hlf->data + j) == '}')
if (j > datalen)
if (datalen > 20)
//number too long, perhaps we should log it, too
numberlen = j - i - PATLEN;
memcpy (numbuf, hlf->data + i + PATLEN, numberlen); //numbuf contains
// AUTH argument
numbuf[numberlen] = 0;
if (atoi (numbuf) > 1024)
// notify admin
syslog(nids_params.syslog_level,
"Imapd exploit attempt, connection %s\n",adres(a_tcp->addr));
// kill the connection
nids_killtcp (a_tcp);
nids_discard (a_tcp, datalen - PATLEN);
return;
// auxiliary structure, needed to keep current dir of ftpd daemon
struct supp
char *currdir;
int last_newline;
};
// the below function adds "elem" string to "path" string, taking care of
// ".." and multiple '/'. If the resulting path is longer than 768,
// return value is 1, otherwise 0
add_to_path (char *path, char *elem, int len)
int plen;
char * ptr;
if (len > 768)
return 1;
if (len == 2 && elem[0] == '.' && elem[1] == '.')
ptr = rindex (path, '/');
if (ptr != path)
*ptr = 0;
else if (len > 0)
plen = strlen (path);
if (plen + len + 1 > 768)
return 1;
if (plen==1)
strncpy(path+1,elem,len);
path[1+len]=0;
else
path[plen] = '/';
strncpy (path + plen + 1, elem, len);
path[plen + 1 + len] = 0;
return 0;
do_detect_ftp (struct tcp_stream *a_tcp, struct supp **param_ptr)
struct supp *p = *param_ptr;
int index = p->last_newline + 1;
char *buf = a_tcp->server.data;
int offset = a_tcp->server.offset;
int n_bytes = a_tcp->server.count - offset;
int path_index, pi2, index2, remcaret;
index2 = index;
while (index2 - offset < n_bytes && buf[index2 - offset] != '\n')
index2++;
if (index2 - offset >= n_bytes)
break;
if (!strncasecmp (buf + index - offset, "cwd ", 4))
path_index = index + 4;
if (buf[path_index - offset] == '/')
{
strcpy (p->currdir, "/");
path_index++;
}
for (;;)
pi2 = path_index;
while (buf[pi2 - offset] != '\n' && buf[pi2 - offset] != '/')
pi2++;
if (buf[pi2-offset]=='\n' && buf[pi2-offset-1]=='\r')
remcaret=1;
else remcaret=0;
if (add_to_path (p->currdir, buf + path_index-offset, pi2 - path_index-remcaret))
{
// notify admin
syslog(nids_params.syslog_level,
"Ftpd exploit attempt, connection %s\n",adres(a_tcp->addr));
nids_killtcp (a_tcp);
return;
}
if (buf[pi2 - offset] == '\n')
break;
path_index = pi2 + 1;
index = index2 + 1;
p->last_newline = index - 1;
nids_discard (a_tcp, index - offset);
detect_ftpd (struct tcp_stream *a_tcp, struct supp **param)
if (a_tcp->addr.dest == 21)
struct supp *one_for_conn;
one_for_conn = (struct supp *) malloc (sizeof (struct supp));
one_for_conn->currdir = malloc (1024);
strcpy (one_for_conn->currdir, "/");
one_for_conn->last_newline = 0;
*param=one_for_conn;
free ((*param)->currdir);
free (*param);
do_detect_ftp (a_tcp, param);
nids_register_tcp (detect_imap);
nids_register_tcp (detect_ftpd);
libpcap使用舉例
日期:2001-01-10
我們曾經提供過<<libnet使用舉例(1-12)>>,比較詳細地介紹了封包發送程式設計。始終
沒有介紹libpcap封包捕捉程式設計的原因很多,tcpdump、snort等著名軟體包都是基于
libpcap,加上W.Richard.Stevens的<<Unix Network Programming Vol I>>第26章推
波助瀾,實在覺得沒有必要繼續介紹libpcap程式設計。更真實的原因可能是BPF、DLPI、
SOCK_PACKET三種接口程式設計已經被演練得太多太濫。
今天讨論的不是效率,而是可能的移植性要求。沒辦法,第一次使用libpcap庫,舉
例能深入到什麼地步,不知道。如果你也是第一次用這個庫,跟我來,第N次使用?
那還是忙你的去吧,别來看這篇無聊的灌水,:-P。我無聊是因為有程式要廣泛可移
植,你無聊是為什麼。
char * pcap_lookupdev ( char * errbuf );
該函數傳回一個網絡裝置接口名,類似libnet_select_device(),對于Linux就是
"eth0"一類的名字。pcap_open_live()、pcap_lookupnet()等函數将用到這個網絡設
備接口名。失敗時傳回NULL,errbuf包含了失敗原因。errbuf一般定義如下:
/usr/include/pcap.h
#define PCAP_ERRBUF_SIZE 256
char errbuf[ PCAP_ERRBUF_SIZE ];
pcap_t * pcap_open_live ( char * device, int snaplen, int promisc,
int to_ms, char * errbuf );
該函數用于擷取一個抽象的包捕捉句柄,後續很多libpcap函數将使用該句柄,類似
檔案操作函數頻繁使用檔案句柄。device指定網絡接口裝置名,比如"eth0。snaplen
指定單包最大捕捉位元組數,為了保證包捕捉不至于太低效率,snaplen盡量适中,以
恰能獲得所需協定層資料為準。promisc指定網絡接口是否進入混雜模式,注意即使
該參數為false(0),網絡接口仍然有可能因為其他原因處在混雜模式。to_ms指定毫
秒級讀逾時,man手冊上并沒有指明什麼值意味着永不逾時,測試下來的結論,0可能
代表永不逾時。如果調用失敗傳回NULL,errbuf包含失敗原因。
--------------------------------------------------------------------------
typedef struct pcap pcap_t;
pcap-int.h裡定義了struct pcap {}
struct pcap
int fd;
int snapshot;
int linktype;
int tzoff; /* timezone offset */
int offset; /* offset for proper alignment */
struct pcap_sf sf;
struct pcap_md md;
int bufsize; /* Read buffer */
u_char * buffer;
u_char * bp;
int cc;
u_char * pkt; /* Place holder for pcap_next() */
struct bpf_program fcode; /* Placeholder for filter code if bpf not in kernel. */
char errbuf[PCAP_ERRBUF_SIZE];
int pcap_lookupnet ( char * device, bpf_u_int32 * netp,
bpf_u_int32 * maskp, char * errbuf );
該函數用于擷取指定網絡接口的IP位址、子網路遮罩。不要被netp的名字所迷惑,它對
應的就是IP位址,maskp對應子網路遮罩。
typedef u_int bpf_u_int32;
顯然簡單了解成32-bit即可。如果調用失敗則傳回-1,errbuf包含失敗原因。
int pcap_compile ( pcap_t * p, struct bpf_program * fp, char * str,
int optimize, bpf_u_int32 netmask );
該函數用于解析過濾規則串,填寫bpf_program結構。str指向過濾規則串,格式參看
tcpdump的man手冊,比如:
這條過濾規則将捕捉所有攜帶SYN标志的到192.168.8.90的TCP封包。過濾規則串可以
是空串(""),表示抓取所有過路的封包。
optimize為1表示對過濾規則進行優化處理。netmask指定子網路遮罩,一般從
pcap_lookupnet()調用中擷取。傳回值小于零表示調用失敗。
這個函數可能比較難于了解,涉及的概念源自BPF,Linux系統沒有這種概念,但是
libpcap采用pcap_compile()和pcap_setfilter()結合的辦法屏蔽了各種鍊路層支援
的不同,無論是SOCK_PACKET、DLPI。曾在華中Security版上寫過一篇
<<核心包捕獲過濾機制介紹>>,參看該文加強了解。
(000) ldh [-4096]
(001) jeq #0x800 jt 2 jf 13
(002) ldb [9]
(003) jeq #0x6 jt 4 jf 13
(004) ld [16]
(005) jeq #0xc0a8085a jt 6 jf 13
(006) ldh [6]
(007) jset #0x1fff jt 13 jf 8
(008) ldxb 4*([0]&0xf)
(009) ldb [x + 13]
(010) and #0x2
(011) jeq #0x2 jt 12 jf 13
(012) ret #65535
(013) ret #0
#
/usr/include/net/bpf.h
/* Structure for BIOCSETF. */
struct bpf_program
u_int bf_len;
struct bpf_insn * bf_insns;
* The instruction data structure.
struct bpf_insn
u_short code;
u_char jt;
u_char jf;
bpf_int32 k;
* Macros for insn array initializers.
#define BPF_STMT(code, k) { (u_short)(code), 0, 0, k }
#define BPF_JUMP(code, k, jt, jf) { (u_short)(code), jt, jf, k }
int pcap_setfilter ( pcap_t * p, struct bpf_program * fp );
該函數用于設定pcap_compile()解析完畢的過濾規則,如果你足夠聰明(愚公?),完
全可以自己提供過濾規則,無須pcap_compile()介入,就象你寫
Password Sniffer For I386/FreeBSD時常做的那樣。成功傳回0,失敗傳回-1。
int pcap_dispatch ( pcap_t * p, int cnt, pcap_handler callback, u_char * user );
該函數用于捕捉封包、分發封包到預先指定好的處理函數(回調函數)。
pcap_dispatch()接收夠cnt個封包便傳回,如果cnt為-1意味着所有封包集中在一個
緩沖區中。如果cnt為0,僅當發生錯誤、讀取到EOF或者讀逾時到了(pcap_open_live
中指定)才停止捕捉封包并傳回。callback指定如下類型的回調函數,用于處理
pcap_dispatch()所捕獲的封包:
typedef void ( *pcap_handler ) ( u_char *, const struct pcap_pkthdr *, const u_char * );
pcap_dispatch()傳回捕捉到的封包個數,如果在讀取靜态檔案(以前包捕捉過程中存
儲下來的)時碰到EOF則傳回0。傳回-1表示發生錯誤,此時可以用pcap_perror()、
pcap_geterr()顯示錯誤資訊。
下面來看看那個回調函數,總共有三個參數,第一個形參來自pcap_dispatch()的第
三個形參,一般我們自己的包捕捉程式不需要提供它,總是為NULL。第二個形參指向
pcap_pkthdr結構,該結構位于真正的實體幀前面,用于消除不同鍊路層支援的差異。
最後的形參指向所捕獲封包的實體幀。
* Each packet in the dump file is prepended with this generic header.
* This gets around the problem of different headers for different
* packet interfaces.
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) */
* Structure prepended to each packet.
struct bpf_hdr
struct timeval bh_tstamp; /* time stamp */
bpf_u_int32 bh_caplen; /* length of captured portion */
bpf_u_int32 bh_datalen; /* original length of packet */
u_short bh_hdrlen; /* length of bpf header (this struct
plus alignment padding) */
* Because the structure above is not a multiple of 4 bytes, some compilers
* will insist on inserting padding; hence, sizeof(struct bpf_hdr) won't work.
* Only the kernel needs to know about it; applications use bh_hdrlen.
#ifdef KERNEL
#define SIZEOF_BPF_HDR 18
void pcap_close ( pcap_t * p );
該函數用于關閉pcap_open_live()擷取的包捕捉句柄,釋放相關資源。
void pcap_perror ( pcap_t * p, char * prefix );
第一形參來自pcap_open_live(),第二行參的作用類似perror()的形參,指定錯誤信
息的字首,與perror()一樣,結尾自動輸出一個換行。
pcap_perror( p, "pcap_compile" )的輸出類似這個效果:
pcap_compile: unknown ip proto ...
pcap_perror并不自動exit(),與perror()一樣,如果需要,應該顯式調用exit()。
介紹到這裡,已經可以寫簡單的sniffer。出于完整示範目的,提供這樣一個sample
code。請勿詢問任何關于該代碼的問題,煩了。
* File : sniffer program for I386/Linux using libpcap
* Version: 0.01 aleph
* Author : Anonymous ( Don't ask anything about this program, please. )
* Complie: gcc -O3 -o pcap pcap_sniffer.c -lpcap `libnet-config --defines --cflags` -Wall
* : strip pcap
* Usage : ./pcap -h
* Date : 2000-12-15 16:35
/*******************************************************************
* *
* Head File *
*******************************************************************/
#include <pcap.h>
#include <libnet.h> /* for LIBNET_TCP_H */
* Macro *
#define SUCCESS 0
#define FAILURE -1
typedef void Sigfunc ( int ); /* for signal handlers */
* Static Global Var *
static pcap_t * pcap_fd = NULL; /* 抽象的包捕捉句柄 */
* Function Prototype *
static void Atexit ( void ( * func ) ( void ) );
static void bpf_dump ( struct bpf_program * p, int option );
char * bpf_image ( struct bpf_insn * p, int n );
static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen );
static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet );
static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel );
static void pcap_read ( pcap_t * p );
static void sig_end ( int signo );
Sigfunc * signal ( int signo, Sigfunc * func );
static Sigfunc * Signal ( int signo, Sigfunc * func ); /* for our signal() function */
static void terminate ( void );
static void usage ( char * arg );
/*----------------------------------------------------------------------*/
static void Atexit ( void ( * func ) ( void ) )
if ( atexit( func ) != 0 )
exit( FAILURE );
} /* end of Atexit */
static void bpf_dump ( struct bpf_program * p, int option )
struct bpf_insn * insn;
int i;
int n = p->bf_len;
insn = p->bf_insns;
if ( option > 2 )
fprintf( stderr, "%d\n", n );
for ( i = 0; i < n; ++insn, ++i )
fprintf( stderr, "%u %u %u %u\n", insn->code,
insn->jt, insn->jf, insn->k );
if ( option > 1 )
fprintf( stderr, "{ 0x%x, %d, %d, 0x%08x },\n",
insn->code, insn->jt, insn->jf, insn->k );
for ( i = 0; i < n; ++insn, ++i )
puts( bpf_image( insn, i ) );
} /* end of bpf_dump */
char * bpf_image ( struct bpf_insn * p, int n )
int v;
char * fmt;
char * op;
static char image[256];
char operand[64];
v = p->k;
switch ( p->code )
default:
op = "unimp";
fmt = "0x%x";
v = p->code;
case BPF_RET|BPF_K:
op = "ret";
fmt = "#%d";
case BPF_RET|BPF_A:
fmt = "";
case BPF_LD|BPF_W|BPF_ABS:
op = "ld";
fmt = "[%d]";
case BPF_LD|BPF_H|BPF_ABS:
op = "ldh";
case BPF_LD|BPF_B|BPF_ABS:
op = "ldb";
case BPF_LD|BPF_W|BPF_LEN:
fmt = "#pktlen";
case BPF_LD|BPF_W|BPF_IND:
fmt = "[x + %d]";
case BPF_LD|BPF_H|BPF_IND:
case BPF_LD|BPF_B|BPF_IND:
case BPF_LD|BPF_IMM:
fmt = "#0x%x";
case BPF_LDX|BPF_IMM:
op = "ldx";
case BPF_LDX|BPF_MSH|BPF_B:
op = "ldxb";
fmt = "4*([%d]&0xf)";
case BPF_LD|BPF_MEM:
fmt = "M[%d]";
case BPF_LDX|BPF_MEM:
case BPF_ST:
op = "st";
case BPF_STX:
op = "stx";
case BPF_JMP|BPF_JA:
op = "ja";
fmt = "%d";
v = n + 1 + p->k;
case BPF_JMP|BPF_JGT|BPF_K:
op = "jgt";
case BPF_JMP|BPF_JGE|BPF_K:
op = "jge";
case BPF_JMP|BPF_JEQ|BPF_K:
op = "jeq";
case BPF_JMP|BPF_JSET|BPF_K:
op = "jset";
case BPF_JMP|BPF_JGT|BPF_X:
fmt = "x";
case BPF_JMP|BPF_JGE|BPF_X:
case BPF_JMP|BPF_JEQ|BPF_X:
case BPF_JMP|BPF_JSET|BPF_X:
case BPF_ALU|BPF_ADD|BPF_X:
op = "add";
case BPF_ALU|BPF_SUB|BPF_X:
op = "sub";
case BPF_ALU|BPF_MUL|BPF_X:
op = "mul";
case BPF_ALU|BPF_DIV|BPF_X:
op = "div";
case BPF_ALU|BPF_AND|BPF_X:
op = "and";
case BPF_ALU|BPF_OR|BPF_X:
op = "or";
case BPF_ALU|BPF_LSH|BPF_X:
op = "lsh";
case BPF_ALU|BPF_RSH|BPF_X:
op = "rsh";
case BPF_ALU|BPF_ADD|BPF_K:
case BPF_ALU|BPF_SUB|BPF_K:
case BPF_ALU|BPF_MUL|BPF_K:
case BPF_ALU|BPF_DIV|BPF_K:
case BPF_ALU|BPF_AND|BPF_K:
case BPF_ALU|BPF_OR|BPF_K:
case BPF_ALU|BPF_LSH|BPF_K:
case BPF_ALU|BPF_RSH|BPF_K:
case BPF_ALU|BPF_NEG:
op = "neg";
case BPF_MISC|BPF_TAX:
op = "tax";
case BPF_MISC|BPF_TXA:
op = "txa";
} /* end of switch */
( void )sprintf( operand, fmt, v );
( void )sprintf( image, ( BPF_CLASS( p->code ) == BPF_JMP && BPF_OP( p->code ) != BPF_JA ) ?
"(%03d) %-8s %-16s jt %d\tjf %d" : "(%03d) %-8s %s",
n, op, operand, n + 1 + p->jt, n + 1 + p->jf );
return image;
} /* end of bpf_image */
static void outputBinary ( const u_char * byteArray, const size_t byteArrayLen )
u_long offset;
int i, j, k;
fprintf( stderr, "byteArray [ %lu bytes ] ----> \n", ( long unsigned int )byteArrayLen );
if ( byteArrayLen <= 0 )
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
fprintf( stderr, "%08X ", ( unsigned int )offset );
for ( j = 0; j < 16; j++, i++ )
if ( j == 8 )
fprintf( stderr, "-%02X", byteArray[i] );
else
fprintf( stderr, " %02X", byteArray[i] );
fprintf( stderr, " " );
i -= 16;
/* if ( isprint( (int)byteArray[i] ) ) */
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) )
fprintf( stderr, "%c", byteArray[i] );
fprintf( stderr, "." );
fprintf( stderr, "\n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
fprintf( stderr, "%08X ", ( unsigned int )offset );
for ( j = 0 ; j < k; j++, i++ )
if ( j == 8 )
fprintf( stderr, "-%02X", byteArray[i] );
fprintf( stderr, " %02X", byteArray[i] );
i -= k;
for ( j = 16 - k; j > 0; j-- )
fprintf( stderr, " " );
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 ) )
fprintf( stderr, "%c", byteArray[i] );
fprintf( stderr, "." );
fprintf( stderr, "\n" );
} /* end of outputBinary */
static void pcap_callback ( u_char * none, const struct pcap_pkthdr * pcap_head, const u_char * packet )
outputBinary( ( u_char * )packet, ( size_t )( pcap_head->caplen ) );
} /* end of pcap_callback */
static pcap_t * pcap_init ( char * dev, char * filter, int snaplen, int timeout, int dumplevel )
pcap_t * p = NULL;
char errbuf[ PCAP_ERRBUF_SIZE ];
struct bpf_program bpf;
bpf_u_int32 ip, mask;
if ( dev == NULL )
if ( ( dev = pcap_lookupdev( errbuf ) ) == NULL )
fprintf( stderr, "%s\n", errbuf );
exit( FAILURE );
fprintf( stderr, "[ device --> %s ]\n", dev );
/* 1表示進入混雜模式 */
if ( ( p = pcap_open_live( dev, snaplen, 1, timeout, errbuf ) ) == NULL )
fprintf( stderr, "%s\n", errbuf );
if ( pcap_lookupnet( dev, &ip, &mask, errbuf ) == -1 )
/* 1表示優化過濾規則 */
if ( pcap_compile( p, &bpf, filter, 1, mask ) < 0 )
/* for example, pcap_compile: unknown ip proto ... */
pcap_perror( p, "pcap_compile" );
if ( dumplevel >= 0 )
bpf_dump( &bpf, dumplevel );
exit( SUCCESS );
else if ( pcap_setfilter( p, &bpf ) == -1 )
return( p );
} /* end of pcap_init */
static void pcap_read ( pcap_t * p )
// static u_long count = 0;
while ( 1 )
pcap_dispatch( p, 1, pcap_callback, NULL );
// fprintf( stderr, "count = %lu\n", ( long unsigned int )count );
// count++;
} /* end of while */
} /* end of pcap_read */
static void sig_end ( int signo )
fprintf( stderr, "\n\nsig_end = %d\n", signo );
exit( SUCCESS );
} /* end of sig_end */
Sigfunc * signal ( int signo, Sigfunc * func )
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
}
else
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
if ( sigaction( signo, &act, &oact ) < 0 )
return( SIG_ERR );
return( oact.sa_handler );
} /* end of signal */
static Sigfunc * Signal ( int signo, Sigfunc * func ) /* for our signal() function */
Sigfunc * sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
return( sigfunc );
} /* end of Signal */
static void terminate ( void )
if ( pcap_fd != NULL )
pcap_close( pcap_fd );
} /* end of terminate */
static void usage ( char * arg )
fprintf( stderr, " Usage: %s [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]\n", arg );
exit( FAILURE );
} /* end of usage */
int main ( int argc, char * argv[] )
char * dev = NULL;
int snaplen = LIBNET_ETH_H + LIBNET_IP_H + LIBNET_TCP_H;
int timeout = 0; /* 值為0是否表示不設定讀逾時 */
int dumplevel = -1;
int c, i;
opterr = 0; /* don't want getopt() writing to stderr */
while ( ( c = getopt( argc, argv, "d:hi:s:t:" ) ) != EOF )
switch ( c )
case 'd':
dumplevel = atoi( optarg );
break;
case 'i':
dev = optarg; /* 指定網絡接口裝置 */
case 's':
snaplen = atoi( optarg );
case 't':
timeout = atoi( optarg );
case 'h':
case '?':
usage( argv[0] );
} /* end of switch */
argc -= optind;
argv += optind;
if ( argc > 0 )
for ( i = 0; i < argc; i++ )
if ( ( strlen( filter ) + strlen( argv[i] ) ) > 256 )
fprintf( stderr, "Checking your filter.\n" );
return( FAILURE );
strcat( filter, argv[i] );
strcat( filter, " " );
fprintf( stderr, "[ filter --> %s ]\n", filter );
Atexit( terminate );
for ( i = 1; i < 9; i++ )
Signal( i, sig_end );
Signal( SIGTERM, sig_end );
pcap_fd = pcap_init( dev, filter, snaplen, timeout, dumplevel );
pcap_read( pcap_fd );
return( SUCCESS );
} /* end of main */
Usage: ./pcap [-h] [-d dumplevel] [-i interface] [-s snaplen] [-t timeout]
libpcap的好處還是很多,比如不需要為解析過濾規則耗費精力。這個程式再次示範
了很多經典Unix程式設計技巧,比如getopt()、signal()、atexit(),回調函數部分沒有
做什麼實際工作,看你自己發揮了。順便提一句,即使是個小程式,也應該保持良好
的風格,在華中看到太多不負責任的提問中的垃圾代碼,實在是有辱C語言的傳奇。