You are here

TCP port 스캔 구현

admin의 아바타

TCP의 port를 스캔하는 확장 패키지를 구현하여 보았습니다.

윈도우즈는 GetTcpTable api를 이용하여 MinGW로 테스트 하였고,
Linux는 /proc/net/tcp 파일을 검색해보면 알 수 있기에 순수 tcl로 작성하였습니다.

아래는 Windows의 코드입니다.

/*
 portscan.c

 how to cpmpile
 % gcc -I$(TCL)/include -DUSE_TCL_STUBS -c portscan.c
 % gcc -shared -o portscan.dll portscan.o -L$(TCL)/lib -ltcl85 -ltclstub85 -lws2_32 -liphlpapi
*/

#include <tcl.h>
#include <tk.h>

#ifdef __MINGW32__
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#endif

#include <stdio.h>

#ifdef __MINGW32__
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
static int portscan_HandleProc(
    ClientData data, Tcl_Interp *interp,
    int objc, Tcl_Obj *CONST objv[])
{
        // Declare and initialize variables
        PMIB_TCPTABLE pTcpTable;
        DWORD dwSize = 0;
        DWORD dwRetVal = 0;

        char szLocalAddr[128];
        char szRemoteAddr[128];

        struct in_addr IpAddr;

        int i;

        pTcpTable = (MIB_TCPTABLE *) MALLOC(sizeof (MIB_TCPTABLE));
        if (pTcpTable == NULL) {
                //printf("Error allocating memory\n");
                return TCL_ERROR;
        }

        dwSize = sizeof (MIB_TCPTABLE);
        if ((dwRetVal = GetTcpTable(pTcpTable, &dwSize, TRUE)) ==
                ERROR_INSUFFICIENT_BUFFER) {
                FREE(pTcpTable);
                pTcpTable = (MIB_TCPTABLE *) MALLOC(dwSize);
                if (pTcpTable == NULL) {
                        //printf("Error allocating memory\n");
                        return TCL_ERROR;
                }
        }
        if ((dwRetVal = GetTcpTable(pTcpTable, &dwSize, TRUE)) == NO_ERROR) {
                for (i = 0; i < (int) pTcpTable->dwNumEntries; i++) {
                        IpAddr.S_un.S_addr = (u_long) pTcpTable->table[i].dwLocalAddr;
                        strcpy(szLocalAddr, inet_ntoa(IpAddr));
                        IpAddr.S_un.S_addr = (u_long) pTcpTable->table[i].dwRemoteAddr;
                        strcpy(szRemoteAddr, inet_ntoa(IpAddr));

                        Tcl_AppendResult(interp, "{", NULL);

                        switch (pTcpTable->table[i].dwState) {
                        case MIB_TCP_STATE_CLOSED:
                                Tcl_AppendResult(interp, "CLOSED ", NULL);
                                break;
                        case MIB_TCP_STATE_LISTEN:
                                Tcl_AppendResult(interp, "LISTEN ", NULL);
                                break;
                        case MIB_TCP_STATE_SYN_SENT:
                                Tcl_AppendResult(interp, "SYN-SENT ", NULL);
                                break;
                        case MIB_TCP_STATE_SYN_RCVD:
                                Tcl_AppendResult(interp, "SYN-RECEIVED ", NULL);
                                break;
                        case MIB_TCP_STATE_ESTAB:
                                Tcl_AppendResult(interp, "ESTABLISHED ", NULL);
                                break;
                        case MIB_TCP_STATE_FIN_WAIT1:
                                Tcl_AppendResult(interp, "FIN-WAIT-1 ", NULL);
                                break;
                        case MIB_TCP_STATE_FIN_WAIT2:
                                Tcl_AppendResult(interp, "FIN-WAIT-2 ", NULL);
                                break;
                        case MIB_TCP_STATE_CLOSE_WAIT:
                                Tcl_AppendResult(interp, "CLOSE-WAIT ", NULL);
                                break;
                        case MIB_TCP_STATE_CLOSING:
                                Tcl_AppendResult(interp, "CLOSING ", NULL);
                                break;
                        case MIB_TCP_STATE_LAST_ACK:
                                Tcl_AppendResult(interp, "LAST-ACK ", NULL);
                                break;
                        case MIB_TCP_STATE_TIME_WAIT:
                                Tcl_AppendResult(interp, "TIME-WAIT ", NULL);
                                break;
                        case MIB_TCP_STATE_DELETE_TCB:
                                Tcl_AppendResult(interp, "DELETE-TCB ", NULL);
                                break;
                        default:
                                Tcl_AppendResult(interp, "UNKNOWN ", NULL);
                                break;
                        }

                        char localPort[1024];
                        sprintf(localPort, "%d", ntohs((u_short)pTcpTable->table[i].dwLocalPort));
                        char remotePort[1024];
                        sprintf(remotePort, "%d", ntohs((u_short)pTcpTable->table[i].dwRemotePort));
                        Tcl_AppendResult(interp, szLocalAddr, " ",
                                        localPort, " ", szRemoteAddr, " ", remotePort, "} ", NULL);
                }
        } else {
                //printf("\tGetTcpTable failed with %d\n", dwRetVal);
                FREE(pTcpTable);
                return TCL_ERROR;
        }

        if (pTcpTable != NULL) {
                FREE(pTcpTable);
                pTcpTable = NULL;
        }    

    return TCL_OK;
}
#endif

DLLEXPORT int Portscan_Init ( Tcl_Interp* interp )
{
    Tcl_InitStubs(interp, "8.4", 0);
        Tcl_CreateObjCommand(interp, "portscan", portscan_HandleProc, NULL, NULL);
    return Tcl_PkgProvide(interp, "portscan", "1.0");
}

아래는 Linux의 경우입니다.

proc make_ipv4 {add} {
        set ipv4_0 [format "%d" 0x[string range $add 6 7]]
        set ipv4_1 [format "%d" 0x[string range $add 4 5]]
        set ipv4_2 [format "%d" 0x[string range $add 2 3]]
        set ipv4_3 [format "%d" 0x[string range $add 0 1]]
        set ipv4 "$ipv4_0.$ipv4_1.$ipv4_2.$ipv4_3"
        return $ipv4
}

proc make_port {port} {
        return [format "%d" 0x$port]
}

proc make_status {st} {
        set st [format "%d" 0x$st]
        if { $st == 1 } {
                return "ESTABLISHED"
        } elseif { $st == 2 } {
                return "SYN-SENT"
        } elseif { $st == 3 } {
                return "SYN-REC3EIVED"
        } elseif { $st == 4 } {
                return "FIN-WAIT-1"
        } elseif { $st == 5 } {
                return "FIN-WAIT-2"
        } elseif { $st == 6 } {
                return "TIME-WAIT"
        } elseif { $st == 7 } {
                return "CLOSED"
        } elseif { $st == 8 } {
                return "CLOSE-WAIT"
        } elseif { $st == 9 } {
                return "LAST-ACK"
        } elseif { $st == 10 } {
                return "LISTEN"
        } elseif { $st == 11 } {
                return "CLOSING"
        } else {
                return "UNKNOWN"
        }
}

proc portscan {args} {
        set result [list]

        set fd [open /proc/net/tcp]
        while {![eof $fd]} {
                set line [string trim [gets $fd]]
                if { $line == "" } break

                set portinfo [split $line]
                if { [lindex $portinfo 0] == "sl" } continue

                set localadd [make_ipv4 [lindex [split [lindex $line 1] :] 0]]
                set localport [make_port [lindex [split [lindex $line 1] :] 1]]
                set remoteadd [make_ipv4 [lindex [split [lindex $line 2] :] 0]]
                set remoteport [make_port [lindex [split [lindex $line 2] :] 1]]
                set remoteport [make_port [lindex [split [lindex $line 2] :] 1]]
                set status [make_status [split [lindex $line 3] :]]

                lappend result [list $status $localadd $localport $remoteadd $remoteport]
        }

        close $fd

        return $result
}

pkgIndex.tcl 파일을 적절히 만드시고, package require portscan을 하시면 portscan 커맨드가 추가가 됩니다.
추가된 커맨드를 이용하여 portscan을 하면 모든 포트를 스캔하여 리스트로 만들어 아래와 같이 리턴해줍니다.

% portscan
{LISTEN 0.0.0.0 135 0.0.0.0 2272} {LISTEN 0.0.0.0 445 0.0.0.0 14580} {LISTEN 0.0.0.0 6000 0.0.0.0 39054} {LISTEN 0.0.0.0 16001 0.0.0.0 37114} {LISTEN 0.0.0.0 56125 0.0.0.0 41171} {LISTEN 127.0.0.1 1026 0.0.0.0 8380} {LISTEN 127.0.0.1 5354 0.0.0.0 37020} {LISTEN 127.0.0.1 10000 0.0.0.0 16458} {LISTEN 192.168.1.106 139 0.0.0.0 22589} {ESTABLISHED 192.168.1.106 1059 74.125.71.125 5222} {FIN-WAIT-1 192.168.1.106 1093 114.76.221.126 6881} {ESTABLISHED 192.168.1.106 1155 192.168.1.7 139} {ESTABLISHED 192.168.1.106 1349 109.165.25.86 22992} {ESTABLISHED 192.168.1.106 1406 74.125.71.139 80} {ESTABLISHED 192.168.1.106 1407 74.125.153.138 80} {ESTABLISHED 192.168.1.106 1549 114.76.221.126 6881} {ESTABLISHED 192.168.1.106 1683 114.76.221.126 6881} {ESTABLISHED 192.168.1.106 1761 74.125.71.17 80} {SYN-SENT 192.168.1.106 1772 110.11.94.62 62870} {SYN-SENT 192.168.1.106 1774 118.219.117.140 10474} {SYN-SENT 192.168.1.106 1776 180.66.88.168 45513} {SYN-SENT 192.168.1.106 1777 180.66.153.34 24826} {SYN-SENT 192.168.1.106 1779 117.196.165.93 22324} {SYN-SENT 192.168.1.106 1780 72.78.22.240 35087} {SYN-SENT 192.168.1.106 1783 180.69.27.81 25379} {ESTABLISHED 192.168.1.106 3111 143.248.239.23 3389} {ESTABLISHED 192.168.1.106 4694 117.224.119.121 35609} {ESTABLISHED 192.168.1.106 4770 82.227.165.91 6881} {LISTEN 192.168.11.1 139 0.0.0.0 61} {LISTEN 192.168.56.1 139 0.0.0.0 2224}

리스트의 의미는 다음과 같습니다.

첫번째 인덱스는 포트의 상태를
두번째 인덱스는 local address를
세번째 인덱스는 local port number를
네번째 인덱스는 remote address를
다섯번째 인덱스는 remote port number를 의미합니다.

포럼: