본문 바로가기

여러가지

[네트워크] 아호-코라식 알고리즘을 이용한 URL 패턴매칭 및 차단

  BoB 에서 길길멘토님의 과제인 아호-코라식을 이용해 패턴매칭을 하는 과제가 있었다.

URL의 대조를 이용해 유해사이트인지 판단하여 패킷을 DROP, SEND 하는 프로그램을 짜오는 내용의 과제였다.

 

먼저 패킷 관련 구현은 윈도우에서 진행하였다. 원래 우분투에서 하려 했지만, 얼마전 대회 중 노트북이 고장나 어쩔 수 없이 윈도우를 설치하게 되었다. (AS기사님이 우분투를 처음 보신다면서... 죄성합니다 기사님) 

 

우분투에서는 보통 패킷 관련 프로그램을 구현할 때 WinDivert를 사용한다고 한다. 실제로도 많은 도구들이 WinDivert를 사용하고 있다. (Snoopspy, Clumsy 등) 그래서 WinDivert를 사용하면서, 언어는 파이썬을 사용하려고 ctypes를 이용해 dll에서 읽어오려 했다. 하지만 어떤 사람이 벌써 Github에 pydivert 라는 이름으로 포팅해 놓은 것이 있었다... 이 모듈을 사용하기로 했다.

 

 

본문으로 넘어가자.

 

 

 

WinDivert의 드라이버 객체는 우선 

WINDIVERT_DLL_PATH = os.path.join(PROJECT_ROOT, 'DLLs', "WinDivert.dll")
self.driver = WinDivert(WINDIVERT_DLL_PATH)

WinDivert 함수를 이용해 가져온다. 해당 dll의 절대 경로를 적어주면 된다.

 

 

해당 드라이버를 이용해 패킷의 핸들을 가져와야 하는데, Handle 함수가 패킷 핸들러를 반환해주므로 이용하면 된다.

 

Handle(self.driver, "outbound and tcp.DstPort == 80", priority=1000) as handle:

순서대로 driver, filter_text, priority 인자이다.

driver는 WinDivert 를 이용해 가져온 드라이버 객체이며, 

filter_text는 모든 패킷을 받지 않고 조건에 따라 패킷을 가져오고 싶을 때 이용한다. outbound and tcp.DstPort == 80 은 밖으로 나가는 패킷 (outbound) 중에, TCP의 도착지 포트가 80번 (tcp.DstPort == 80) 인 패킷만 가져온다는 뜻이다. 포트가 80번인 서비스는 웹 서비스 이므로 웹 패킷만 가져온다고 봐도 무방하다.

priority 는 우선 순위로, 예제를 찾아봐도 대부분 1000을 주지만 왜 1000을 주는 지 그 이유에 대해서는 특별히 나온 정보가 없었다. 추후에 추가하겠다.

 

핸들을 가져오는데 성공하면 패킷을 받으면 된다.

 

raw, meta = handle.recv()

raw 에는 패킷의 raw data가, meta 에는 패킷의 메타정보가 담긴다. 우리가 필요한 정보는 tcp/ip/http 정보이므로 raw 데이터를 주목하자.

 

패킷의 인덱스를 이용해 분석할 수도 있지만, 다행히 driver 에서 raw 데이터의 분석을 해준다. 우리가 필요한 부분은 패킷의 payload 부분이므로, 해당 부분을 가져오자.

captured = self.driver.parse_packet(raw)

captured 는 분석된 패킷의 객체가 담긴다. 이제 필터링할 호스트를 가져온다. 

host = self.find_host(captured.payload)

우선 raw 데이터는 bytes 형이므로, decode 해주고 분석해주면 편하다. 

GET / HTTP/1.1\r\nHost: www.naver.com\r\nConnection: 부분에서 www.naver.com 만 가져오려면 Host: 와 \r\nConnection 사이를 파싱하면 된다.

head_idx = payload.index('Host: ') + len('Host: ')
end_idx = payload.index('\r\nConnection', head_idx)

시작 index 와 끝 index 를 가져온다. 따라서 

return payload[head_idx:end_idx]

이 부분은 host를 가지고 있게 된다.

 

 

패킷에서 호스트를 가져오는데 성공했으니, 이제 이 호스트가 유해한 URL인지 확인만 하면 된다. 

def filter_host(self, host):
    """
    Using Aho-Corasick, check is it bad url
    :param host: host string ( www.google.com )
    :return: boolean
    """
    # Do Aho-Corasick!!!!
    return self.filter.search_pattern(host)

 

이 부분에서 아호-코라식 알고리즘을 사용했다.