Multi-threaded port scanning in Delphi

Delphi 7There were several times when my team had to do a port scan in a commercial environment to discover rouge or lingering devices on the network. I always had troubles finding a portable, commercial-free, easy-to-use, fast and trusted application so I decided to write my own.
The idea came from this blog, but it was far away from sufficient in a mass port scanner. Sometimes the Winsock connect function times out in 20 seconds, so checking only 3 ports with the original procedure would take a minute. I had to do a manual timeout, which I achieved by calling the connect function in a separate thread, which I forcefully terminate after 500 ms. Since in a port scanner we want to maximize the speed I needed to put the above in a thread, so I can launch multiple port checks at the same time.
 
Type
  TConnectRec = record
   Socket: Integer;
   Client: sockaddr_in;
   Success: Boolean;
  end;
  PConnectRec = ^TConnectRec;
 
  TWellKnownPorts = Record
   PortNR: Word;
   PortFunction: String;
  End;
 
  TCheckThread = class(TThread)
  protected
    PortIndex: Integer;
    IPToCheck: String;
    ConnectRec: TConnectRec;
    procedure Execute; Override;
  published
    Constructor Create(In_PortIndex: Integer; In_IP: String);
  End;
 
const
  WellKnownPorts: Array[0..20] Of TWellKnownPorts =
  ( (PortNR: 20; PortFunction: 'FTP data'),
    (PortNR: 21; PortFunction: 'FTP control'),
    (PortNR: 22; PortFunction: 'SSH'),
    (PortNR: 23; PortFunction: 'Telnet'),
    (PortNR: 25; PortFunction: 'SMTP'),
    (PortNR: 80; PortFunction: 'HTTP'),
    (PortNR: 110; PortFunction: 'POP3'),
    (PortNR: 115; PortFunction: 'SFTP'),
    (PortNR: 139; PortFunction: 'NetBIOS Session'),
    (PortNR: 143; PortFunction: 'IMAP'),
    (PortNR: 389; PortFunction: 'LDAP'),
    (PortNR: 443; PortFunction: 'HTTPS'),
    (PortNR: 445; PortFunction: 'SMB over IP'),
    (PortNR: 601; PortFunction: 'Syslog'),
    (PortNR: 631; PortFunction: 'Internet Printing Protocol'),
    (PortNR: 860; PortFunction: 'iSCSI'),
    (PortNR: 1433; PortFunction: 'MSSQL'),
    (PortNR: 3268; PortFunction: 'Microsoft Global Catalog'),
    (PortNR: 3306; PortFunction: 'MySQL'),
    (PortNR: 3389; PortFunction: 'Remote Desktop Connection')
  );
 
  TCP_Connection_Timeout = 500;
 
procedure Conn(Parameter: Pointer);
begin
 PConnectRec(Parameter).Success := connect(PConnectRec(Parameter).Socket, PConnectRec(Parameter).Client, SizeOf(PConnectRec(Parameter).Client)) = 0;
end;
 
Constructor TCheckThread.Create(In_PortIndex: Integer; In_IP: String);
Begin
 inherited Create(False);
 PortIndex := In_PortIndex;
 IPToCheck := In_IP;
 ConnectRec.Success := False;
End;
 
Procedure TCheckThread.Execute;
var
 client: sockaddr_in;
 sock, ret: Integer;
 wsdata: WSAData;
 ThId: Cardinal;
 ThH: THandle;
Begin
 ret := WSAStartup($0002, wsdata);
 If Ret <> 0 Then Exit;
 Try
  client.sin_family := AF_INET;
  client.sin_port := htons(WellKnownPorts[PortIndex].PortNR);
  client.sin_addr.s_addr := inet_addr(PChar(IPToCheck));
  sock := socket(AF_INET, SOCK_STREAM, 0);
  ConnectRec.Socket := sock;
  ConnectRec.Client := Client;
  ThH := BeginThread(nil, 0, Addr(Conn), Addr(ConnectRec), 0, ThId);
  If ThH <> 0 Then Begin
                   If (WaitForSingleObject(ThH, TCP_Connection_Timeout) <> WAIT_OBJECT_0) And (sock <> INVALID_SOCKET) Then closesocket(sock);
                   CloseHandle(ThH);
                   End;
 Finally
  WSACleanup;
 End;
End;

 
You can launch the process with TCheckThread.Create(0,'127.0.0.1') or you can create an array of TThread objects - I'll leave this part to you. You can see if the port check is done by calling WaitForSingleObject, and the result in CheckThread.ConnectRect.Success. For easy understanding the WellKnownPorts[CheckThread.PortIndex].PortFunction will return the general use of the port instead of a port number.
 
Enjoy!

Add a comment

HTML code is displayed as text and web addresses are automatically converted.

This post's comments feed