6.1 악성코드 패턴을 별도 파일로 분리
6.1.1 virus.db 파일 생성
'antivirus.py'의 VirusDB에 담긴 내용을 별도의 파일인 'virus.db'로 분리한다. 파일에 저장된 악성코드 패턴의 구성은 '악성코드 파일 크기:MD5 해시:악성코드 이름'이다. 이렇게 별도의 파일로 만듦으로써 기존의 VirusDB에 저장되어 있던 악성코드 패턴을 필요없으므로 다 지운다.
6.2 virus.db 파일에서 악성코드 패턴 로딩
6.2.1 virus.db 파일에서 VirusDB로 악성코드 패턴 로딩
'virus.db' 파일에는 악성코드 패턴이 한 줄 단위로 저장되어 있다. 이 파일로부터 악성코드 패턴을 로딩하는 함수를 따로 만든다. 아래의 'LoadVirusDB()'이다.
def LoadVirusDB():
fp = open('virus.db', 'rb')
while True:
#한 줄 읽음
line = fp.readline()
if not line: break
#엔터 입력 제거
line = line.strip()
VirusDB.append(line)
fp.close()
6.2.2 전체 소스코드
#-*-coding:utf-8 -*-
import sys
import os
import hashlib
VirusDB = []
vdb = []
vsize = []
def LoadVirusDB():
fp = open('virus.db', 'rb')
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(seze) == 0
vsize.append(size)
def SearchVDB(fmd5):
for t in vdb:
if t[0] == fmd5:
return True, t[1]
return False, ''
if __name__ == '__main__':
LoadVirusDB()
MakeVirusDB()
if len(sys.argv) != 2:
print 'Usage : antivirus.py [file]'
exit(0)
fname = sys.argv[1]
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)
6.3 악성코드 패턴 파일 암/복호화
백신과 악성코드 패턴 파일을 분리하였다. 이는 백신 소스코드에 더 이상 악성코드 패턴이 존재하지 않고, 별도의 파일을 사용자에게 배포해야 한다는 의미이다. 하지만, 이렇게 되면 사용자가 악성코드 패턴 파일을 통해 임의로 편집을 할 수 있게 된다. 악성코드 패턴 파일은 백신 배포자만이 수정할 수 있어야만 안전하다. 이를 위해 악성코드 패턴 파일을 암호화하면 된다.
6.3.1 암호화 도구 만들기
암호화 도구는 백신 업체만 소지해야 한다. 만약에 암호화 도구가 사용자에게도 배포된다면 악의적으로 수정할 수 있기 때문이다. 암호화 프로그램의 이름은 책과 동일하게 kmake.py로 하겠다. 아래는 kmake.py 소스이다.
#-*- coding:utf-8 -*-
import sys
import zlib
import hashlib
import os
#선택한 파일 암호화
def main():
if len(sys.argv) != 2:
print 'Usage : kmake.py [file]'
return
#암호화 할 파일
fname = sys.argv[1]
tname = fname
#바이너리 형식으로 파일을 연 후, buf에 내용 저장
fp = open(tname, 'rb')
buf = fp.read()
fp.close()
#buf를 압축하여 buf2에 저장
buf2 = zlib.compress(buf)
#buf3에 buf2에 저장된 내용들을 한글자씩 0xff로 xor연산하여 저장
buf3 = ''
for c int buf2:
buf3 += chr(ord(c) ^ 0xff)
#'KAVM'문자열을 xor 연산한 내용 앞에 붙인다
buf4 = 'KAVM' + buf3
#buf4의 내용을 3번에 거쳐서 해쉬값을 얻는다
f = buf4
for i in range(3):
md5 = hashlib.md5()
md5.update(f)
f = md5.hexdigest()
#3번에 거쳐 얻은 해쉬값을 buf4 뒤에 붙인다
buf4 += f
#암호화 한 내용을 저장할 파일을 생성한다
#원본 파일 이름에 확장자는 'kmd'를 사용해 새로 만든다
kmd_name = fname.split('.')[0] + '.kmd'
fp = open(kmd_name, 'wb')
fp.wirte(buf4)
fp.close()
print '%s -> %s' % (fname, kmd_name)
if __name__ == '__main__':
main()
6.3.2 복호화 모듈 만들기
암호화 도구에 의해 'virus.db' 파일이 'virus.kmd' 파일로 변경되었다. 백신 프로그램에서는 암호화된 virus.kmd 파일을 읽어올 수 없으므로 복호화가 필요하다. 복호화를 진행하는 함수는 책과 동일하게 'DecodeKMD()'로 하겠다. 아래는 'DecodeKMD()' 함수의 소스이다.
def DecodeKMD(fname):
try:
fp = openo(fname, 'rb')
buf = fp.read()
fp.close()
#해쉬값 32byte
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 = ''
#'KAVM'문자열 제외
for c in buf2[4:]:
buf3 += chr(ord(c) ^ 0xff)
buf4 = zlib.decompress(buf3)
return buf4
except:
pass
return None
앞서 암호화를 할 때에 원본 파일 내용의 뒤에 해쉬값을 붙여두었다. 이는 파일의 변조 유무를 확인하기 위한것이다. 붙여두었던 해쉬값을 분리한 뒤, 해당 파일의 해쉬값을 구한 뒤에 뒤에 붙어있던 해쉬값과 비교하여 동일한지 확인한다. 같다면 다시 xor 연산을 하여 원본 파일의 내용을 구한다.
위의 소스를 분석하다가 이해가 안되는 부분이 있었다. 바로 해쉬값의 크기가 32byte라는 부분이었다. 본인은 md5 해쉬의 크기는 16byte로 알고 있었다. 구글링을 통해 알아낸 결과 hashlib에서 사용하는 md5는 32byte 크기의 결과를 만들어준다는 것이었다. 이 부분에 대해서는 확실하지는 않으므로 추후 확실한 정보를 알게 되면 수정하도록 하겠다.
6.3.3 백신에 복호화 적용
백신 소스코드에 복호화 함수를 적용해보자. 우선 먼저 알아두어야 할 것은 복호화 함수를 사용해 만든 원본 내용에는 내용을 한 줄씩 읽어오는 fp.readline 함수를 사용할 수 없다는 것이다. 그 이유에 대해서는 아직 정확하게 모르기 때문에 추후에 수정하도록 하겠다. fp.readline을 사용할 수 있도록 만드는 방법은 StringIO 모듈을 사용하는 것이다. 복호화를 적용한 전체 소스코드를 확인해보자.
#-*-coding:utf-8 -*-
import sys
import os
import hashlib
import StringIO
VirusDB = []
vdb = []
vsize = []
def DecodeKMD(fname):
try:
fp = openo(fname, 'rb')
buf = fp.read()
fp.close()
#해쉬값 32byte
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 = ''
#'KAVM'문자열 제외
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(seze) == 0
vsize.append(size)
def SearchVDB(fmd5):
for t in vdb:
if t[0] == fmd5:
return True, t[1]
return False, ''
if __name__ == '__main__':
LoadVirusDB()
MakeVirusDB()
if len(sys.argv) != 2:
print 'Usage : antivirus.py [file]'
exit(0)
fname = sys.argv[1]
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)
6.4 정리
악성코드는 계속적으로 증가하고 있다. 이렇게 증가하면서 악성코드 패턴 또한 증가하기 마련이다. 백신 프로그램 내부에 악성코드 패턴을 담고 있다면, 이미 배포된 백신 프로그램을 악성코드 패턴이 증가할 때마다 수정해주어야하는 불편함이 생긴다. 이를 방지하기 위해서 패턴 파일만을 배포하는 방법을 백신 업체들이 많이 사용한다고 한다.
위의 이유 때문에 분리된 악성코드 패턴 파일은 외부 사용자에 의해 조작될 가능성이 있다. 이 때문에 암호화가 필요하며 암호화된 파일을 복호화하는 기능 또한 백신 프로그램이 가지고 있어야 한다.
※ 참고 서적 : 파이썬으로 배우는 Anti-Virus 구조와 원리 (최원혁 지음)
'Project > Anti-Virus (Python)' 카테고리의 다른 글
7. 악성코드 진단/치료 모듈 분리 (1) | 2019.04.11 |
---|---|
5. 다양한 악성코드 진단/치료 (0) | 2019.03.17 |
4. 전용백신 개발하기 (0) | 2019.03.16 |
3. 개발환경 구축 (0) | 2019.03.15 |
2. 악성코드와 백신 (0) | 2019.03.15 |