דמיינו מצב שאתם מנהלים שרת אינטרנט וההייתם רוצים לדעת כמה משתמשים יש על השרת באותו רגע, וכמובן באיזה שירותים הם משתמשים, אחת הדרכים הנפוצות לעשות זו היא בעזרת שימוש בכלים קיימים ב 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.
{
//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;
}
}
}
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 שניתן ללמוד ממנו המון.
תתחילו לעקוב...
אין תגובות:
הוסף רשומת תגובה