תוכנת האבטחה של היום היא תוכנת הפריצה של מחר, ותחום אבטחת מידע הוא תחום שיש בו טובים ורעים העניין איך מסתכלים על הקוד ומה הולכים לעשות איתו, במאמר זה נלמד את נושא Packet Injection שהוא נושא רחב ומעניין ,כבר יצא לי להציג בבלוק את המושג "חבילות מלוכלכות" והראתי גם כלים שעובדים בצורה הזאת כמו Scapy ו Inguma.
אז מה זה בדיוק Packet Injection?
זאת בעצם הזרקת חבילה לתוך כרטיס רשת בצורה יזומה, בעצם כתיבת חבילה מלוכלת שתצא לטייל לה ברשת עד שתמצא את היעד, דוגמה מבין רבות היא נושא ה Spoofing , יש לי אפשרות לכתוב את המקורות והיעדים של החבילה שלי כלומר אני יכול לשלוח חבילות מטעם מחשב אחר, למרות שאני לא אקבל ישירות את התשובה, אלא המחשב המקורי שרשום בחבילה אבל אני עדיין יוכל לדעת אם הפורט פתוח בשיטת ה Zombie שתיארתי בעבר בבלוג.
אז הדרך הכי פשוטה לעשות זאת דרך Linux שם יש לי גישה ישירה למחלקות ה Ethernet בניגוד ל Windows , האמת שכל הנושא הזה קצת מלוכלך מתחילתו ו Microsoft מעולם לא נתנו לך ספריות ניהול כמו שצריך בכל הקשור Packets עד Windows 7 שבו הכניסו מנגנון WFP - windows filtering platform.
רכיבים נחוצים:
Linux - במקרה שלי החלטתי להשתמש ב OpenSuse
קצת רקע:
אנחנו שולחים חבילה והחבילה שלנו מחולקת לחלקים (Headers), והיא נשלחת ישירות דרך RawSocket, אז קודם יש להכין את המידע לרשום אותו לחבילה בצורה מסויימת ואח"כ להעביר אותה ל Raw Socket.
מה זה Raw Socket?
Raw Socket זאת שיטה לשליחת חבילות מידע דרך ה Ethernet ללא התערבות של מערכת ההפעלה, מרבית ה Api של Sockets מבוססים על Berkeley sockets.
קיימות 2 דרכים כיצד להכין את החבילה שלנו, וההבדל העיקרי איך מכינים את ה Packet וכמות הזיכרון שאנו מקצים.
הדרך הראשונה אומרת קודם נבנה כל אובייקט בנפרד (Ethernet,Ip,Tcp,Data ) אח"כ נקצה את זיכרון החבילה בחישוב של הגדלים של כל האובייקטים ביחד ונשלח ל Raw Socket - שיטה שמבזבזת יותר זיכרון כי אנחנו יוצרים אובייקטים נפרדים ואח"כ מקצים שטח נוסף בגודל כל האובייקטים שלנו.
הדרך השנייה אומרת קודם תקצה את הזיכרון עבור החבילה ותתחיל למלא את שטח הזיכרון באובייקטים - פה אנחנו משתמשים בשטח זיכרון מוגדר ובו אנו מעמיסים ישירות את האובייקטים.
החבילה שלנו מחולקת ל Headers , מבנה של נתונים שאנו צריכים להעביר בחבילה על מנת שתצא מה Raw Socket, אי אפשר להגיד שהדרך הזאת טובה יותר מהשניה בסה"כ אנחנו עושים את אותו דבר רק עם הקצאות זיכרון שונות.
Ethernet Header
מכיל את נתוני ה Mac Address, ואת סוג החבילה: Arp, Ip וכו' (הרשימה המלאה).
מבנה (Struct) שמוגדר בקובץ linux/if_ether.h בגודל של 14 בתים.
Ip Header
מכיל את נתוני ה IP של החבילה, את כתובת המקור והיעד ומספר פרמטרים (לפרטים נוספים).
מבנה (Struct) שמוגדר בקובץ linux/ip.h בגודל של 6 כפול 32 בתים.
מבנה (Struct) שמוגדר בקובץ linux/ip.h בגודל של 6 כפול 32 בתים.
Tcp Header
מכיל את נתוני ה Tcp של החבילה, פורטים, דגלים ואת המידע (לפרטים נוספים).
מבנה (Struct) שמוגדר ב linux/tcp.h בגודל של 6 כפול 32 בתים, ול Data נקצה גודל משלו.
מבנה (Struct) שמוגדר ב linux/tcp.h בגודל של 6 כפול 32 בתים, ול Data נקצה גודל משלו.
ספריות מערכת שבהן נשתמש:
#include sys/socket.h
#include features.h
#include linux/if_packet.h
#include linux/if_ether.h
#include errno.h
#include sys/ioctl.h
#include net/if.h
#include net/ethernet.h
#include linux/ip.h
#include arpa/inet.h
#include string.h
string.h - string operations
הספריות ,הן חלק מספריות המערכת של Linux, הן אחראיות על הגדרת מבנים של אובייקטים שונים כמו Packet,Header, התחברות אל ממשקי ה Ethernet וטיפול בשגיאות.
עכשיו אחרי התאוריה נעבור לשלב המעשי בכל הסיפור הזה , דבר ראשון נעשה לנו פרוייקט חדש של Ansi C ב Eclipse ונעשה include לכל הספריות הרשומות למעלה אח"כ נגדיר לנו את הפוקנציות שקשורות ל Raw Socket.
int CreateRawSocket(int protocol_to_sniff)
{
int rawsock;
if((rawsock = socket(PF_PACKET, SOCK_RAW, htons(protocol_to_sniff)))== -1
{
perror("Error creating raw socket: ");
exit(-1);
}
return rawsock;
}
יצירת ה Socket ע"י יצוג הפרוטוקול ETH_P_ALL כלומר כל סוג של Packet, בדרך כלל מגדירים זאת כך אבל אפשר להגדיר את סוג החבילה באופן ספציפי כמו ETH_P_IP , ETH_P_ARP וכו'.
int BindRawSocketToInterface(char *device, int rawsock, int protocol)
{
struct sockaddr_ll sll;
struct ifreq ifr;
bzero(&sll, sizeof(sll));
bzero(&ifr, sizeof(ifr));
strncpy((char *)ifr.ifr_name, device, IFNAMSIZ);
if((ioctl(rawsock, SIOCGIFINDEX, &ifr)) == -1)
{
printf("Error getting Interface index !\n");
exit(-1);
}
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifr.ifr_ifindex;
sll.sll_protocol = htons(protocol);
if((bind(rawsock, (struct sockaddr *)&sll, sizeof(sll)))== -1)
{
perror("Error binding raw socket to interface\n");
exit(-1);
}
return 1;
}
בפונקציה הזאת אנחנו מחברים את ה Raw Socket ל Interface שלנו, אנחנו שולחים את שם ה Device במקרה שלי Eth0 ואח"כ את ה Raw שיצרנו עם הפרוטוקול שהעברנו (ETH_P_ALL).
int SendRawPacket(int rawsock, unsigned char *pkt, int pkt_len)
{
int sent= 0;
if((sent = write(rawsock, pkt, pkt_len)) != pkt_len)
{
printf("Could only send %d bytes of packet of length %d\n", sent, pkt_len);
return 0;
}
return 1;
}
רושמים את החבילה ל Socket, מעבירים את ה Socket, מצביע (Pointer) עבור החבילה ואת גודל החבילה.
עכשיו יש לנו תקשורת עם ה Raw Socket שלנו, מה שנשאר לנו הוא לבנות את החבילה:
struct ethhdr* CreateEthernetHeader(char *src_mac, char *dst_mac, int protocol)
{
struct ethhdr *ethernet_header;
ethernet_header = (struct ethhdr *)malloc(sizeof(struct ethhdr));
memcpy(ethernet_header->h_source, (void *)ether_aton(src_mac), 6);
memcpy(ethernet_header->h_dest, (void *)ether_aton(dst_mac), 6);
ethernet_header->h_proto = htons(protocol);
return (ethernet_header);
}
יוצרים מבנה (Struct) מסוג ethhdr ורושמים אליו את ה Mac Address של המקור והיעד, בנוסף אנחנו מוסיפים את הפרוטוקול שהוא ETHERTYPE_IP אך יש גם פרוטוקלים נוספים (לרשימה המלאה).
struct iphdr *CreateIPHeader()
{
struct iphdr *ip_header;
ip_header = (struct iphdr *)malloc(sizeof(struct iphdr));
ip_header->version = 4;
ip_header->ihl = (sizeof(struct iphdr))/4 ;
ip_header->tos = 0;
ip_header->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + DATA_SIZE);
ip_header->id = htons(111);
ip_header->frag_off = 0;
ip_header->ttl = 111;
ip_header->protocol = IPPROTO_TCP;
ip_header->check = 0;
ip_header->saddr = inet_addr(SRC_IP);
ip_header->daddr = inet_addr(DST_IP);
ip_header->check = ComputeChecksum((unsigned char *)ip_header, ip_header->ihl*4);
return (ip_header);
}
}
יוצרים מבנה (Struct) מסוג Iphdr , מקצים זיכרון בגודל Iphdr רושמים לתוכו את הפרמטרים: כתובת מקור ויעד, גרסאת ה IP, גודל ה Header, פרוטוקול במקרה שלנו TCP, את ה TTL במקרה שלנו 111 וכו', אנחנו שולחים את ה Header לפונקצית ComputeCheckSum שבודקת אם יש תקלה ב Iphdr שלנו על מנת שלא נשלח מידע שהוא corruption ולסיום מחזרים את ה Iphdr בחזרה.
יוצרים מבנה (Struct) מסוג tcphdr , מקצים זיכרון בגודל tcphdr רושמים לתוכו פרמטרים: פורט יעד,פורט מקור,דגל וכו', אנחנו שולחים אותו גם לבדיקה אבל שונה מהבדיקה של ה CheckSum שעשינו ל Iphdr פה הבדיקה של ה CheckSum יותר מורכבת ועליה אני אפרט:
מבנה (Struct) שנוצר עבור בדיקת ה CheckSum של ה Tcpheader, האובייקט מכיל נתונים מה Iphdr ובעצם נשתל בזיכרון לפני Tcphdr ואחרי ה Iphdr, החישוב של ה Checksum הוא גודל ה tcphdr + data + pseudohdr ,התוצאה נרשמת ב Checksum של ה tcphdr ומשחררים את ה pseudo header ,כאשר החבילה תגיע ליעד המכונה המקבלת תפתח את החבילה ותבנה לעצמה pseudo header ותבדוק אם הוא שווה לגודל של ה checksum שמופיע ב tcphdr.
הדוגמה הבאה מראה שליחה של Packet מכתובת שלא קיימת עם Mac Address מזוייפים.
struct tcphdr *CreateTcpHeader()
{
struct tcphdr *tcp_header;
tcp_header = (struct tcphdr *)malloc(sizeof(struct tcphdr));
tcp_header->source = htons(SRC_PORT);
tcp_header->dest = htons(DST_PORT);
tcp_header->dest = htons(DST_PORT);
tcp_header->seq = htonl(111);
tcp_header->ack_seq = htonl(111);
tcp_header->res1 = 0;
tcp_header->doff = (sizeof(struct tcphdr))/4;
tcp_header->syn = 1;
tcp_header->window = htons(100);
tcp_header->check = 0;
tcp_header->urg_ptr = 0;
return (tcp_header);
}
יוצרים מבנה (Struct) מסוג tcphdr , מקצים זיכרון בגודל tcphdr רושמים לתוכו פרמטרים: פורט יעד,פורט מקור,דגל וכו', אנחנו שולחים אותו גם לבדיקה אבל שונה מהבדיקה של ה CheckSum שעשינו ל Iphdr פה הבדיקה של ה CheckSum יותר מורכבת ועליה אני אפרט:
Pseudo Header
CreatePseudoHeaderAndComputeTcpChecksum(struct tcphdr *tcp_header, struct iphdr *ip_header, unsigned char *data)
{
/*The TCP Checksum is calculated over the PseudoHeader + TCP header +Data*/
/* Find the size of the TCP Header + Data */
int segment_len = ntohs(ip_header->tot_len) - ip_header->ihl*4;
/* Total length over which TCP checksum will be computed */
int header_len = sizeof(PseudoHeader) + segment_len;
/* Allocate the memory */
unsigned char *hdr = (unsigned char *)malloc(header_len);
/* Fill in the pseudo header first */
PseudoHeader *pseudo_header = (PseudoHeader *)hdr;
pseudo_header->source_ip = ip_header->saddr;
pseudo_header->dest_ip = ip_header->daddr;
pseudo_header->reserved = 0;
pseudo_header->protocol = ip_header->protocol;
pseudo_header->tcp_length = htons(segment_len);
/* copy TCP */
memcpy((hdr + sizeof(PseudoHeader)), (void *)tcp_header, tcp_header->doff*4);
/* copy the Data */
memcpy((hdr + sizeof(PseudoHeader) + tcp_header->doff*4), data, DATA_SIZE);
/* Calculate the Checksum */
tcp_header->check = ComputeChecksum(hdr, header_len);
/* Free the PseudoHeader */
free(hdr);
return ;
}
הדוגמה הבאה מראה שליחה של Packet מכתובת שלא קיימת עם Mac Address מזוייפים.
קוד להורדה
בהצלחה...
אין תגובות:
הוסף רשומת תגובה