יום שבת, 1 בדצמבר 2012

Process Network Analyzer .Net


דמיינו מצב שאתם מנהלים שרת אינטרנט וההייתם רוצים לדעת כמה משתמשים יש על השרת באותו רגע, וכמובן באיזה שירותים הם משתמשים, אחת הדרכים הנפוצות לעשות זו היא בעזרת שימוש בכלים קיימים ב Windows כמו ה Task Manager ו NetStat אבל זה תהליך מסורבל ומבלבל (נעבור עליו מיד) , חשבתי לעצמי איך ניתן לכתוב כלי כזה בעצמי ממקום להתחיל להתערבב עם הכלים ולזכור כל מיני מספרים...

השיטה הקלאסית:

נפעיל את חברנו הטוב - Task Manager.


חשוב לזכור שבהגדרות ה Default של טבלת ה Task Manager לא מופיעה העמודה של ה PID - Process Identifier שהיא חשובה לנו בהמשך, על מנת להפעיל את העמודה יש ללחוץ על View -> Select Columns ולסמן את ה CheckBox.


לאחר מכן נפתח Cmd ונרשום netstat -n -a -o ונקבל את רשימת החיבורים הפעילים שבמחשב ובנוסף ה PID של ה Process שאליו קשור החיבור.


עכשיו נקשר בין ה PID שמופיע ב NetStat לבין ה PID שמופיע ב Task Manager  ונדע כמה חיבורים יש לכל Process וכבר אפשר לראות שזה ממש לא נוח וצריכים כלי שיעשה זאת בשבילנו.

Process Analyzer

בסה"כ מדובר על כלי מאוד פשוט, קיימות מחלקות מוכנות ב .Net לניהול Processes שבעזרתם נקבל את רשימת ה Processes שרצים על המערכת, החלק היותר בעייתי איך מוצאים את כל החיבורים עבור כל Process, בעזרת Invoke (קריאה) ל פונקציות ומבנים ב Api נמצא את מה שאנחנו צריכים, אבל זה קצת טריקי, קיימת בעייה לגשת מתוכנית .Net שמוגדרת Managed Code לפונקציות API של Windows שמוגדרים כ UnManaged Code, אתר Pinvoke מכיל חתימות ידועות של פונקציות ב API של Windows שמאפשר לקרוא לפונקציה ישירות מ Net.

יש 2 פונקציות מרכזיות שאותן צריך להפעיל ב API של Windows הראשונה GetExtendedTcpTable והשנייה GetExtendedUdpTable, כל אחת מהפונקציות עובדת עם מבנים קבועים לצורך הדוגמה הפונקציה GetExtendedTcpTable צריכה לקבל מבנים מסוג MIB  שמיוחסים לפרוטוקול TCP כמו MIB_TCPROW_OWNER_PID MIB_TCPROW, MIB_TCPTABLE_OWNER_PID וכו',  בסיום הפונקציה מתמלא Buffer של טבלת החיבורים הפעילים וחוזר Pointer לאותו Buffer, מסביב כל פונקציה יש מעטפת שמכילה בתוכה את כל המבנים הדרושים.

tcpTavble.cs - מבוסס על הפונקציה שרשומה ב Pinvoke.

 public class tcpTable
    {
        //sort values by declaration order in memory
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_TCPROW_OWNER_PID
        {
            public uint state;
            public uint localAddr;
            public byte localPort1;
            public byte localPort2;
            public byte localPort3;
            public byte localPort4;
            public uint remoteAddr;
            public byte remotePort1;
            public byte remotePort2;
            public byte remotePort3;
            public byte remotePort4;
            public int owningPid;
        }

        //sort values by declaration order in memory
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_TCPROW
        {
            public uint dwState;
            public uint dwLocalAddr;
            public uint dwLocalPort;
            public uint dwRemoteAddr;
            public uint dwRemotePort;

        }

        //sort values by declartion order in memory
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_TCPTABLE_OWNER_PID
        {
            public uint dwNumEntries;
            MIB_TCPROW_OWNER_PID table;
        }

        //sort values by declaration order in memory
        //enum of type for tcptable
        enum TCP_TABLE_CLASS
        {
            TCP_TABLE_BASIC_LISTENER,
            TCP_TABLE_BASIC_CONNECTIONS,
            TCP_TABLE_BASIC_ALL,
            TCP_TABLE_OWNER_PID_LISTENER,
            TCP_TABLE_OWNER_PID_CONNECTIONS,
            TCP_TABLE_OWNER_PID_ALL,
            TCP_TABLE_OWNER_MODULE_LISTENER,
            TCP_TABLE_OWNER_MODULE_CONNECTIONS,
            TCP_TABLE_OWNER_MODULE_ALL
        }

        //enum of the state of single row in the table
        public enum TCP_ROW_STATE
        {
            CLOSED,
            LISTEN,
            SENT,
            RCVD,
            ESTAB,
            FIN_WAIT1,
            FIN_WAIT2,
            CLOSE_WAIT,
            CLOSING,
            LAST_ACK,
            TIME_WAIT,
            DELETE_TCB
        }


        //expose signature, importing ip helper api
        [DllImport("iphlpapi.dll", SetLastError = true)]
        static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, TCP_TABLE_CLASS tblClass, int reserved);

        //return the MIB_TCPROW_OWNER_PID array as connection 
        public MIB_TCPROW_OWNER_PID[] GetAllTcpConnections()
        {
            MIB_TCPROW_OWNER_PID[] tTable;
            int AF_INET = 2;    // IP_v4
            int buffSize = 0;

            // what the size of the memory we need to allocate for the table
            uint ret = GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
            //set pointer to buffer
            IntPtr buffTable = Marshal.AllocHGlobal(buffSize);

            try
            {
                //getting the buffer
                ret = GetExtendedTcpTable(buffTable, ref buffSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
                if (ret != 0)
                {
                    return null;
                }

                //convert pointer to MIB_TCPTABLE_OWNER_PID pointer
                MIB_TCPTABLE_OWNER_PID tab = (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));
                IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));

                tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];
 
                //reading the row from buffer using next position pointer
                //size of MIB_TCPROW_OWNER_PID
                for (int i = 0; i < tab.dwNumEntries; i++)
                {
                    //convert pointer to MIB_TCPROW_OWNER_PID pointer
                    MIB_TCPROW_OWNER_PID tcpRow = (MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));
                    //save row in table
                    tTable[i] = tcpRow;
                    //go to the next entry.
                    rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow));
                }

            }
            finally
            {
                // clear buffer
                Marshal.FreeHGlobal(buffTable);
            }

            return tTable;
        }
    }





הפונקציה GetExtendedUdpTable אומנם לא חשופה ב Pinvoke אבל היא מאוד דומה ל GetExtendedTcpTable.

udpTable.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace processNetwork
{
    public class udpTable
    {
        //sort values by declartion order in memory
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_UDPROW_OWNER_PID
        {
            public uint LocalAddr;
            public byte localPort1;
            public byte localPort2;
            public byte localPort3;
            public byte localPort4;
            public int owningPid;
        }


        //sort values by declartion order in memory
        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_UDPTABLE_OWNER_PID
        {
            public uint dwNumEntries;
            MIB_UDPROW_OWNER_PID table;
        }



        enum UDP_TABLE_CLASS
        {
            UDP_TABLE_BASIC,
            UDP_TABLE_OWNER_PID,
            UDP_TABLE_OWNER_MODULE
        }

        //expose signature, importing ip helper api
        [DllImport("iphlpapi.dll", SetLastError = true)]
        static extern uint GetExtendedUdpTable(IntPtr pUddpTable, ref int dwOutBufLen, bool sort, int ipVersion, UDP_TABLE_CLASS tblClass, int reserved);

        //return the MIB_UDPROW_OWNER_PID array
        public MIB_UDPROW_OWNER_PID[] GetAllUdpConnections()
        {


            MIB_UDPROW_OWNER_PID[] tTable;
         
            int AF_INET = 2;    // IP_v4
            int buffSize = 0;


            // what the size of the memory we need to allocate for the table?
            uint ret = GetExtendedUdpTable(IntPtr.Zero, ref buffSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
            //set pointer to buffer
            IntPtr buffTable = Marshal.AllocHGlobal(buffSize);

            try
            {
                //getting the buffer
                ret = GetExtendedUdpTable(buffTable, ref buffSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
                if (ret != 0)
                {
                    return null;
                }


                //convert pointer to MIB_UDPTABLE_OWNER_PID pointer
                MIB_UDPTABLE_OWNER_PID tab = (MIB_UDPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_UDPTABLE_OWNER_PID));
             
                IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
         
                tTable = new MIB_UDPROW_OWNER_PID[tab.dwNumEntries];

                //reading the row from buffer using next position pointer
                //size of MIB_UDPROW_OWNER_PID     
                for (int i = 0; i < tab.dwNumEntries; i++)
                {
                    //convert pointer to MIB_UDPROW_OWNER_PID pointer
                    MIB_UDPROW_OWNER_PID udpRow = (MIB_UDPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_UDPROW_OWNER_PID));
                    //save row in table
                    tTable[i] = udpRow;
                    //go to the next entry.
                    rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(udpRow));
                }

            }
            finally
            {
                // clear buffer
                Marshal.FreeHGlobal(buffTable);
            }

            return tTable;
        }
    }

}


הטבלה שחוזרת לנו מהפונקציה צריכה לעבור מספר המרות, יש להמיר Ip Number ל Ip Address, ולהוציא את ה Port מהאובייקט.

Utils.cs

 public static class utils
 {
        //convert uint to ip
        public static string convert_uint_to_ip(uint ipnum)
        {
            string ipAddress = new IPAddress(BitConverter.GetBytes(ipnum)).ToString();
            return ipAddress;
        }

        //convert bytes to ushort -> port
        public static ushort convert_bytes_to_port(byte p0, byte p1, byte p2, byte p3)
        {
            ushort port;
            byte[] arr = new byte[4];

            //check if the system is litte endian, or big endian and sort the array
            //reading the first 2 bytes
            if (BitConverter.IsLittleEndian)
            {
                arr[0] = p3;
                arr[1] = p2;
                arr[2] = p1;
                arr[3] = p0;
                port = BitConverter.ToUInt16(arr, 2);
            }
            else
            {
                arr[0] = p0;
                arr[1] = p1;
                arr[2] = p2;
                arr[3] = p3;
                port = BitConverter.ToUInt16(arr,0);
            }


            return port;


        }
    }



GUI




הפונקציה שהכי חשובה ב Gui היא init_processStack שאוספת את המידע ממחלקות ה Api שהצגתי וממחלקת ה Process הפנימית של .Net ומחברת בניהם על פי ה Process id ,שאר הפונקציות משמשות לעבודה מול הפקדים ב Gui לכן לא ארחיב עליהם. (פירוט מלא בקוד).

        /// collecting all the processes and the active connections
        private void init_processStack()
        {
            //create table of all the tcp connections
            tcpTable tcptbl = new tcpTable();
            tcpTable.MIB_TCPROW_OWNER_PID[] tcpRow = tcptbl.GetAllTcpConnections();

            //create table of all the udp connections
            udpTable udptbl = new udpTable();
            udpTable.MIB_UDPROW_OWNER_PID[] udpRow = udptbl.GetAllUdpConnections();

            //getting the processes from the machine
            Process[] pro = Process.GetProcesses();

            //add rows from tcp and udp tables inside sysProcess array
            //compate the process id inside the process array and the process id
            //inside the connections tables
            if (pro != null)
            {

                    prcs = new SysProcess[pro.Length];
          


                for (int i = 0; i < pro.Length; i++)
                {
                    prcs[i] = new SysProcess(pro[i]);

                    if (tcpRow != null)
                    {
                        for (int z = 0; z < tcpRow.Length; z++)
                        {
                            if (prcs[i].prc.Id == tcpRow[z].owningPid)
                                prcs[i].tcprows.Add(tcpRow[z]);
                        }
                    }

                    if (udpRow != null)
                    {
                        for (int z = 0; z < udpRow.Length; z++)
                        {
                            if (prcs[i].prc.Id == udpRow[z].owningPid)
                                prcs[i].udpRows.Add(udpRow[z]);
                        }
                    }

                }
            }

        }

סרט דוגמה:





קוד:
https://sourceforge.net/projects/processnetwork/

סיכום:

למדנו להכיר טוב יותר את התהליכים שרצים לנו על המחשב וכיצד ניתן לעקוב אחריהם, קיימים המון כלים בתחום , Mark Russinovich היה בין הראשונים שלקח את ניתוח המערכות של מיקרוספט לקצה וניתן למצוא המון ספרים שלו בתחום, בנוסף אני חייב להזכיר את הפרויקט Process Hacker שבקוד פתוח ב .Net שניתן ללמוד ממנו המון.

תתחילו לעקוב...


אין תגובות:

הוסף רשומת תגובה