본문 바로가기

Project/Anti-Virus (Python)

7. 악성코드 진단/치료 모듈 분리

7.1 악성코드 진단 모듈 만들기

 앞서 정리한 글에서 악성코드는 꾸준히 증가하고 있다고 하였다. 그렇다면 악성코드 패턴 또한 증가한다는 말이다. 만약에 악성코드 패턴이 백신 프로그램 내부에 존재한다면 악성코드가 증가할때마다 백신 프로그램을 수정하여 재배포하여야 하는 불편함이 존재한다고 했다. 이를 방지하기 위하여 악성코드 패턴을 'virus.db' 파일로 따로 분리하였다.


 백신 프로그램에는 악성코드 패턴 말고도 자주 수정이 되어야 하는 것들이 존재한다. 장 빈번한 수정이 필요한 것은 바로 악성코드 진단/치료 기능이다. 이번 글에서는 악성코드 진단/치료 기능을 분리하는 방법을 알아보겠다.


7.1.1 MD5 진단 모듈 만들기

size = os.path.getsize(fname) if vsize.count(size):     fp = open(fname, 'rb')     buf = fp.read()

fp.close()


m = hashlib.md5()

m.update(buf)

fmd5 = m.hexdigest()


ret, vname = SearchVDB(fmd5)

if ret == True:

print '%s : %s' % (fname, vname)

os.remove(fname)

else:

print '%s : ok' % (fname)

else:

print '%s : ok' % (fname)

위 소스는 'antivirus.py'의 입력받은 파일의 내용의 MD5 해쉬값을 추출하는 부분이다. 이 부분을 'ScnaMD5'의 이름으로 따로 함수화한다.


def SearchVDB(fmd5):

for t in vdb:

if t[0] == fmd5:

return True, t[1]


return False, ''


def ScanMD5(fname)

ret = False

vname = ''


size = os.path.getsize(fname)

if vsize.count(size):

fp = open(fname, 'rb')

buf = fp.read()

fp.close()


m = hashlib.md5()

m.update(buf)

fmd5 = m.hexdigest()


ret, vname = SearchVDB(fmd5)


return ret, vname

입력받은 파일의 이름을 매개변수로 넘겨받아 그에 해당하는 해쉬값을 추출하여 'SearchVDB'함수를 실행시키는 기능을 하는 함수로 만들었다. 악성코드를 진단하는 부분은 'SearchVDB()'와 'ScanMD5()'로 나누어졌다. 이제 이 두 함수를 하나의 파일로 만들면 된다.


#scanmod.py


# -*- coding:utf-8 -*-

import os

import hashlib


def SearchVDB(vdb. fmd5):

for t in vdb:

if t[0] == fmd5:

return True, t[1]


def ScanMD5(vdb, vsize, fname):

ret = False

vname = ''


size = os.path.getsize(fname)

if vsize.count(size):

fp = open(fname, 'rb')

buf = fp.read()

fp.close()


m = hashlib.md5()

m.update(buf)

fmd5 = m.hexdigest()


ret, vname = SearchVDB(vdb, fmd5)


return ret, vname

#antivirus.py


# -*- coding:utf-8 -*-

import sys

import os

import hashlib

import zlib

import StringIO

import scanmod


VirusDB = []

vdb = []

vsize = []


def DecodeKMD(fname):

try:

fp = open(fname, 'rb')

buf = fp.read()

fp.close()


buf2 = buf[:-32]

fmd5 = buf[-32:]


f = buf2

for i in range(3):

md5 = hashlib.md5()

md5.update(f)

f = md5.hexdigest()


if f != fmd5:

raise SystemError


buf3 = ''

for c in buf2[4:]:

buf3 += chr(ord(c) ^ 0xff)


buf4 = zlib.decompress(buf3)

return buf4

except:

pass


return None


def LoadVirusDB():

buf = DecodeKMD('virus.kmd')

fp = StringIO.StringIO(buf)


while True:

line = fp.readline()

if not line:

break


line = line.strip()

VirusDB.append(line)


fp.close()


def MakeVirusDB():

for pattern in VirusDB:

t = []

v = pattern.split(':')

t.append(v[1])

t.append(v[2])

vdb.append(t)


size = int(v[0])

if vsize.count(size) == 0:

vsize.append(size)


if __name__ == '__main__':

LoadVirusDB()

MakeVirusDB()


if len(sys.argv) != 2:

print 'Usage : antivirus.py [file]'

exit(0)


fname = sys.argv[1]


ret, vname = scanmod.ScanMD5(vdb, vsize, fname)

if ret == True:

print '%s : %s' % (fname, vaname)

os.remove(fname)

else:

print '%s : ok' % (fname)

첫번째 코드가 악성코드를 진단하는 기능만 따로 뺀 'scanmod.py'이다. 두번째 코드는 'antivirus.py'로 악성코드 진단 기능은 빠진 백신 프로그램이다. 구분하면서 바뀐 점은 'scanmod.py'에서 참조할 악성코드 패턴들이 모두 'antivirus.py'에 있기 때문에 매개변수로 받아와야 한다. 그래서 함수로 전달되는 매개변수가 늘어났다. 'antivirus.py'에서는 진단 기능을 가진 'scanmod.py'를 사용하기 위하여 'import scanmod'를 하였다.


7.1.2 특정 위치 진단 모듈 만들기

 지금까지 나온 악성코드 진단 방법은 MD5 해쉬값을 이용하는 방법, 악성코드 진단 문자열을 사용하는 방법 두 가지가 있다. 그 중 악성코드 진단 문자열을 이용하는 진단 방법을 조금 더 자세하게 알아보겠다. 악성코드 진단 문자열을 사용하여 진단하는 방법은 크게 두 가지로 나눌 수 있다. 첫 번째는 임의 파일을 열어서 파일 처음부터 끝까지 해당 악성코드 지난 문자열이 존재하는지 체크하는 방법, 두 번째는 특정 위치에 악성코드 진단 문자열이 존재하는지 체크하는 것이다. 전자를 전체 위치 검색법, 후자를 특정 위치 검색법이라 한다.


 전체 위치 검색법은 파일 처음부터 끝까지 모두 확인하기에 속도가 느릴 수 밖에 없지만, 악성코드의 변형을 진단하는 성능은 뛰어나다. 특정 위치 검색법은 해당 위치에 악성코드 진단 문자열이 존재하는지를 체크하기 때문에 검사 속도는 바르지만 악성코드의 변형을 진단하기는 어렵다. 특정 위치 검색법을 이용한 악성코드 진단 소스를 보자.

#-*- coding:utf-8 -*-


#특정 위치 검색법

def ScanStr(fp, offset, mal_str):

#악성코드 진단 문자열 길이

size = len(mal_str)


#특정 위치에 문자열이 존재하는지 확인

fp.seek(offset)

buf = fp.read(size)


if buf = mal_str:

#발견

return True

else:

#미발견

return False


#파일을 열어서 악성코드 검사

fp = open('eicar.txt', 'rb')

print ScanStr(fp, 0, 'X50')

fp.close()

소스를 보면 알 수 있듯이 특정 위치 검색하는 함수를 실행하기 위해 넘겨주는 인자는 열어볼 파일, 파일 내의 위치, 진단 문자열의 세 가지이다. 위치와 진단 문자열은 백신 개발자의 선택에 달려있다. 이렇게 만들어진 특정 위치 검색법을 'scanmod.py'에 추가해보겠다.

# -*- coding:utf-8 -*-

import os

import hashlib


def SearchVDB(vdb, fmd5):

for t in vdb:

if t[0] == fmd5:

return True, t[1]


return False, ''


def ScanMD5(vdb, vsize, fname):

ret = False

vname - ''


size = os.path.getsize(fname)

if vsize.count(size):

fp = open(fname, 'rb')

buf = fp.read()fp.close()


m = hashlib.md5()

m.update(buf)

fmd5 = m.hexdigest()


ret, vname = SearchVDB(vdb, fmd5)


return ret, vname


def ScanStr(fp, offset, mal_str)

size = len(mal_str)


fp.seek(offset)

buf = fp.read(size)


if buf == mal_str:

return True

else:

return False






7.2 악성코드 치료 모듈 만들기

 악성코드 진단 모듈을 분리하였다. 악성코드 치료 모듈 또한 잦은 수정이 필요한 기능이기 때문에 따로 모듈화 해야한다. 모듈의 이름은 책과 동일하게 'curemod.py'로 하겠다. 지금까지 악성코드를 치료하는 기능으로 삭제하는 것만 다뤘기 때문에 소스코드는 간단하다.






7.3 악성코드 패턴 파일 수정하기

 현재 악성코드 패턴 파일에는 MD5 해시만을 이용한 악성코드 패턴이 들어있다. 진단 모듈에 새로운 진단법은 특정 위치 검색법이 추가 됨으로써 그에 해당하는 악성코드 패턴을 업데이트 해주어야 한다. 어떻게 수정해주어야 할까? 먼저 진단 방법에 따른 치료가 진행되어야 하므로 진단 방법이 추가 되어야 한다. 그리고 치료 방법이 추가 되어야 한다.


(1) MD5 해시를 이용하는 경우

 기존의 'virus.db'에 진단 방법과 치료방법만 추가하면 된다.

'악성코드 진단 함수(ScanMD5):악성코드 치료 함수:악성코드 파일 크기:MD5 해시:악성코드 이름'


(2) 특정 위치 검색법을 이용하는 경우

 특정위치 검색법은 새롭게 정의되어야 한다.

'악성코드 진단 함수(ScanStr):악성코드 치료 함수:악성코드 진단 문자열 위치:악성코드 진단 문자열:악성코드 이름'


이렇게 새롭게 바뀐 악성코드 패턴 파일을 확인해보자.

ScanMD5:CureDelete:63:b0fb72bf368f0ffc70ded92ffe287d9e:Dummy Test
ScanStr:CureDelete:51:Wonyong Choi:Eicar Test






7.4 백신 수정

 'antivirus.py'의 수정이 필요하다. 왜냐하면, 현재 수정하기 이전의 'virus.db'의 내용만을 인식하기 때문이다. 그리고 새롭게 추가된 진단 방법으로의 요청 또한 업데이트 해주어야 하기 때문이다. 먼저 수정된 'virus.db'의 내용을 인식하게끔 수정하겠다. 'virus.db'로부터 악성코드 패턴을 읽어오는 함수는 'MakeVirusDB'이다. 현재 'virus.db'는 첫 번째 요소에 악성코드 검사 함수, 두 번째 요소에 악성코드 치료 함수가 존재한다. 현재 'MakeVirusDB' 함수는 MD5 해시값으로만 악성코드 검사가 가능하므로 이를 고쳐 주어야 한다.

#antivirus.py 소스


def MakeVirusDB():

for pattern in VirusDB:

t = []

v = pattern.split(':')


scan_func = v[0]

cure_func = v[1]


if scanf_func == 'ScanMD5':

t.append(v[3])

t.append(v[4])

vdb.append(t)


size = int(v[2])

if vsize.count(size) == 0:

vsize.append(size)


elif scan_func == 'ScanStr':

t.append(int(v[2]))

t.append(v[3])

t.append(v[4])

sdb.append(t)


if __name__ == '__main__':

LoadVirusDB()

MakeVirusDB()


if len(sys.argv) != 2:

print 'Usage : antivirus.py [file]'

exit(0)


fname = sys.argv[1]


ret, vname = scanmod.ScanVirus(vdb, vsize, sdb, fname)

if ret == True:

print '%s : %s' % (fname, vname)

curemod.CureDelete(fname)

else:

print '%s : ok' % (fname)


'scanmod.py' 수정도 필요하다. 현재는 MD5 해시값으로만 바이러스 검사가 가능하기 때문에 특정 위치 검색법까지 이용하도록 변경시켜준다.

def ScanVirus(vdb, vsize, sdb, fname):

ret, vname = ScanMD5(vdb, vsize, fname)

if ret == True:

return ret, vname


fp = open(fname, 'rb')

for t in sdb:

if ScanStr(fp, t[0], t[1]) == True:

ret = True

vname = t[2]

break


fp.close()


return ret, vname

'ScanVirus' 함수는 'scanmod.py'에 추가한다. 기존의 MD5 해시값을 비교하는 함수에 특정 위치 검색법까지 이용 가능하도록 만들어준다. 이렇게 되면 'antivirus.py'는 바이러스 검색법이 새로 생기더라도 수정할 필요가 없이 'scanmod.py'만 수정해주면 된다.






7.5 정리

 백신은 다양한 악성코드의 등장으로 잦은 업데이트가 필요하다. 하지만, 악성코드의 추가시마다 수정을 하게 되면 그만큼 비효율적일 수가 없을 것이다. 그래서 악성코드의 증가와 치료 및 검사 방법의 추가에 용이하도록 고쳐보았다.


 백신 프로그램의 잦은 수정을 방지하기 위해서 적용한 방법으로는 첫번째, 악성코드 패턴 파일을 별도로 만든 것이다. 두번째, 악성코드 진단 방법을 수정하기 쉽도록 모듈화한 것, 마지막으로 치료 방법까지 모듈화한 것이다.





※ 참고 서적 : 파이썬으로 배우는 Anti-Virus 구조와 원리 (최원혁 지음)

'Project > Anti-Virus (Python)' 카테고리의 다른 글

6. 악성코드 패턴 분리하기  (0) 2019.04.04
5. 다양한 악성코드 진단/치료  (0) 2019.03.17
4. 전용백신 개발하기  (0) 2019.03.16
3. 개발환경 구축  (0) 2019.03.15
2. 악성코드와 백신  (0) 2019.03.15