본문 바로가기

카테고리 없음

포트스캔

반응형

포트스캔(PortScan)의 이해와 구현


 

1. 포트(Port)란?


사전적으로는 모뎀과 컴퓨터 사이에 데이터를 주고받을 수 있는 통로라고 한다.
여기서 다루고자 하는 포트는 프로그래밍에 의해 만들어진 네트워크 프로그램이
외부와 통신을 위해 약속한 통로인 소프트웨어적인 측면을 이야기 하겠다.


 

2. 포트가 왜 필요할가??


사실 우리는 랜카드와 같은 네트워크 장비를 이용해서 외부와 접속을 하지 않을거면
포트는 필요없다. 우리가 가끔쓰는 메모장이나 그림판, 계산기를 생각해보자 이러한 프로그램들은
외부와 접속이 필요없다. 그냥 컴퓨터 내부에서 짜여진 프로그램 논리대로 알아서 잘 굴러간다.
하지만 외부와 접속을 하는 프로그램의 예를들어 소리바다, 네이트온, 파일구리
이러한 네트워크 프로그램은 컴퓨터 내부가 아닌 외부의 어떤 다른 컴퓨터와 데이터를 교환한다.
만약 포트가 없다면 내 컴퓨터로 들어오는 수 많은 데이터중에서 어떤 데이터가 네이트온으로
가야하는 데이터이고 어떤 데이터가 소리바다로 가야하는 데이터일지 구분 할 수 가 없다.
이렇게 되면 소리바다에서 노래를 다운 받을때 그 데이터가 갑자기 네이트온으로 가버릴수가
있을 것이다. 이렇듯 약속된 통로(포트)가 없다면 여러개의 네트워크 프로그램을 사용할때 그 많은
데이터가 갈팡질팡 제멋대로 상이한 프로그램과 데이터를 교환하여 원래의 프로그램 사용의도에
어긋나 제대로 통신을 할 수 없을 것이다. 그렇기 때문에 외부와 데이터를 교환하는 프로그램들은
프로그램설계 당시에 특정포트를 정해놓고 그 포트를 이용해서 데이터를 교환한다. 이러한 포트가
있기 때문에 우리는 파일을 공유하는 동시에 채팅도 할 수 있는 것이다.
일반적으로 가장 많이 사용하는 포트와 그에 대응하는 프로그램(서비스)들이 있는데.
0번부터 1023번까지 1024개의 포트는 잘 알려진(well-known)포트라고 불린다. 대부분 고유의
용도가 있다. 이런 포트정보는 리눅스의 경우 /etc/services 파일에 있고 윈도우의 경우
/WINDOWS/system32/drivers/etc/services 파일에 정보가 있다.


 

3. 포트스캔이란?


먼저 스캔(Scan)이란 단어를 보면 자세히[꼼꼼하게] 조사하다, 정밀 검사하다;유심히[뚫어지게]
쳐다보다 이러한 뜻이 있다. 말 그대로 포트들을 조사하여서 어떤 포트가 닫혀 있는지 열려있는지
조사하는 것이다. 이것은 여러가지 이유에서 행해질 수 있는데 자신의 서버에 네트워크 서비스들을
점검하기 위해서 쓰일수도 있고 해커가 해킹을 하기 위한 첫번째 단계인 정보수집 단계에서 타겟
서버의 정보를 취득하기 위해서 쓰일 수도 있다. 어찌됬든 간에 포트스캔은 포트의 활성화 여부를
조사하게 된다.

 

 

4. 포트스캔의 원리


포트스캔의 원리를 이해하기 위해선 TCP/IP의 네트워크 구조와 패킷이 전달되는 과정을 알어야 한다.
이러한 설명을 다하기에는 무리가 있어 괜찮은 영상을 하나 소개하려 한다.
http://www.hackerslab.org/images/tech/goodwarriors.wmv   <----이 영상이 패킷 전달 과정에 대한
이해를 도울 수 있을 것이다.
개괄적으로 원리를 설명하자면 포트활성와 여부조사를 위해 타겟서버에 특정한 패킷을 하나 보낸다.
그 패킷에는 특정포트에 대한 서비스를 요청하는 패킷이다. 일반적으로는 타겟서버의 포트가
열려 있을때의 응답패킷과 닫혀 있을때의 응답패킷이 다르다. 이러한 차이를 조사하여 포트활성화
여부를 알아낼 수 있다.


 

5. 포트스캔 구현을 위한 지식


첫째 OSI 7계층 프로토콜에 대한 전반적인 지식이 필요한데 그중에서 TCP, IP, UDP, ICMP 와 같은
     전송계층과 네트워크계층의 프로토콜에 대한 지식이 특히 필요하다.

둘째 C언어를 어느정도 다룰 수 있어야 한다.

셋째 패킷을 보내야 하기 때문에 외부와 접속을 하는 프로그래밍을 위해 소켓프로그래밍에 대한
     기본지식이 필요하다.


C언어는 어느정도 할 수 있다는 가정하에 설명을 하되 소켓프로그래밍은 기본적인 요소만 알면
되므로 BeeJ's의 네트워크프로그래밍 문서를 소개하려 한다.
http://www.hackerschool.org/HS_Boards/data/Lib_prog/Socket_Programming_KLDP.txt
필자도 C언어만 알고 있었지만 위의 강좌만 보고도 간단한 포트스캔을  만드는데는 큰 무리가
없었으며 혹시라도 생소한 부분이 나오면 그 부분만 서적에서 참고 하거나 검색으로 충분히 학습이
가능하다.

여기서는 패킷(packet)의 구조 중에서 TCP/IP에 대해서 살펴보겠다. 일단 우리가 다른 컴퓨터와
정보를 주고 받을때  패킷이란 것을 주고 받는데 패킷에는 여러가지 중요한 정보가 포함되어 있다.
예를 들면 전송하고자 하는  목적지 IP주소, 출발지 IP주소, 목적지 Port, 출발지 Port, 패킷의 수명,
실제 데이터 내용, 오류검사를 하는 플래그 등등 이외에도 다수의 정보가 많이 포함되어 있다.


먼저 IP헤더에 대해서 살펴보자.
IP는 패킷을 전송함에 있어서 매우 중요한 부분을 차지한다. 쉽게 말해서 대상 호스트의 주소를 가지고
있는 곳이다. 이는 편지지에 적는 집주소와 같이 데이터를 전송할 곳의 주소를 가지고 있다. 하지만
IP는 데이터 전송에 대해서 신뢰성을 보장하는 않는다. 데이터가 중간에 분실이 된다고 해도 그 상황을
알 수 없다. 순서대로 데이터가 도착하는지에 대한 어떠한 보장도 하지 않는다.


     IP헤더의 구조

0                                     15,16                      31
+------------+-------------------+--------------------------+----------------------------------+   ㅡㅡㅡㅡ
| (4)version | (4)header length  | (8)type of service(TOS)  |        (16)total length          |
+------------+-------------------+--------------------------+-----------+----------------------+
|            (16)identification         | (3)flags  | (13)fragment offset  |  
+--------------------------------+--------------------------+-----------+----------------------+
|    (8)time to live (TTL)       |       (8)protocol        |        (16)header checksum       |   20 bytes 
+--------------------------------+--------------------------+----------------------------------+     
|        (32)source IP address           |
+----------------------------------------------------------------------------------------------+  
|                                   (32)destination IP address           |
+----------------------------------------------------------------------------------------------+   ㅡㅡㅡㅡ
|     options (if any)           |
+----------------------------------------------------------------------------------------------+     ~~~~
|         data            |
+----------------------------------------------------------------------------------------------+   ㅡㅡㅡㅡ 
    (4) <-- 이것은 4-bit 를 나타낸다.
    (8) <-- 이것은 8-bit 를 나타낸다.
      .....


version : IP 헤더 형식을 나타낸다. 현재는 4이고 차세대는 6이다. 4는 IPv4를 의미하며, 6은
   IPv6을 의미한다.

header length : 데이터 영역을 제외한 순수 헤더의 길이를 말한다. 옵션은 일반적으로 잘 사용되
  지 않는 부분이다. 그래서 대부분 5가 된다. 만약 옵션이 사용될 경우에는
  반드시 4byte씩을 채워야 한다.

type of server(TOS) : 우선순위, 지연시간, 처리 능력, 신뢰성 등 바람직한 QoS (Quality of Ser
        vice)를 표시한다. 총 8bit 중에서 상위 3bit는 사용하지 않고, 나머지
        5bit 중 4bit는 성정하여 사용하며, 마지막 비트값은 0으로 지정한다.

total length : IP 데이터그램의 길이를 byte로 표시한다. 최대 길이는 16bit이므로 2의16제곱
        에다가 1의 뺀 65535bytes가 된다. 그럼 이 데이터를 넘게되면 데이터는 어떻게
        전송이 될까? 데이터는 분해되어 전송되는데 이것을 단편화라고 부른다.

identification : IP가 중복되지 않는 값으로 IP헤더를 구별할 수 있는 값이다. 이 값은 계속적으로
   증가되며 최대 65535까지 증가하고 이후 다시 처음으로 돌아와서 증가하기 시작
  한다. IP 단편화에 이용된다.

flags : 단편화와 관련이 있는 플래그이다. 먼저 첫번째 플래그는 0이다. 두번째 플로그는 DF
 (Don't Fragment)로, 단편화를 하지 않는다는 뜻으로 설정되는 비트이다. 세번째 플래그는
 MF(More Fragment)로 단편화를 한다는 뜻이다.

fragment offset : MF되었을 때 그 위치 값을 나타내는 값이다. 쉽게 말하면 쪼개고 나서 다시
    조립할 때 상대적인 위치 를 나타낸다. 사실 데이터 패킷을 분석할 때 이 부분은
    매우 중요한 부분이다.

time to live(TTL) : 네트워크 상에서 얼마나 오랫동아 살아남을 수 있을지 결정하는 수치이다.
      라우터를 하나 지날 때마다 이 값은 1씩 줄어든다. 만약 값이 0이 되면
      이 패킷은 버려진다. TTL 값이 0이 되면 그 결과를 ICMP패킷으로 출발지에
      되둘려준다.

protocol : 수신할 상위 계층을 나타내기 위해서 사용한다. 이 필드가 사용되는 가장 큰 이유는
    다중화의 지원이다. 한 IP 주소를 가진 컴퓨터에서 여러 가지 서비스를 동시에 하기 위해서
    반드시 필요한 부분이 될 것이다. TCP는 6번, UDP는 17번, ICMP는 1번 값을 사용한다.

header checksum : IP 헤더에만 사용된다. 헤더를 구성하는 16개 비트 각각 1의 보수를 더한다.
    그런 다음 합계의 1의 보수를 구한다. 이 필트는 TTL 필드가 라우터에서 감소되므로
    각 라우터에서 다시 계된되며 그에 따라 IP헤더도 수정된다. 오류검사를 위해 사용한다.

Source IP Address, Destination IP Address : 출발지와 목적지의 IP주소 값을 가진다.
     


다음으로 TCP헤더를 살펴보자.
데이터를 전송하는 프로토콜로 신뢰성을 확보해주며 가장 범용적으로 사용된다. IP는 무연결이므로
패킷 전송에 대해서 어떤 보장도 하지 않는다. TCP는 IP가 신뢰할 수 없는 것으로 가정하고, 이에
신뢰성을 확보해 준다.

 

     TCP헤더의 구조

          15,16                  31
+----------------------------------------------+-----------------------------------------------+    ㅡㅡㅡㅡ
|       (16)source port        |    (16)destnation port          |
+----------------------------------------------+-----------------------------------------------+   
|      (32)sequence number           |
+----------------------------------------------------------------------------------------------+
|     (32)acknowledge number           |     20byte 
+-------------+---------------+----------------+-----------------------------------------------+ 
|   (4)HLEN   | (4)reserved   |(6)|U|A|P|R|S|F||    (16)window size         |
+-------------+---------------+----------------+-----------------------------------------------+
|                   (16)checksum        |    (16)urgent pointer         |
+----------------------------------------------+-----------------------------------------------+    ㅡㅡㅡㅡ
|          option            |      ~~~~
+----------------------------------------------------------------------------------------------+    ㅡㅡㅡㅡ

 

source port : 패킷을 보내는 출발지 포트번호

destination port : 패킷을 받는 목적지 포트번호

sequence number : 처음 연결을 시도할 때 시스템에서 임의로 생성한다. 이 값은 전송량을 내포하고 있으며,
    받는 쪽에서 이 값을 이용하여 재조립을 한다.

acknowledge number : 상대편 호스트에서 받고자 하는 바이트의 번호를 정의한다.

header length(HLEN) : TCP헤더의 길이 값을 말한다. 기본적인 값은 20이다. 옵션이 사용될 경우 값의 크기는
        변경된다.

reserved : 현재 사용되지 않으며, 예약되어 있는 비트이다. 일반적으로 개발자들이 특별한 용도의 값이나
    설정용으로 사용됙도 한다.

|U|A|P|R|S|F| : 6개의 서로 다른 제어 비트를 표시한다.
  URG -> 긴급 플래그  (데이터를 전송하는 중간에 [CTRL + C]와 같은 행동 했을시 발생 플래그)
  ACK -> 수신 확인 플래그 (데이터가 제대로 전송되었다고 알려주는 플래그)
  PSH -> 푸시 플래그  (버퍼에 데이터가 차지 않아도 데이터 즉시 전송하겠다는 플래그)
  RST -> 리셋 플래그  (재설정을 요구하는 플래그 이상한 패깃이 도착하면 잘못 전송했다고 알려주는 플래그)
  SYN -> 동기화 플래그(상대에게 연결을 해도 되는지 제의하는 플래그)
  FIN -> 종료 플래그  (세션을 종료하고자 할때 사용하는 플래그, 접속 종료하겠다는 플래그)

window size : 패킷의 송수신시 네트워크 상황이나, 자신의 상황을 고려하여 받을 수 있는 최대 버퍼의크기를 말한다.


checksum : 데이터의 무결성을 보장해주는 값이다. 무결성 검사에서 잘못 되었다고 판단되면 이 패깃에 대해서 재전송을
    요구하게 된다. 데이터의 오류검사하기 위한 필드

urgent pointer : 긴급 데이터를 우선적으로 처리할 것을 수신측에 알리기 위한 필드

option : 최대 40bytes의 선택 정보가 가능하며, 수신지에 추가 정보를 전달하기 위하여 사용된다.


TCP/IP 구조에 대해서 살표보았다. 사실 이것 말고도  ICMP(Internet Control Message Protocol)나
UDP(User Datagram Protocol)등 여러가지 프로토콜들이 있다. ICMP는 패킷의 전송중에서 예기치 못한 오류가
발생했을때 오류발생 원인에 대해서 알려주는 프로토콜이다. 오류를 해결하지는 못하고 오류를 보고 하기만 한다.
UDP는 비연결지향 프로토콜로 패킷의 전송속도에 중점을 두어 TCP에있는 플래그나 기타 연결지향성 필드를 생략하고
오로지 속도지향을 위해 최소의 필드들만 구성하여 빠른속도로 패킷을 보내는 프로토콜이다.

 


6. 포트스캔을 하는 여러가지 방법들

 

포트스캔을 하는 방법이 여러가지인 이유는 보낼수 있는 패킷의 종료가 다양하고 그에 따른 응답도
다양하기 때문이다. 그래도 일반적으로 구분짓는 방법으로는 아래와 같이 구분을 한다.

Open Scan :  - TCP Connect
   - Reverse ident

Half-Open :  - SYN flag
   - IP ID header "dump scan"

Stealth   :   - FIN flag
   - ACK flags
   - NULL flags
   - ALL flags(XMAS)
   - TCP fragmenting
   - SYN | ACK flags

Sweeps    :  - TCP echo
   - UDP echo
   - TCP ACK
   - TCP SYN
   - ICMP echo

ICMP Scan :     - Echo Request(Type 8) / Echo Reply(Type 0)
          - Timestamp Request(Type 13) / Timestam Reply(Type 14)
   - Information Request(Type 15) / Information Reply(Type 16)
   - ICMP Address Mask Request(Type 17) / ICMP Address Mask Reply(Type 18)


Misc      :  - UDP/ICMP error
   - FTP bounce

 


여기서는 몇몇 포트스캔에 대한 간략적인 설명을 하겠다.

[Open Scan]

TCP Connect : TCP를 이용한 가장 기본적인 스캔으로 Open 스캔을 생각할 수 있다. 먼저 작동하리라고 
       예상되는 포트에 세션을 생성하기 위한 SYN 패킷을 보낸다. 포트가 열려 있을 경우에는
         서버에서 SYN+ACK 패깃이 돌아오고 공격자는 다시 ACK 패깃을 보내어 포트가 열려있다는
       것을 탐지한다. 만약 포트가 닫혀있다면 SYN 패킷을 보냈을때 RST+ACK 패킷이 돌아오고
       공격자는 아무런 패킷을 보내지 않는다. 이러한 과정을 3-way handshaking라고 한다.
       이러한 과정을 모두 거치기 때문에 상대방 시스템에 로그 기록이 남는다는 단점이 있다.
  
      
      [열려 있는 포트의 경우 : TCP Connect]

        공격자                                공격대상
    |---------->>------SYN------->>------| 
    |          |
    |----------<<---SYN + ACK----<<------|
    |           |
    |---------->>------ACK------->>------|

 

     [닫혀 있는 포트의 경우 : TCP Connect]
 
         공격자                                공격대상
    |---------->>------SYN------->>------| 
    |          |
    |         |
    |           |
    |----------<<---RST + ACK----<<------|

 


[Half-Open]


SYN flag : Open 스캔은 완전한 세션을 성립한 것으로 포트의 활성화 여부를 확인했지만, Half Open스캔은
    세션을 완성하지 않는다. 따라서 세션에 대한 로그가 남지 않는다는 장점이 있다. 스캔의 시작은
           Open 스캔과 같다. 먼저 SYN 패킷을 보낸다. 포트가 열려 있을 경우 서버는 SYN+ACK 패킷을 보내고
    공격자는 즉시 연결을 끊는 RST(Reset) 패킷을 보낸다.
    Open 스캔에서는 ACK 패킷을 보냈었는데 여기서는 ACK를 보내지 않는다. 왜냐면 ACK를 보냈을 경우는
    완전한 연결이 성립되어 로그가 남기 때문이다. 다음 그림에서 포트가 닫혀있을 경우가 열려있을
    경우를 살펴보자

   
      [열려 있는 포트의 경우 : SYN flag]

        공격자                                공격대상
    |---------->>------SYN------->>------| 
    |          |
    |----------<<---SYN + ACK----<<------|
    |           |
    |---------->>------RST------->>------|


   
                 [닫혀 있는 포트의 경우 : SYN flag]
 
         공격자                                공격대상
    |---------->>------SYN------->>------| 
    |          |
    |         |
    |           |
    |----------<<---RST + ACK----<<------|
 
  

   

[Stealth]


FIN :  FIN(Finish)플래그 값만 설정하여 패킷을 보내는 방법이다.      
NULL : 모든 플래그(flag)값을 설정하지 않고 보낸 패킷이다.
XMAS : ACK, FIN, RST, SYN, URG 플래그를 모두 설정하여 보낸 패킷


     이러한 패킷들을 보내는 것들을 스텔스(Stealth)스캔이 한다. Open 스캔처럼 세션을 완전히 성립하지 않고
     공격 대상 시스템의 포트의 활성화 여부를 알아내기 때문에 공격 대상 시스템에로그 정보가 남지 않는다.
     이러한 패킷을 보냈을때 상대 컴퓨터의 포트가 열려있다면 공통적으로 아무런 응답이 없고 상대 컴퓨터의
     포트가 열려있다면 RST 패킷이 돌아올 것이다. 이것을 다음 그림으로 살펴보자


     [열려 있는 포트의 경우 : FIN,NULL,XMAS]

        공격자    (이것 들중 하나만 보내면)    공격대상
    |-------->>--FIN,NULL,XMAS-->>-------| 
    |          |
    |         |
    |           |
    |-------------응답이 없다.-----------|


  

    [닫혀 있는 포트의 경우 : FIN,NULL,XMAS]
 
         공격자                                공격대상
    |-------->>--FIN,NULL,XMAS-->>-------| 
    |          |
    |         |
    |           |
    |----------<<------RST-------<<------|


[ICMP Scan]

ICMP(Internet Control Message Protocol) 프로토콜을 이용한 스캔이다. ICMP는 패킷의 오류에 대한
정보를 가지고 있고 패킷을 제어하는 역활을 한다. 특정 포트에 대한 스캔이라기 보다 서버가 나와
연결가능한 상태인지 알아 보는 방법이다. 대표적인 예로는 ping 프로그램을 사용하는 것이다. 윈도우나
리눅스에는 기본적으로 설치 되어 있는 프로그램인다. 명령어 창에서 ping [아이피] <-- 이렇게 입력하면
타켓 아이피로 부터 내 컴퓨터로 접속이 가능한지 ICMP 프로토콜을 이용하여 알아 볼 수 있다. 자세하게
이야기 하자면 ICMP프로토콜에 여러가지 필드들이 있는데 그중에서 Type 필드라는 것이 있는데 그 필드에는
여러가지 요청과 응답 옵션이 있다. 그중에서  Echo Request(Type 8) / Echo Reply(Type 0) 라는 기능을
이용해서 Type 값을 8로 만들어서 보내면 반향 응답 기능으로 상대 서버에서는 Type 값을 0으로 만들어서
응답이 온다. 이렇게 ICMP 프로토콜을 이용해서 상대서버로 부터 연결이 가능한지 알아 볼 수 있다. 하지만
요즘 ping이 나쁜쪽으로 악용되는 경우가 있어서 어떤 서버는 ping(Echo Request(Type 8) / Echo Reply(Type 0))을
요청할 때 아예 응답을 하지 않게 만들어 놓기도 한다. 아무튼 TCP/IP 말고도 이렇게 ICMP를 이용해서 서버를
탐지하기도 한다.


[Misc]

FTP bounce : 현재는 잘 사용되지 않지만 이 방법은 취약한 FTP 서버에서는 Port 명령을 통해서 다른
      시스템의 포트에 대한 활성화 여부를 확인 할 수 있는데, 방화벽 내에 이런 취약한 FTP서버가
      있을 때 유용하게 사용될 수 있다. 하지만 현재는 이러한 취약점이 존재하는 FTP서버가 거의 없다.

 

 

7. 간단한 포트스캔 구현

 

포트스캔 구현 환경
운영체제 : Linux
컴파일러 : gcc

위에서 언급했지만 C언어, 소켓프로그래밍, OSI 7계층 등 기본적인 지식을 알아야 한다.
포트스캔을 구현하는 여러가지 방법들을 위해서 언급했지만 그것들을 다 구현하고 설명하고 싶지만
시간관계상 그리고 부족한 실력으로 딱 한가지 방법의 포트스캔을 설명하고 구현하려 한다.
(다음에 기회가 된다면 더 공부하여서 여러가지 다른 포트스캔에 대해서 구현하고 설명하려 한다.)
여기서 구현해볼 포트스캔은 [Open Scan] TCP Connect을 이용한 스캔이다.

소켓프로그래밍중에서도 패킷을 자동으로 만들어주는 일반 소켓프로그래밍과 패킷의 내용을
프로그래머가 하나하나 조작할 수 있는 로우소켓프로그래밍(raw-socketprograming)이 있다.
로우소켓프로그래밍을 할 경우 패킷을 하나하나 조작할 수 있기때문에 스텔스(Stealth)스캔
과 같이 패킷의 내용을 변칙적으로 조작하여 전송해야 하기에는 적합한 프로그래밍이다.

하지만 우리가 여기서 구현하려 하는 경우는 TCP Connect 방법이기 때문에 패킷은 조작할 필요가
없고 단순히 3-way handshaking 을 이용하여 접속을 요청하고 그에 대한 응답만 확인하여 포트를 조사
할 것이다.


TCP Connect을 이용한 간단한 스캔예제
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
//TCP Connect Scan
//tcpscan.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char* argv[])
{
    int sockfd;
    struct sockaddr_in dest_addr;
    int port = 0;
    int ret = 0;


    for(port = 1; port <= 1024; port++)
    {

    sockfd = socket(PF_INET,SOCK_STREAM,0);


    memset((char*)&dest_addr,0,sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port   = htons(port);
    dest_addr.sin_addr.s_addr = inet_addr(argv[1]);


    ret = connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(dest_addr));

    if(ret != -1)
        {
            printf("%d Port Open\n",port);
        }
    else
        {
            //printf("%d Port Close\n",port);
        }

    close(sockfd);
    }
 printf("OK\n");

    return 0;
}


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
실행 화면 :
[wiseguyz@localhost tmp]$ gcc tcpscan.c -o tcpscan
[wiseguyz@localhost tmp]$ ./tcpscan 127.0.0.1
21 Port Open
22 Port Open
25 Port Open
80 Port Open
111 Port Open
631 Port Open
OK
[wiseguyz@localhost tmp]$
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

위와 같은 실행 결과 화면이 나온다. 이것은 단순하게 소켓을 하나 만들고 특정포트에 대한 접속을
요청하여 오픈되었을 때의 리턴값과 닫혀있을 때의 리턴값을 구분하여 조사하는 방법이다.
1번 포트부터 1024 포트까지 반복해서 소켓을 만들었다가 접속해보고 접속을 종료하고 다시 소켓을
만들었다가 접속해보고 접속을 종료하고를 반복한다. 이 방법이 위에서 언급한 [Open Scan] TCP Connect
이라고 한다. 유의할 점이 상대 서버에 접속한 로그가 남는 다는 것이다.

하지만 이 포트스캔의 문제점이 있는데 로그가 남는것 말고도 내부의 자기 자신의 포트를 조사할 때는
잘 작동하지만 멀리 떨어진 외부의 포트를 조사할때 특정포트 137,138,139포트등을 (이외에도 몇개 더있다)
조사할때는 프로그램이 장시간 멈춰있다는 것이다. 이것은 멀리 떨어진 외부의 컴퓨터에 접속을 할때는
ISP(인터넷 서비스 제공업체)를 거치게 되어있는데 특정포트 137,138,139포트등 이러한 포트는 보안에
취약한 포트로 분류하고 필터링을 하여서 요청한 포트에대한 응답을 하지 않도록 해놨기 때문이다. 그래서
오지도 않는 응답패킷을 소켓이 계속 기다리게 된다. 결국은 한참 기다리다가 자동리턴 되어서 닫혀 있는
포트로 단판하지만 그 시간이 너무 오래 걸리기 때문에 포트하나를 검사하는데 너무 많은 시간을 소비한다.
이것을 해결하기 위해서는 소켓동작 모드에 대한 이해가 필요하다.


소켓의 동작모드
소켓의 동작모드에는 blocking, non-blocking, 비동기(asynchronous) 모드 세 가지가 있다.

▶blocking 모드
● 소켓을 처음 생성하면 디폴트로 blocking 모드가 되는데, 이 소켓에 대 해 어떤 시스템 콜을 호출하였을 때
   네트워크 시스템(즉, TCP/IP)이 동작 을 완료할 때까지 그 시스템 콜에서 프로세스가 멈추어 있게 된다.
● block 될 수 있는 소켓 시스템 콜은 listen(), connect(), accept(), recv(), send(), read(), write(),
   recvfrom(), sendto(), close() 등이다.
● 일 대 일 통신을 하거나 프로그램이 한가지 작업만 하면 되는 경우는 blocking 모드로 프로그램을 작성할 수 있다.

▶ Non-blocking 모드
● 소켓 관련 시스템 콜에 대하여 네트워크 시스템이 즉시 처리할 수 없는 경우라도 시스템 콜이 바로 리턴되어
   응용 프로그램이 block되지 않게 하는 소켓 모드
● 통신 상대가 여럿이거나 여러 가지 작업을 병행하려면 non-blocking 또는 비동기 모드를 사용하여야 한다.
● non-blocking 모드를 사용하는 경우에는 일반적으로 어떤 시스템 콜 이 성공적으로 실행될 때까지 계속 루프를
   돌면서 확인하는 방법(폴링)을 사용한다.
● 유닉스에서는 fcntl() 시스템 콜을 사용하여 소켓을 non-blocking 모드로 바꿀 수 있다.

▶ 비동기 모드
● 소켓에서 어떤 I/O 변화가 발생하면 (데이터의 도착 등) 그 사실을 응용 프로그램이 알 수 있도록 하여 
   그 때 원하는 동작을 할 수 있게 하는 모드
● 전화에서 상대방과 통화할 수 없을 때 전화를 걸어 달라고 부탁하고 끊는 것과 유사하다.
● 소켓을 비동기 모드로 바꾸는 방법에는, select() 함수를 이용하는 방법, 그리고 fcntl()를 사용하여 소켓을
   signal-driven I/O 모드로 바꾸는 방법이 있다.
● select()를 이용하는 방법은 I/O 변화가 발생할 수 있는 소켓 전체를 대상으로 select()를 호출해 두면 
   그 중 임의의 소켓에서 I/O 변화가 발생되었을 때 select()문이 리턴되고 이 때 원하는 작업을 하는 방법이다.
● signal-driven I/O 방법은 특정 소켓에서 I/O 변화가 발생하였을 때 그 소켓이 SIGIO 시그널을  발생시키도록
   하고 응용 프로그램에서는 이 시그널을 받으면 필요한 작업을 하도록 하는 방법이다.


이와 같이 우리는 처음에 기본적으로 소켓을 생성하였기 때문에 blocking모드로 생성되어서 프로그램이
특정포트에 의한 응답을 장시간 기다리는 것 이였다. 다음은 앞의 소스를 non-blocking 모드를 바꾸고
타겟을 ip말고도 도메인명으로 입력받고 포트의 범위를 입력받아서 검사하는 프로그램으로 수정된 소스를
작성해 보겠다.

 

non-blocking로 수정된 TCP Connect스캔 예제
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
//TCP Connect Scan
//tcpscan2.c
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>

int pscan(struct hostent *he, int cport, int socktype);

int main(int argc, char* argv[])
{
    int cport;
    int start = 0;
    int end = 0;
    struct hostent *he;
    start = atoi(argv[2]);
    end = atoi(argv[3]);

    if( (he = gethostbyname(argv[1])) == NULL)
    {
        printf("gethostbyname\n");
        exit(0);
    }

    if(argc !=4)
    {
        printf("test [IP_Address] [start port] [end port]\n");
        return -1;
    }

    for(cport = start; cport <= end; cport++)
        pscan(he,cport,SOCK_STREAM);

        printf("OK\n");

    return 0;
}


int pscan(struct hostent *he,int cport, int socktype)
{
    int sockfd;
    int flags, n, error;
    socklen_t len;
    fd_set rset,wset;
    struct timeval tval;
    struct sockaddr_in destaddr;

     sockfd = socket(AF_INET,socktype,0);

     destaddr.sin_family = AF_INET;
     destaddr.sin_addr = *((struct in_addr *)he->h_addr);
     destaddr.sin_port = htons(cport);
     bzero(&(destaddr.sin_zero),8);

    flags = fcntl(sockfd,F_GETFL,0);
    fcntl(sockfd,F_SETFL,flags | O_NONBLOCK);

 

    n=connect(sockfd,(struct sockaddr*)&destaddr,sizeof(struct sockaddr));

    FD_ZERO(&rset);
    FD_SET(sockfd,&rset);
    wset = rset;
    tval.tv_sec = 2;   //blcok time 2 second
    tval.tv_usec = 0;


   n = select(sockfd+1,&rset,&wset,NULL,&tval);


     if( n == 0 )
     {
        close(sockfd);
        printf("%d Port TimeOut\n",cport);
        return -1;
     }

    if(FD_ISSET(sockfd,&rset) && FD_ISSET(sockfd,&wset))
    {
    //  printf("%d Port Close\n",cport);
    }
    else
    {
        printf("%d Port Open\n",cport);
    }

    close(sockfd);

    return 0;
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
실행화면 :
[wiseguyz@localhost tmp]$ gcc tcpscan2.c -o tcpscan2
[wiseguyz@localhost tmp]$ ./tcpscan2 www.hackerschool.org 0 1000
21 Port Open
22 Port Open
23 Port Open
25 Port Open
53 Port Open
80 Port Open
111 Port Open
137 Port TimeOut
138 Port TimeOut
139 Port TimeOut
445 Port TimeOut
707 Port TimeOut
OK
[wiseguyz@localhost tmp]$
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

위의 실행화면에서 볼 수 있듯이 ISP에서 필터링한 포트들은 프로그램 소스에서 작성한 시간대로
2초정도만 기다리고 응답이 없으면 TimeOut를 출력한다. 소스에 대한 일일이 설명하진 않겠다.
소켓프로그래밍을 공부하셨다면 충분히 해석이 가능할 것이다.

이것으로 포트스캔(PortScan)의 이해와 구현에 대한 문서는 마치려 한다. 처음으로 작성해본
문서라서 부족한 면이 많을 텐데 여기까지 읽어주신 모든 분들께 감사의 마음을 표합니다.('')(..)꾸벅
다음에는 시간을 좀 두고 더 잘 작성하고 싶네요.
     ㅡ감사합니다ㅡ

반응형
댓글